From b837d35ac8ddc1e21c690f14022682083d8398b5 Mon Sep 17 00:00:00 2001 From: Aldous Date: Fri, 4 Sep 2026 14:41:21 -0400 Subject: [PATCH] feat(terminal): send pastes with bracketed paste markers :CrushPaste previously wrote the raw register or selection to the terminal channel, so Crush received it as a stream of individual keystrokes and large pastes took a long time to play out. Wrap the text in ESC[200~ ... ESC[201~ so the TUI receives a single paste event. Bubble Tea enables bracketed paste mode by default. Also fold the duplicated buffer/channel validation in paste() and paste_selection() into a local get_term_chan() helper. Co-Authored-By: Claude Fable 5.1 --- lua/neocrush/terminal.lua | 44 ++++++++++++++++++++++++++------------- tests/terminal_spec.lua | 10 +++++++++ 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/lua/neocrush/terminal.lua b/lua/neocrush/terminal.lua index 0ee3898..ef93232 100644 --- a/lua/neocrush/terminal.lua +++ b/lua/neocrush/terminal.lua @@ -257,18 +257,38 @@ function M.restart() M.open() end ---- Paste text into the Crush terminal. ---- Can paste from a register or the current visual selection. ----@param register? string Register to paste from (default: '+' for system clipboard) -function M.paste(register) +--- Return the channel of the running Crush terminal, or nil after warning. +---@return integer? +local function get_term_chan() if not crush_buf or not vim.api.nvim_buf_is_valid(crush_buf) then vim.notify('Crush terminal not running', vim.log.levels.WARN) - return + return nil end local term_chan = vim.bo[crush_buf].channel if term_chan == 0 then vim.notify('Crush terminal channel not found', vim.log.levels.WARN) + return nil + end + + return term_chan +end + +--- Wrap text in bracketed paste markers (ESC[200~ ... ESC[201~). +--- Lets the terminal application receive the text as one paste event instead +--- of interpreting it as individual keystrokes. +---@param text string +---@return string +function M.bracketed(text) + return '\27[200~' .. text .. '\27[201~' +end + +--- Paste text into the Crush terminal. +--- Can paste from a register or the current visual selection. +---@param register? string Register to paste from (default: '+' for system clipboard) +function M.paste(register) + local term_chan = get_term_chan() + if not term_chan then return end @@ -280,19 +300,13 @@ function M.paste(register) return end - vim.api.nvim_chan_send(term_chan, content) + vim.api.nvim_chan_send(term_chan, M.bracketed(content)) end --- Paste the current visual selection into the Crush terminal. function M.paste_selection() - if not crush_buf or not vim.api.nvim_buf_is_valid(crush_buf) then - vim.notify('Crush terminal not running', vim.log.levels.WARN) - return - end - - local term_chan = vim.bo[crush_buf].channel - if term_chan == 0 then - vim.notify('Crush terminal channel not found', vim.log.levels.WARN) + local term_chan = get_term_chan() + if not term_chan then return end @@ -302,7 +316,7 @@ function M.paste_selection() return end - vim.api.nvim_chan_send(term_chan, text) + vim.api.nvim_chan_send(term_chan, M.bracketed(text)) end ------------------------------------------------------------------------------- diff --git a/tests/terminal_spec.lua b/tests/terminal_spec.lua index d9aacfc..e124224 100644 --- a/tests/terminal_spec.lua +++ b/tests/terminal_spec.lua @@ -94,6 +94,16 @@ describe('neocrush.terminal', function() end) end) + describe('bracketed', function() + it('should wrap text in bracketed paste markers', function() + assert.equals('\27[200~hello\nworld\27[201~', terminal.bracketed 'hello\nworld') + end) + + it('should wrap empty text', function() + assert.equals('\27[200~\27[201~', terminal.bracketed '') + end) + end) + describe('paste', function() it('should warn when no terminal is running', function() assert.has_no.errors(function()