From c9ebbf6749980680a533125ac08a0d57257c04a9 Mon Sep 17 00:00:00 2001 From: Mika Vilpas Date: Fri, 26 Jul 2024 22:10:34 +0300 Subject: [PATCH] fix: close the floating terminal if it loses focus (#269) * refactor(tests): add mouse click support * fix: close the floating terminal if it loses focus The floating terminal that shows yazi running can lose focus when e.g. clicked outside of. This commit adds an autocmd that closes the floating terminal in these cases. The reason for this is that it's inconvenient to navigate back to the floating terminal to close it manually. Even if you did this, it was in normal mode - not insert mode which is typically what you want to be in when you're using yazi. --- integration-tests/client/client.ts | 18 +++++++++++- .../client/validateMouseEvent.ts | 17 +++++++++++ .../opening-files.cy.ts | 4 +++ .../e2e/using-ya-to-read-events/mouse.cy.ts | 28 +++++++++++++++++++ .../opening-files.cy.ts | 10 +++++-- integration-tests/cypress/support/commands.ts | 2 +- integration-tests/eslint.config.mjs | 7 +++++ integration-tests/server/server.ts | 8 ++++++ lua/yazi/window.lua | 7 +++++ 9 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 integration-tests/client/validateMouseEvent.ts create mode 100644 integration-tests/cypress/e2e/using-ya-to-read-events/mouse.cy.ts diff --git a/integration-tests/client/client.ts b/integration-tests/client/client.ts index 872dd17..eff2019 100644 --- a/integration-tests/client/client.ts +++ b/integration-tests/client/client.ts @@ -6,14 +6,17 @@ import { FitAddon } from "@xterm/addon-fit" import { Terminal } from "@xterm/xterm" import io from "socket.io-client" import type { + MouseEventMessage, StartNeovimMessage, StdinMessage, StdoutMessage, } from "../server/server" + import type { StartNeovimArguments, StartNeovimServerArguments, } from "./testEnvironmentTypes" +import { validateMouseEvent } from "./validateMouseEvent" const app = document.querySelector("#app") if (!app) { @@ -24,7 +27,6 @@ const terminal = new Terminal({ cursorBlink: false, convertEol: true, fontSize: 13, - // letterSpacing: 0.5, }) { const colors = flavors.macchiato.colors @@ -112,3 +114,17 @@ socket.on( terminal.onKey((event) => { socket.emit("stdin" satisfies StdinMessage, event.key) }) + +terminal.onData((data) => { + // this gets called for mouse events. However, some mouse events seem to + // confuse Neovim, so for now let's just send click events + + if (typeof data !== "string") { + throw new Error(`unexpected onData message type: '${JSON.stringify(data)}'`) + } + + const mouseEvent = validateMouseEvent(data) + if (mouseEvent) { + socket.emit("mouseEvent" satisfies MouseEventMessage, data) + } +}) diff --git a/integration-tests/client/validateMouseEvent.ts b/integration-tests/client/validateMouseEvent.ts new file mode 100644 index 0000000..50af9c7 --- /dev/null +++ b/integration-tests/client/validateMouseEvent.ts @@ -0,0 +1,17 @@ +// Function to parse mouse events +// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Button-event-tracking +export function validateMouseEvent(data: string): string | undefined { + const match = /\x1b\[<(\d+);(\d+);(\d+)([mM])/.exec(data) + if (match) { + const buttonCode = parseInt(match[1], 10) + const column = parseInt(match[2], 10) + const row = parseInt(match[3], 10) + const isRelease = match[4] === "m" + + console.log( + `Mouse event: buttonCode=${buttonCode}, column=${column}, row=${row}, isRelease=${isRelease}`, + ) + + return data + } +} diff --git a/integration-tests/cypress/e2e/using-shell-redirection-to-read-events/opening-files.cy.ts b/integration-tests/cypress/e2e/using-shell-redirection-to-read-events/opening-files.cy.ts index bc79377..99d790c 100644 --- a/integration-tests/cypress/e2e/using-shell-redirection-to-read-events/opening-files.cy.ts +++ b/integration-tests/cypress/e2e/using-shell-redirection-to-read-events/opening-files.cy.ts @@ -31,6 +31,7 @@ describe("opening files", () => { it("can open a file in a vertical split", () => { cy.startNeovim().then((dir) => { cy.typeIntoTerminal("{upArrow}") + cy.contains(dir.contents["test.lua"].name) cy.typeIntoTerminal("/test.lua{enter}") cy.typeIntoTerminal("{control+v}") @@ -46,6 +47,7 @@ describe("opening files", () => { it("can open a file in a horizontal split", () => { cy.startNeovim().then((dir) => { cy.typeIntoTerminal("{upArrow}") + cy.contains(dir.contents["test.lua"].name) cy.typeIntoTerminal("/test.lua{enter}") cy.typeIntoTerminal("{control+x}") @@ -61,6 +63,7 @@ describe("opening files", () => { it("can open a file in a new tab", () => { cy.startNeovim().then((dir) => { cy.typeIntoTerminal("{upArrow}") + cy.contains(dir.contents["test.lua"].name) cy.typeIntoTerminal("/test.lua{enter}") cy.typeIntoTerminal("{control+t}") @@ -101,6 +104,7 @@ describe("opening files", () => { it("can open files with complex characters in their name", () => { cy.startNeovim().then((dir) => { cy.typeIntoTerminal("{upArrow}") + cy.contains(dir.contents["test.lua"].name) // enter the routes/ directory cy.typeIntoTerminal("/routes{enter}") diff --git a/integration-tests/cypress/e2e/using-ya-to-read-events/mouse.cy.ts b/integration-tests/cypress/e2e/using-ya-to-read-events/mouse.cy.ts new file mode 100644 index 0000000..de3f49c --- /dev/null +++ b/integration-tests/cypress/e2e/using-ya-to-read-events/mouse.cy.ts @@ -0,0 +1,28 @@ +import { startNeovimWithYa } from "./startNeovimWithYa" + +describe("mouse support", () => { + beforeEach(() => { + cy.visit("http://localhost:5173") + }) + + it("can use grug-far.nvim to search and replace in the cwd", () => { + startNeovimWithYa().then((dir) => { + // wait until text on the start screen is visible + cy.contains("If you see this text, Neovim is ready!") + + // open yazi + cy.typeIntoTerminal("{upArrow}") + + // yazi should be showing adjacent files + cy.contains(dir.contents["test.lua"].name) + + // click outside of the yazi floating window. This should close it + // because it's designed to close when it loses focus + cy.contains("-- TERMINAL --").click() + + // clicking outside of the yazi window should close it, after which + // Neovim should not be showing the TERMINAL buffer any longer + cy.contains("-- TERMINAL --").should("not.exist") + }) + }) +}) diff --git a/integration-tests/cypress/e2e/using-ya-to-read-events/opening-files.cy.ts b/integration-tests/cypress/e2e/using-ya-to-read-events/opening-files.cy.ts index 6cacdb1..6d51f4a 100644 --- a/integration-tests/cypress/e2e/using-ya-to-read-events/opening-files.cy.ts +++ b/integration-tests/cypress/e2e/using-ya-to-read-events/opening-files.cy.ts @@ -33,6 +33,7 @@ describe("opening files", () => { it("can open a file in a vertical split", () => { startNeovimWithYa().then((dir) => { cy.typeIntoTerminal("{upArrow}") + cy.contains(dir.contents["test.lua"].name) cy.typeIntoTerminal("/test.lua{enter}") cy.typeIntoTerminal("{control+v}") @@ -48,6 +49,7 @@ describe("opening files", () => { it("can open a file in a horizontal split", () => { startNeovimWithYa().then((dir) => { cy.typeIntoTerminal("{upArrow}") + cy.contains(dir.contents["test.lua"].name) cy.typeIntoTerminal("/test.lua{enter}") cy.typeIntoTerminal("{control+x}") @@ -63,6 +65,7 @@ describe("opening files", () => { it("can open a file in a new tab", () => { startNeovimWithYa().then((dir) => { cy.typeIntoTerminal("{upArrow}") + cy.contains(dir.contents["test.lua"].name) cy.typeIntoTerminal("/test.lua{enter}") cy.typeIntoTerminal("{control+t}") @@ -102,7 +105,7 @@ describe("opening files", () => { describe("bulk renaming", () => { it("can bulk rename files", () => { - startNeovimWithYa().then((_dir) => { + startNeovimWithYa().then((dir) => { // in yazi, bulk renaming is done by // - selecting files and pressing "r". // - It opens the editor with the names of the selected files. @@ -110,6 +113,7 @@ describe("opening files", () => { // file. // - Finally, yazi should rename the files to match the new names. cy.typeIntoTerminal("{upArrow}") + cy.contains(dir.contents["test.lua"].name) cy.typeIntoTerminal("{control+a}r") // yazi should now have opened an embedded Neovim. The file name should say @@ -129,8 +133,9 @@ describe("opening files", () => { }) it("can rename a buffer that's open in Neovim", () => { - startNeovimWithYa().then((_dir) => { + startNeovimWithYa().then((dir) => { cy.typeIntoTerminal("{upArrow}") + cy.contains(dir.contents["test.lua"].name) // select only the current file to make the test easier cy.typeIntoTerminal("v") cy.typeIntoTerminal("r") // start renaming @@ -166,6 +171,7 @@ describe("opening files", () => { cy.typeIntoTerminal("{upArrow}") // enter the routes/ directory + cy.contains("routes") cy.typeIntoTerminal("/routes{enter}") cy.typeIntoTerminal("{rightArrow}") cy.contains(dir.contents["routes/posts.$postId/route.tsx"].name) // file in the directory diff --git a/integration-tests/cypress/support/commands.ts b/integration-tests/cypress/support/commands.ts index 88d7b45..4b3825c 100644 --- a/integration-tests/cypress/support/commands.ts +++ b/integration-tests/cypress/support/commands.ts @@ -59,7 +59,7 @@ Cypress.Commands.add( (text: string, options?: Partial) => { // the syntax for keys is described here: // https://docs.cypress.io/api/commands/type - cy.get("#app").type(text, options) + cy.get("textarea").focus().type(text, options) }, ) diff --git a/integration-tests/eslint.config.mjs b/integration-tests/eslint.config.mjs index 71af3d5..79f2a67 100644 --- a/integration-tests/eslint.config.mjs +++ b/integration-tests/eslint.config.mjs @@ -55,6 +55,13 @@ export default [ }, ], + "@typescript-eslint/restrict-template-expressions": [ + "error", + { + allowNumber: true, + allowBoolean: true, + }, + ], "@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/no-import-type-side-effects": "error", "@typescript-eslint/explicit-module-boundary-types": ["warn"], diff --git a/integration-tests/server/server.ts b/integration-tests/server/server.ts index 9468b58..179b43f 100644 --- a/integration-tests/server/server.ts +++ b/integration-tests/server/server.ts @@ -14,6 +14,7 @@ const testDirectory = path.join(__dirname, "..", "test-environment/") export type StdinMessage = "stdin" export type StdoutMessage = "stdout" export type StartNeovimMessage = "startNeovim" +export type MouseEventMessage = "mouseEvent" const expressApp = express() const server = createServer(expressApp) @@ -139,6 +140,13 @@ io.on("connection", function connection(socket) { assert(typeof data === "string", "stdin message must be a string") app.write(data) }) + + socket.on( + "mouseEvent" satisfies MouseEventMessage, + function (data: string) { + app.write(data) + }, + ) }, ) }) diff --git a/lua/yazi/window.lua b/lua/yazi/window.lua index 161a615..66e1aaa 100644 --- a/lua/yazi/window.lua +++ b/lua/yazi/window.lua @@ -86,6 +86,13 @@ function YaziFloatingWindow:open_and_display() vim.cmd('setlocal winhl=NormalFloat:YaziFloat') vim.cmd('set winblend=' .. self.config.yazi_floating_window_winblend) + vim.api.nvim_create_autocmd({ 'WinLeave', 'TermLeave' }, { + buffer = yazi_buffer, + callback = function() + self:close() + end, + }) + if self.config.enable_mouse_support == true then self:add_hacky_mouse_support(yazi_buffer) end