Skip to content

Commit

Permalink
feat: prevent conflicts with custom yazi config for <enter> (opt-in)
Browse files Browse the repository at this point in the history
Issue
=====

For custom ways to open files, such as "open in new vertical split",
"open in new tab", etc., yazi.nvim uses the `<enter>` key to open the
file. This is not very robust because the user might have set a custom
keybinding for the `<enter>` key in their yazi config. In this case, the
custom keybinding would be triggered instead of the file being opened.

Solution
========

Instead of relying on the `<enter>` key, yazi.nvim can now use `ya emit
open` to make yazi open the file(s) that are currently selected. This
completely avoids the issue, but requires a recent version of yazi
(0.4.0 or later).

To opt into this behaviour, set the following in your config:

```lua
{
  -- example for lazy.nvim
  "mikavilpas/yazi.nvim",
  -- ... (other settings here) ...
  ---@type YaziConfig
  opts = {
    future_features = {
      ya_emit_open = true,
      -- 👆🏻 this is the new setting
    },
  },
}
```

This issue was found in
#611 where
smart-enter.yazi was found to have this behaviour by default:
https://github.com/yazi-rs/plugins/tree/main/smart-enter.yazi#advanced
  • Loading branch information
mikavilpas committed Dec 12, 2024
1 parent 0b1ca50 commit 6059639
Show file tree
Hide file tree
Showing 11 changed files with 347 additions and 15 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,13 @@ You can optionally configure yazi.nvim by setting any of the options below.

future_features = {
-- Whether to use `ya emit reveal` to reveal files in the file manager.
-- This requires yazi 0.4.0 but will likely be the default in the
-- future.
ya_emit_reveal = true,
-- Requires yazi 0.4.0 or later (from 2024-12-08).
ya_emit_reveal = false,

-- Use `ya emit open` as a more robust implementation for opening files
-- in yazi. This can prevent conflicts with custom keymappings for the enter
-- key. Requires yazi 0.4.0 or later (from 2024-12-08).
ya_emit_open = false,
},
},
}
Expand Down
7 changes: 7 additions & 0 deletions integration-tests/MyTestDirectory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ export const MyTestDirectorySchema = z.object({
extension: z.literal("lua"),
stem: z.literal("modify_yazi_config_and_set_help_key."),
}),
"modify_yazi_config_do_not_use_ya_emit_open.lua": z.object({
name: z.literal("modify_yazi_config_do_not_use_ya_emit_open.lua"),
type: z.literal("file"),
extension: z.literal("lua"),
stem: z.literal("modify_yazi_config_do_not_use_ya_emit_open."),
}),
"modify_yazi_config_log_yazi_closed_successfully.lua": z.object({
name: z.literal(
"modify_yazi_config_log_yazi_closed_successfully.lua",
Expand Down Expand Up @@ -276,6 +282,7 @@ export const testDirectoryFiles = z.enum([
"config-modifications/modify_yazi_config_and_highlight_buffers_in_same_directory.lua",
"config-modifications/modify_yazi_config_and_open_multiple_files.lua",
"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_ya_emit_reveal.lua",
"config-modifications/notify_custom_events.lua",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import type { MyTestDirectoryFile } from "MyTestDirectory"
import {
isFileNotSelectedInYazi,
isFileSelectedInYazi,
} from "./utils/yazi-utils"

describe("opening files with yazi < 0.4.0", () => {
// versions before this do not support `ya emit open`. Instead, they send a
// fake <enter> keypress to yazi and react on the file that was opened by
// yazi. This approach is not very robust because the user might have set a
// custom enter key - in which case the whole thing does not work.
//
// This has been fixed for recent yazi versions by using `ya emit open` to
// send the file path to yazi. These tests are here until the legacy approach
// is removed.
beforeEach(() => {
cy.visit("/")
})

it("can open a file in a vertical split", () => {
cy.startNeovim({
startupScriptModifications: [
"modify_yazi_config_do_not_use_ya_emit_open.lua",
],
}).then((dir) => {
cy.contains("If you see this text, Neovim is ready!")
cy.typeIntoTerminal("{upArrow}")
isFileNotSelectedInYazi("file2.txt" satisfies MyTestDirectoryFile)
cy.typeIntoTerminal(
`/${"file2.txt" satisfies MyTestDirectoryFile}{enter}`,
)
cy.typeIntoTerminal("{esc}") // hide the search highlight
isFileSelectedInYazi("file2.txt" satisfies MyTestDirectoryFile)
cy.typeIntoTerminal("{control+v}")

// yazi should now be closed
cy.contains("-- TERMINAL --").should("not.exist")

// the file path must be visible at the bottom
cy.contains(dir.contents["file2.txt"].name)
cy.contains(dir.contents["initial-file.txt"].name)
})
})

it("can open a file in a horizontal split", () => {
cy.startNeovim({
startupScriptModifications: [
"modify_yazi_config_do_not_use_ya_emit_open.lua",
],
}).then((dir) => {
cy.contains("If you see this text, Neovim is ready!")
cy.typeIntoTerminal("{upArrow}")
cy.contains(dir.contents["file2.txt"].name)
cy.typeIntoTerminal(`/${dir.contents["file2.txt"].name}{enter}`)
cy.typeIntoTerminal("{esc}") // hide the search highlight
isFileSelectedInYazi(dir.contents["file2.txt"].name)
cy.typeIntoTerminal("{control+x}")

// yazi should now be closed
cy.contains("-- TERMINAL --").should("not.exist")

// the file path must be visible at the bottom
cy.contains(dir.contents["file2.txt"].name)
cy.contains(dir.contents["initial-file.txt"].name)
})
})

it("can open a file in a new tab", () => {
cy.startNeovim({
startupScriptModifications: [
"modify_yazi_config_do_not_use_ya_emit_open.lua",
],
}).then((dir) => {
cy.contains("If you see this text, Neovim is ready!")
cy.typeIntoTerminal("{upArrow}")
isFileNotSelectedInYazi(dir.contents["file2.txt"].name)
cy.contains(dir.contents["file2.txt"].name)
cy.typeIntoTerminal(`/${dir.contents["file2.txt"].name}{enter}`)
cy.typeIntoTerminal("{esc}") // hide the search highlight
isFileSelectedInYazi(dir.contents["file2.txt"].name)
cy.typeIntoTerminal("{control+t}")

// yazi should now be closed
cy.contains("-- TERMINAL --").should("not.exist")

cy.contains(
// match some text from inside the file
"Hello",
)
cy.runExCommand({ command: "tabnext" })

cy.contains("If you see this text, Neovim is ready!")

cy.contains(dir.contents["file2.txt"].name)
cy.contains(dir.contents["initial-file.txt"].name)
})
})

it("can send file names to the quickfix list", () => {
cy.startNeovim({
filename: "file2.txt",
startupScriptModifications: [
"modify_yazi_config_do_not_use_ya_emit_open.lua",
],
}).then((dir) => {
cy.contains("Hello")
cy.typeIntoTerminal("{upArrow}")

// wait for yazi to open
cy.contains(dir.contents["file2.txt"].name)

// file2.txt should be selected
isFileSelectedInYazi("file2.txt" satisfies MyTestDirectoryFile)

// select file2, the cursor moves one line down to the next file
cy.typeIntoTerminal(" ")
isFileNotSelectedInYazi("file2.txt" satisfies MyTestDirectoryFile)

// also select the next file because multiple files have to be selected
isFileSelectedInYazi("file3.txt" satisfies MyTestDirectoryFile)
cy.typeIntoTerminal(" ")
isFileNotSelectedInYazi("file3.txt" satisfies MyTestDirectoryFile)
cy.typeIntoTerminal("{control+q}")

// yazi should now be closed
cy.contains("-- TERMINAL --").should("not.exist")

// items in the quickfix list should now be visible
cy.contains(`${dir.contents["file2.txt"].name}||`)
cy.contains(`${dir.contents["file3.txt"].name}||`)
})
})

it("can copy the relative path to the initial file", () => {
// the copied path should be relative to the file/directory yazi was
// started in (the initial file)

cy.startNeovim({
startupScriptModifications: [
"modify_yazi_config_do_not_use_ya_emit_open.lua",
],
}).then((dir) => {
cy.contains("If you see this text, Neovim is ready!")

cy.typeIntoTerminal("{upArrow}")
isFileNotSelectedInYazi("file2.txt" satisfies MyTestDirectoryFile)

// enter another directory and select a file
cy.typeIntoTerminal("/routes{enter}")
cy.contains("posts.$postId")
cy.typeIntoTerminal("{rightArrow}")
cy.contains(
dir.contents.routes.contents["posts.$postId"].contents["route.tsx"]
.name,
) // file in the directory
cy.typeIntoTerminal("{rightArrow}")
cy.typeIntoTerminal(
`/${
dir.contents.routes.contents["posts.$postId"].contents[
"adjacent-file.txt"
].name
}{enter}{esc}`,
// esc to hide the search highlight
)
isFileSelectedInYazi(
dir.contents.routes.contents["posts.$postId"].contents[
"adjacent-file.txt"
].name,
)

// the file contents should now be visible
cy.contains("this file is adjacent-file.txt")

cy.typeIntoTerminal("{control+y}")

// yazi should now be closed
cy.contains(
dir.contents.routes.contents["posts.$postId"].contents["route.tsx"]
.name,
).should("not.exist")

// the relative path should now be in the clipboard. Let's paste it to
// the file to verify this.
// NOTE: the test-setup configures the `"` register to be the clipboard
cy.typeIntoTerminal("o{enter}{esc}")
cy.runLuaCode({ luaCode: `return vim.fn.getreg('"')` }).then((result) => {
expect(result.value).to.contain(
"routes/posts.$postId/adjacent-file.txt" satisfies MyTestDirectoryFile,
)
})
})
})

it("can copy the relative paths of multiple selected files", () => {
// similarly, the copied path should be relative to the file/directory yazi
// was started in (the initial file)

cy.startNeovim({
startupScriptModifications: [
"modify_yazi_config_do_not_use_ya_emit_open.lua",
],
}).then((dir) => {
cy.contains("If you see this text, Neovim is ready!")

cy.typeIntoTerminal("{upArrow}")
cy.contains(dir.contents["file2.txt"].name)

// enter another directory and select a file
cy.typeIntoTerminal("/routes{enter}")
cy.contains("posts.$postId")
cy.typeIntoTerminal("{rightArrow}")
cy.contains(
dir.contents.routes.contents["posts.$postId"].contents["route.tsx"]
.name,
) // file in the directory
cy.typeIntoTerminal("{rightArrow}")
cy.typeIntoTerminal("{control+a}")

cy.typeIntoTerminal("{control+y}")

// yazi should now be closed
cy.contains(
dir.contents.routes.contents["posts.$postId"].contents["route.tsx"]
.name,
).should("not.exist")

// the relative path should now be in the clipboard. Let's paste it to
// the file to verify this.
// NOTE: the test-setup configures the `"` register to be the clipboard
cy.typeIntoTerminal("o{enter}{esc}")
cy.runLuaCode({ luaCode: `return vim.fn.getreg('"')` }).then((result) => {
expect(result.value).to.eql(
(
[
"routes/posts.$postId/adjacent-file.txt",
"routes/posts.$postId/route.tsx",
"routes/posts.$postId/should-be-excluded-file.txt",
] satisfies MyTestDirectoryFile[]
).join("\n"),
)
})
})
})
})
3 changes: 3 additions & 0 deletions integration-tests/test-environment/.config/nvim/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ local plugins = {
clipboard_register = '"',
-- allows logging debug data, which can be shown in CI when cypress tests fail
log_level = vim.log.levels.DEBUG,
future_features = {
ya_emit_open = true,
},
integrations = {
grep_in_directory = function(directory)
require("telescope.builtin").live_grep({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---@module "yazi"

require("yazi").setup(
---@type YaziConfig
{
future_features = {
ya_emit_open = false,
},
}
)
10 changes: 7 additions & 3 deletions lua/yazi/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ function M.set_keymappings(yazi_buffer, config, context)
{ "t" },
config.keymaps.open_file_in_vertical_split,
function()
keybinding_helpers.open_file_in_vertical_split(config)
keybinding_helpers.open_file_in_vertical_split(config, context.api)
end,
{ buffer = yazi_buffer }
)
Expand All @@ -125,7 +125,7 @@ function M.set_keymappings(yazi_buffer, config, context)
{ "t" },
config.keymaps.open_file_in_horizontal_split,
function()
keybinding_helpers.open_file_in_horizontal_split(config)
keybinding_helpers.open_file_in_horizontal_split(config, context.api)
end,
{ buffer = yazi_buffer }
)
Expand All @@ -134,6 +134,7 @@ function M.set_keymappings(yazi_buffer, config, context)
if config.keymaps.grep_in_directory ~= false then
vim.keymap.set({ "t" }, config.keymaps.grep_in_directory, function()
keybinding_helpers.select_current_file_and_close_yazi(config, {
api = context.api,
on_file_opened = function(chosen_file, _, _)
keybinding_helpers.grep_in_directory(config, chosen_file)
end,
Expand All @@ -146,7 +147,7 @@ function M.set_keymappings(yazi_buffer, config, context)

if config.keymaps.open_file_in_tab ~= false then
vim.keymap.set({ "t" }, config.keymaps.open_file_in_tab, function()
keybinding_helpers.open_file_in_tab(config)
keybinding_helpers.open_file_in_tab(config, context.api)
end, { buffer = yazi_buffer })
end

Expand All @@ -159,6 +160,7 @@ function M.set_keymappings(yazi_buffer, config, context)
if config.keymaps.replace_in_directory ~= false then
vim.keymap.set({ "t" }, config.keymaps.replace_in_directory, function()
keybinding_helpers.select_current_file_and_close_yazi(config, {
api = context.api,
on_file_opened = function(chosen_file)
keybinding_helpers.replace_in_directory(config, chosen_file)
end,
Expand All @@ -173,6 +175,7 @@ function M.set_keymappings(yazi_buffer, config, context)
vim.keymap.set({ "t" }, config.keymaps.send_to_quickfix_list, function()
local openers = require("yazi.openers")
keybinding_helpers.select_current_file_and_close_yazi(config, {
api = context.api,
on_multiple_files_opened = openers.send_files_to_quickfix_list,
on_file_opened = function(chosen_file)
openers.send_files_to_quickfix_list({ chosen_file })
Expand Down Expand Up @@ -268,6 +271,7 @@ function M.set_keymappings(yazi_buffer, config, context)
config.keymaps.copy_relative_path_to_selected_files,
function()
keybinding_helpers.select_current_file_and_close_yazi(config, {
api = context.api,
on_file_opened = function(chosen_file)
local relative_path = require("yazi.utils").relative_path(
config,
Expand Down
8 changes: 8 additions & 0 deletions lua/yazi/health.lua
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ return {
end
end

if config.future_features and config.future_features.ya_emit_open then
if not checker.ge(yazi_semver, "0.4.0") then
vim.health.warn(
"You have enabled `future_features.ya_emit_open` in your config. This requires yazi.nvim version 0.4.0 or newer."
)
end
end

vim.health.start("yazi.config")
vim.health.info(table.concat({
"hint: execute the following command to see your configuration: >",
Expand Down
Loading

0 comments on commit 6059639

Please sign in to comment.