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 79f739a9..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 @@ -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", @@ -108,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 eb95c93e..92158fda 100644 --- a/integration-tests/test-environment/.config/nvim/init.lua +++ b/integration-tests/test-environment/.config/nvim/init.lua @@ -64,20 +64,22 @@ 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 }, + { + "nvim-telescope/telescope.nvim", + lazy = true, + opts = { + pickers = { + live_grep = { + theme = "dropdown", + }, + }, + }, + }, + { "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/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..d00a2d8e 100644 --- a/lua/yazi/keybinding_helpers.lua +++ b/lua/yazi/keybinding_helpers.lua @@ -163,16 +163,33 @@ 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 - config.integrations.grep_in_directory(last_directory) + 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, + }) + 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 end ---@param config YaziConfig ---@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,7 +199,24 @@ function YaziOpenerActions.grep_in_selected_files(config, chosen_files) table.insert(paths, plenary_path:new(path)) end - config.integrations.grep_in_selected_files(paths) + ---@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, + }) + 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 end ---@param config YaziConfig diff --git a/lua/yazi/types.lua b/lua/yazi/types.lua index b5ba047f..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? 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" | "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")