From 6ea5beaa53f3572a65cc22b28ef89c90148e29fd Mon Sep 17 00:00:00 2001 From: Mika Vilpas Date: Sat, 14 Dec 2024 21:18:32 +0200 Subject: [PATCH 1/2] refactor: telescope integrations are specified with `"telescope"` This preserves the old behaviour but more easily allows for an alternative builtin implementation to be used in the next commit. --- .../integrations.cy.ts | 21 ++++++++++++ .../test-environment/.config/nvim/init.lua | 23 ++++++------- lua/yazi/config.lua | 22 ++----------- lua/yazi/keybinding_helpers.lua | 32 ++++++++++++++++++- lua/yazi/types.lua | 4 +-- 5 files changed, 68 insertions(+), 34 deletions(-) diff --git a/integration-tests/cypress/e2e/using-ya-to-read-events/integrations.cy.ts b/integration-tests/cypress/e2e/using-ya-to-read-events/integrations.cy.ts index 79f739a9..13390be7 100644 --- a/integration-tests/cypress/e2e/using-ya-to-read-events/integrations.cy.ts +++ b/integration-tests/cypress/e2e/using-ya-to-read-events/integrations.cy.ts @@ -74,10 +74,31 @@ describe("grug-far integration (search and replace)", () => { }) describe("telescope integration (search)", () => { + // https://github.com/nvim-telescope/telescope.nvim beforeEach(() => { cy.visit("/") }) + it("can use telescope.nvim to search in the current directory", () => { + cy.startNeovim({ + filename: "routes/posts.$postId/adjacent-file.txt", + }).then((dir) => { + cy.contains("this file is adjacent-file.txt") + cy.typeIntoTerminal("{upArrow}") + cy.contains( + dir.contents.routes.contents["posts.$postId"].contents["route.tsx"] + .name, + ) + + cy.typeIntoTerminal("{control+s}") + + cy.contains(new RegExp(`Grep in testdirs/.*?/routes/posts.\\$postId`)) + + // verify this manually for now as I'm a bit scared this will be too + // flaky + }) + }) + it("can use telescope.nvim to search, limited to the selected files only", () => { cy.startNeovim({ filename: "routes/posts.$postId/adjacent-file.txt", diff --git a/integration-tests/test-environment/.config/nvim/init.lua b/integration-tests/test-environment/.config/nvim/init.lua index eb95c93e..c23a3ad0 100644 --- a/integration-tests/test-environment/.config/nvim/init.lua +++ b/integration-tests/test-environment/.config/nvim/init.lua @@ -64,20 +64,21 @@ local plugins = { ya_emit_open = true, }, integrations = { - grep_in_directory = function(directory) - require("telescope.builtin").live_grep({ - -- disable previewer to be able to see the full directory name. The - -- tests can make assertions on this path. - previewer = false, - search = "", - prompt_title = "Grep in " .. directory, - cwd = directory, - }) - end, + grep_in_directory = "telescope", + }, + }, + }, + { + "nvim-telescope/telescope.nvim", + lazy = true, + opts = { + pickers = { + live_grep = { + theme = "dropdown", + }, }, }, }, - { "nvim-telescope/telescope.nvim", lazy = true }, { "catppuccin/nvim", name = "catppuccin", priority = 1000 }, { "https://github.com/MagicDuck/grug-far.nvim", opts = {} }, { "folke/snacks.nvim", opts = {} }, diff --git a/lua/yazi/config.lua b/lua/yazi/config.lua index 2e5f1bbb..cd06bf2e 100644 --- a/lua/yazi/config.lua +++ b/lua/yazi/config.lua @@ -45,26 +45,8 @@ function M.default() hovered_buffer_in_same_directory = nil, }, integrations = { - grep_in_directory = function(directory) - require("telescope.builtin").live_grep({ - search = "", - prompt_title = "Grep in " .. directory, - cwd = directory, - }) - end, - grep_in_selected_files = function(selected_files) - ---@type string[] - local files = {} - for _, path in ipairs(selected_files) do - files[#files + 1] = path:make_relative(vim.uv.cwd()):gsub(" ", "\\ ") - end - - require("telescope.builtin").live_grep({ - search = "", - prompt_title = string.format("Grep in %d paths", #files), - search_dirs = files, - }) - end, + grep_in_directory = "telescope", + grep_in_selected_files = "telescope", replace_in_directory = function(directory) -- limit the search to the given path -- diff --git a/lua/yazi/keybinding_helpers.lua b/lua/yazi/keybinding_helpers.lua index 924a101e..157b5503 100644 --- a/lua/yazi/keybinding_helpers.lua +++ b/lua/yazi/keybinding_helpers.lua @@ -163,9 +163,22 @@ end ---@return nil function YaziOpenerActions.grep_in_directory(config, chosen_file) if config.integrations.grep_in_directory == nil then + -- the user has opted out of this feature for some reason. Do nothing. return end - local last_directory = utils.dir_of(chosen_file).filename + local cwd = vim.uv.cwd() + local last_directory = utils.dir_of(chosen_file):make_relative(cwd) + + if config.integrations.grep_in_directory == "telescope" then + require("telescope.builtin").live_grep({ + search = "", + prompt_title = "Grep in " .. last_directory, + cwd = last_directory, + }) + return + end + + -- the user has a custom implementation. Call it. config.integrations.grep_in_directory(last_directory) end @@ -173,6 +186,7 @@ end ---@param chosen_files string[] function YaziOpenerActions.grep_in_selected_files(config, chosen_files) if config.integrations.grep_in_selected_files == nil then + -- the user has opted out of this feature for some reason. Do nothing. return end @@ -182,6 +196,22 @@ function YaziOpenerActions.grep_in_selected_files(config, chosen_files) table.insert(paths, plenary_path:new(path)) end + if config.integrations.grep_in_selected_files == "telescope" then + ---@type string[] + local files = {} + for _, path in ipairs(paths) do + files[#files + 1] = path:make_relative(vim.uv.cwd()):gsub(" ", "\\ ") + end + + require("telescope.builtin").live_grep({ + search = "", + prompt_title = string.format("Grep in %d paths", #files), + search_dirs = files, + }) + return + end + + -- the user has a custom implementation. Call it. config.integrations.grep_in_selected_files(paths) end diff --git a/lua/yazi/types.lua b/lua/yazi/types.lua index b5ba047f..27ec3a34 100644 --- a/lua/yazi/types.lua +++ b/lua/yazi/types.lua @@ -54,8 +54,8 @@ ---@field public yazi_opened_multiple_files fun(chosen_files: string[], config: YaziConfig, state: YaziClosedState): nil ---@class (exact) YaziConfigIntegrations # Defines settings for integrations with other plugins and tools ----@field public grep_in_directory? fun(directory: string): nil "a function that will be called when the user wants to grep in a directory" ----@field public grep_in_selected_files? fun(selected_files: Path[]): nil"called to grep on files that were selected in yazi" +---@field public grep_in_directory? "telescope" | fun(directory: string): nil "implementation to be called when the user wants to grep in a directory. Defaults to `"telescope"`" +---@field public grep_in_selected_files? "telescope" | fun(selected_files: Path[]): nil "called to grep on files that were selected in yazi. Defaults to `"telescope"`" ---@field public replace_in_directory? fun(directory: Path, selected_files?: Path[]): nil "called to start a replacement operation on some directory; by default uses grug-far.nvim" ---@field public replace_in_selected_files? fun(selected_files?: Path[]): nil "called to start a replacement operation on files that were selected in yazi; by default uses grug-far.nvim" ---@field public resolve_relative_path_application? string "the application that will be used to resolve relative paths. By default, this is GNU `realpath` on Linux and `grealpath` on macOS" From f0bef5e737597d944d257757a343d51d75a61a62 Mon Sep 17 00:00:00 2001 From: Mika Vilpas Date: Sun, 15 Dec 2024 16:01:39 +0200 Subject: [PATCH 2/2] feat: add fzf-lua integration for grepping in files and dirs Previously, it was only possible to use Telescope to grep (search for text) in files and directories. This commit adds support for using fzf-lua.nvim. The default continues to be Telescope, but users can now set the following values to opt into using fzf-lua: ```lua ---@type YaziConfig { -- ... other settings integrations = { grep_in_directory = "fzf-lua", grep_in_selected_files = "fzf-lua", }, } ``` --- .github/workflows/test.yml | 20 +++-- README.md | 11 ++- integration-tests/MyTestDirectory.ts | 7 ++ .../integrations.cy.ts | 82 +++++++++++++++++++ .../test-environment/.config/nvim/init.lua | 1 + .../modify_yazi_config_use_fzf_lua.lua | 11 +++ lua/yazi/keybinding_helpers.lua | 32 ++++---- lua/yazi/types.lua | 4 +- spec/yazi/keybinding_helpers_spec.lua | 9 +- 9 files changed, 151 insertions(+), 26 deletions(-) create mode 100644 integration-tests/test-environment/config-modifications/modify_yazi_config_use_fzf_lua.lua diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d71b2b76..b20652d9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,12 +18,22 @@ jobs: - uses: actions/checkout@v4.2.2 - name: Set up dependencies run: | - # ripgrep is a telescope dependency - which rg || { - sudo apt-get install ripgrep - } + # ripgrep is a dependency of telescope and fzf-lua + sudo apt-get install ripgrep + rg --version + + # fd is a dependency of telescope and fzf-lua + # https://github.com/sharkdp/fd?tab=readme-ov-file#on-ubuntu + # make sure it's available as `fd` - there seems to be some conflict in Ubuntu + sudo apt-get install fd-find + sudo ln -s $(which fdfind) /usr/local/bin/fd + fd --version + + # install fzf + sudo apt install fzf + fzf --version - # realpath is used to resolve relative paths + # realpath is used to resolve relative paths in yazi.nvim which realpath || { # just fail with an error message if realpath is not found echo "realpath is not installed, but it should be part of GNU coreutils and included in Ubuntu" diff --git a/README.md b/README.md index 6ad3a70a..8b77d385 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,10 @@ open yazi in a floating window in Neovim. vertical split, a horizontal split, a new tab, as quickfix items... - Integrations to other plugins and tools, if they are installed: - - For [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim): you - can grep/search in the directory yazi is in + - For [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) and + [fzf-lua.nvim](https://github.com/ibhagwan/fzf-lua): you can grep/search in + the directory yazi is in. Select some files to limit the search to those + files only. - For [grug-far.nvim](https://github.com/MagicDuck/grug-far.nvim): you can search and replace in the directory yazi is in - Copy the relative path from the start file to the currently hovered file. @@ -313,7 +315,10 @@ These are the default keybindings that are available when yazi is open: separately: - ``: search in the current yazi directory using [telescope](https://github.com/nvim-telescope/telescope.nvim)'s `live_grep`, - if available. + if available. Optionally you can use + [fzf-lua.nvim](https://github.com/ibhagwan/fzf-lua) or provide your own + implementation - see the instructions in the configuration section for more + info. - if multiple files/directories are selected in yazi, the search and replace will only be done in the selected files/directories - ``: search and replace in the current yazi directory using diff --git a/integration-tests/MyTestDirectory.ts b/integration-tests/MyTestDirectory.ts index a1805b92..41077c7c 100644 --- a/integration-tests/MyTestDirectory.ts +++ b/integration-tests/MyTestDirectory.ts @@ -107,6 +107,12 @@ export const MyTestDirectorySchema = z.object({ extension: z.literal("lua"), stem: z.literal("modify_yazi_config_log_yazi_closed_successfully."), }), + "modify_yazi_config_use_fzf_lua.lua": z.object({ + name: z.literal("modify_yazi_config_use_fzf_lua.lua"), + type: z.literal("file"), + extension: z.literal("lua"), + stem: z.literal("modify_yazi_config_use_fzf_lua."), + }), "modify_yazi_config_use_ya_emit_reveal.lua": z.object({ name: z.literal("modify_yazi_config_use_ya_emit_reveal.lua"), type: z.literal("file"), @@ -284,6 +290,7 @@ export const testDirectoryFiles = z.enum([ "config-modifications/modify_yazi_config_and_set_help_key.lua", "config-modifications/modify_yazi_config_do_not_use_ya_emit_open.lua", "config-modifications/modify_yazi_config_log_yazi_closed_successfully.lua", + "config-modifications/modify_yazi_config_use_fzf_lua.lua", "config-modifications/modify_yazi_config_use_ya_emit_reveal.lua", "config-modifications/notify_custom_events.lua", "config-modifications/notify_hover_events.lua", diff --git a/integration-tests/cypress/e2e/using-ya-to-read-events/integrations.cy.ts b/integration-tests/cypress/e2e/using-ya-to-read-events/integrations.cy.ts index 13390be7..d57fe1d4 100644 --- a/integration-tests/cypress/e2e/using-ya-to-read-events/integrations.cy.ts +++ b/integration-tests/cypress/e2e/using-ya-to-read-events/integrations.cy.ts @@ -129,3 +129,85 @@ describe("telescope integration (search)", () => { }) }) }) + +describe("fzf-lua integration (grep)", () => { + beforeEach(() => { + cy.visit("/") + }) + + it("can use fzf-lua.nvim to search in the current directory", () => { + cy.startNeovim({ + filename: "routes/posts.$postId/adjacent-file.txt", + startupScriptModifications: ["modify_yazi_config_use_fzf_lua.lua"], + }).then((dir) => { + cy.contains("this file is adjacent-file.txt") + cy.typeIntoTerminal("{upArrow}") + cy.contains( + dir.contents.routes.contents["posts.$postId"].contents["route.tsx"] + .name, + ) + + cy.typeIntoTerminal("{control+s}") + + // wait for fzf-lua to be visible + cy.contains("to Fuzzy Search") + + cy.typeIntoTerminal("this") + + // results should be visible + cy.contains( + dir.contents.routes.contents["posts.$postId"].contents[ + "should-be-excluded-file.txt" + ].name, + ) + + // results from outside the directory should not be visible. This + // verifies the search is limited to the current directory + cy.contains(dir.contents["initial-file.txt"].name).should("not.exist") + }) + }) + + it("can use fzf-lua.nvim to search, limited to the selected files only", () => { + // https://github.com/ibhagwan/fzf-lua + cy.startNeovim({ + filename: "routes/posts.$postId/route.tsx", + startupScriptModifications: ["modify_yazi_config_use_fzf_lua.lua"], + }).then((dir) => { + // wait until the file contents are visible + cy.contains("02c67730-6b74-4b7c-af61-fe5844fdc3d7") + + cy.typeIntoTerminal("{upArrow}") + cy.contains( + dir.contents.routes.contents["posts.$postId"].contents["route.tsx"] + .name, + ) + + // select the current file and the file below. There are three files in + // this directory so two will be selected and one will be left + // unselected + cy.typeIntoTerminal("vk") + cy.typeIntoTerminal("{control+s}") + + // telescope should be open now + cy.contains("to Fuzzy Search") + + // search for some file content. This should match + // ../../../test-environment/routes/posts.$postId/adjacent-file.txt + cy.typeIntoTerminal("this") + + // some results should be visible + cy.contains( + dir.contents.routes.contents["posts.$postId"].contents[ + "adjacent-file.txt" + ].name, + ) + cy.contains("02c67730-6b74-4b7c-af61-fe5844fdc3d7") + + cy.contains( + dir.contents.routes.contents["posts.$postId"].contents[ + "should-be-excluded-file.txt" + ].name, + ).should("not.exist") + }) + }) +}) diff --git a/integration-tests/test-environment/.config/nvim/init.lua b/integration-tests/test-environment/.config/nvim/init.lua index c23a3ad0..92158fda 100644 --- a/integration-tests/test-environment/.config/nvim/init.lua +++ b/integration-tests/test-environment/.config/nvim/init.lua @@ -79,6 +79,7 @@ local plugins = { }, }, }, + { "ibhagwan/fzf-lua" }, { "catppuccin/nvim", name = "catppuccin", priority = 1000 }, { "https://github.com/MagicDuck/grug-far.nvim", opts = {} }, { "folke/snacks.nvim", opts = {} }, diff --git a/integration-tests/test-environment/config-modifications/modify_yazi_config_use_fzf_lua.lua b/integration-tests/test-environment/config-modifications/modify_yazi_config_use_fzf_lua.lua new file mode 100644 index 00000000..aca05211 --- /dev/null +++ b/integration-tests/test-environment/config-modifications/modify_yazi_config_use_fzf_lua.lua @@ -0,0 +1,11 @@ +---@module "yazi" + +require("yazi").setup( + ---@type YaziConfig + { + integrations = { + grep_in_selected_files = "fzf-lua", + grep_in_directory = "fzf-lua", + }, + } +) diff --git a/lua/yazi/keybinding_helpers.lua b/lua/yazi/keybinding_helpers.lua index 157b5503..d00a2d8e 100644 --- a/lua/yazi/keybinding_helpers.lua +++ b/lua/yazi/keybinding_helpers.lua @@ -175,11 +175,14 @@ function YaziOpenerActions.grep_in_directory(config, chosen_file) prompt_title = "Grep in " .. last_directory, cwd = last_directory, }) - return + elseif config.integrations.grep_in_directory == "fzf-lua" then + require("fzf-lua").live_grep({ + search_paths = { last_directory }, + }) + else + -- the user has a custom implementation. Call it. + config.integrations.grep_in_directory(last_directory) end - - -- the user has a custom implementation. Call it. - config.integrations.grep_in_directory(last_directory) end ---@param config YaziConfig @@ -196,23 +199,24 @@ function YaziOpenerActions.grep_in_selected_files(config, chosen_files) table.insert(paths, plenary_path:new(path)) end - if config.integrations.grep_in_selected_files == "telescope" then - ---@type string[] - local files = {} - for _, path in ipairs(paths) do - files[#files + 1] = path:make_relative(vim.uv.cwd()):gsub(" ", "\\ ") - end + ---@type string[] + local files = {} + for _, path in ipairs(paths) do + files[#files + 1] = path:make_relative(vim.uv.cwd()):gsub(" ", "\\ ") + end + if config.integrations.grep_in_selected_files == "telescope" then require("telescope.builtin").live_grep({ search = "", prompt_title = string.format("Grep in %d paths", #files), search_dirs = files, }) - return + elseif config.integrations.grep_in_selected_files == "fzf-lua" then + require("fzf-lua").live_grep({ search_paths = files }) + else + -- the user has a custom implementation. Call it. + config.integrations.grep_in_selected_files(paths) end - - -- the user has a custom implementation. Call it. - config.integrations.grep_in_selected_files(paths) end ---@param config YaziConfig diff --git a/lua/yazi/types.lua b/lua/yazi/types.lua index 27ec3a34..4ba7eb9f 100644 --- a/lua/yazi/types.lua +++ b/lua/yazi/types.lua @@ -54,8 +54,8 @@ ---@field public yazi_opened_multiple_files fun(chosen_files: string[], config: YaziConfig, state: YaziClosedState): nil ---@class (exact) YaziConfigIntegrations # Defines settings for integrations with other plugins and tools ----@field public grep_in_directory? "telescope" | fun(directory: string): nil "implementation to be called when the user wants to grep in a directory. Defaults to `"telescope"`" ----@field public grep_in_selected_files? "telescope" | fun(selected_files: Path[]): nil "called to grep on files that were selected in yazi. Defaults to `"telescope"`" +---@field public grep_in_directory? "telescope" | "fzf-lua" | fun(directory: string): nil "implementation to be called when the user wants to grep in a directory. Defaults to `"telescope"`" +---@field public grep_in_selected_files? "telescope" | "fzf-lua" | fun(selected_files: Path[]): nil "called to grep on files that were selected in yazi. Defaults to `"telescope"`" ---@field public replace_in_directory? fun(directory: Path, selected_files?: Path[]): nil "called to start a replacement operation on some directory; by default uses grug-far.nvim" ---@field public replace_in_selected_files? fun(selected_files?: Path[]): nil "called to start a replacement operation on files that were selected in yazi; by default uses grug-far.nvim" ---@field public resolve_relative_path_application? string "the application that will be used to resolve relative paths. By default, this is GNU `realpath` on Linux and `grealpath` on macOS" diff --git a/spec/yazi/keybinding_helpers_spec.lua b/spec/yazi/keybinding_helpers_spec.lua index 68c1f16a..2c647f64 100644 --- a/spec/yazi/keybinding_helpers_spec.lua +++ b/spec/yazi/keybinding_helpers_spec.lua @@ -4,6 +4,7 @@ local keybinding_helpers = require("yazi.keybinding_helpers") local match = require("luassert.match") local plenary_path = require("plenary.path") local stub = require("luassert.stub") +local spy = require("luassert.spy") describe("keybinding_helpers", function() local vim_cmd_stub @@ -25,7 +26,9 @@ describe("keybinding_helpers", function() describe("grep_in_directory", function() it("should grep in the parent directory for a file", function() local config = config_module.default() - local s = stub(config.integrations, "grep_in_directory") + local s = spy.new(function() end) + ---@diagnostic disable-next-line: assign-type-mismatch + config.integrations.grep_in_directory = s keybinding_helpers.grep_in_directory(config, "/tmp/file") @@ -34,7 +37,9 @@ describe("keybinding_helpers", function() it("should grep in the directory when a directory is passed", function() local config = config_module.default() - local s = stub(config.integrations, "grep_in_directory") + local s = spy.new(function() end) + ---@diagnostic disable-next-line: assign-type-mismatch + config.integrations.grep_in_directory = s keybinding_helpers.grep_in_directory(config, "/tmp")