Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add fzf-lua integration for grepping in files and dirs #620

Merged
merged 2 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,22 @@ jobs:
- uses: actions/[email protected]
- 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"
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -313,7 +315,10 @@ These are the default keybindings that are available when yazi is open:
separately:
- `<c-s>`: 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
- `<c-g>`: search and replace in the current yazi directory using
Expand Down
7 changes: 7 additions & 0 deletions integration-tests/MyTestDirectory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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")
})
})
})
24 changes: 13 additions & 11 deletions integration-tests/test-environment/.config/nvim/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {} },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---@module "yazi"

require("yazi").setup(
---@type YaziConfig
{
integrations = {
grep_in_selected_files = "fzf-lua",
grep_in_directory = "fzf-lua",
},
}
)
22 changes: 2 additions & 20 deletions lua/yazi/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
--
Expand Down
40 changes: 37 additions & 3 deletions lua/yazi/keybinding_helpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions lua/yazi/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 7 additions & 2 deletions spec/yazi/keybinding_helpers_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")

Expand All @@ -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")

Expand Down
Loading