wez / wezterm

A GPU-accelerated cross-platform terminal emulator and multiplexer written by @wez and implemented in Rust
https://wezfurlong.org/wezterm/
Other
16.71k stars 749 forks source link

Search/Copy Mode enhancements #5952

Open mikatpt opened 1 month ago

mikatpt commented 1 month ago

Is your feature request related to a problem? Please describe.

Hi! Thanks for such a lovely project. I'm coming from tmux land and 90% of things are peachy, but there's a few things with search that I'm having a lot of difficulty working around.

  1. If you run a command (like a test), then go into Copy Mode, the stdout output will cause your cursor position to move with the output—this happens regardless of scrollback buffer size.
  2. Transitioning from Search Mode to Copy Mode is a pain—it always throws you straight into selection (Visual mode for vim users), and there is no way to clear it. i.e. ClearSelectionMode and SetSelectionMode only work if you're already in Copy Mode, running those after ActivateCopyMode will fail.
  3. Search always begins from the bottom of the terminal. If I am 6000 lines up in CopyMode, it's pretty unexpected to go into search and be thrown all the way back.
  4. Switching tabs or panes will reset your Copy Mode cursor location as well IF you have previously Searched for something.

Describe the solution you'd like

I saw there was a request for a unified search/copy mode ( https://github.com/wez/wezterm/issues/1592 ), but am unsure that the solution came to totally did this—I think that removing search mode entirely, in favor of combing it as functionality in copy mode, is a more ergonomic solution here.

For us vim users, it's very natural to feel like you are simply in normal mode and you can navigate around, search using / and ?, then select/copy with v and y—tmux did this quite well and it's the one thing I miss from it a lot!

Aside from that I'm very pleased with everything—thanks again for your hard work.

Describe alternatives you've considered

Here are my copy mode bindings. It's imperfect, allowing me to sort of mimick vim "normal mode"—again, there's no solution I found to going Search Mode -> Normal Copy Mode, only Search -> Selection Copy Mode

local wezterm = require('wezterm')
local function close_copy_mode()
    return act.Multiple({
        act.CopyMode('ClearSelectionMode'),
        act.CopyMode('ClearPattern'),
        act.CopyMode('Close'),
    })
end
local function copy_to()
    return act.Multiple({
        act.CopyTo('Clipboard'),
        act.CopyMode('ClearSelectionMode'),
    })
end
local function next_match(int)
    local m = act.CopyMode('NextMatch')
    if int == -1 then
        m = act.CopyMode('PriorMatch')
    end
    return act.Multiple({ m, act.CopyMode('ClearSelectionMode') })
end

local function extend_keys(target, source)
    -- snip. ... overrides duplicate `key` values
    return target
end

local config = {}
config.key_tables = {
    copy_mode = extend_keys(default_keys.copy_mode, {
        { key = 'c', mods = 'CTRL', action = close_copy_mode() },
        { key = 'q', mods = 'NONE', action = close_copy_mode() },
        { key = 'y', mods = 'NONE', action = copy_to() },
        { key = 'Escape', mods = 'NONE', action = close_copy_mode() },
        { key = '/', mods = 'NONE', action = act.Multiple({
                act.CopyMode('ClearPattern'),
                act.Search({ CaseInSensitiveString = '' }),
            }),
        },
        { key = '?', mods = 'NONE', action = act.Multiple({
                act.CopyMode('ClearPattern'),
                act.EmitEvent('update-status'),
                act.Search({ CaseInSensitiveString = '' }),
            }),
        },
        { key = 'p', mods = 'CTRL', action = next_match(-1) },
        { key = 'n', mods = 'CTRL', action = next_match(1) },
        { key = 'n', mods = 'NONE', action = next_match(1) },
        { key = 'N', mods = 'NONE', action = next_match(-1) },
    }),
    search_mode = extend_keys(default_keys.search_mode, {
        { key = 'Escape', mods = 'NONE', action = act.CopyMode('Close') },
        { key = 'Enter', mods = 'NONE',   act.ActivateCopyMode },
    }),

}

return config
trustednstaller commented 3 weeks ago

I would add in addition to this the capacity to paste directly from CopyMode, without resorting to exiting it each time for a paste. This defeats the purpose when one is using CopyMode for multiple things outside of a one-shot copy/paste. Alacritty implements this very well. It would be nice to see WezTerm support it as well:

https://github.com/user-attachments/assets/777d1721-0397-42fc-8590-550f599c4bb9

In Alacritty, I enter Vim mode, hit y to yank something (and still remain in Vim mode), then repeatedly hit p to paste it while still staying in Vim mode, where I can continue being in, making other selections, etc.

mikatpt commented 2 weeks ago

Small update here. TLDR workaround available for everything except search mode always starting at bottom of terminal. Allowing search forwards/backwards from Copy Mode cursor location would resolve all remaining issues.

I've realized that Search mode cursor behavior is the primary issue here (1 and 3 in my initial post) - terminal output only causes a scroll to bottom if you have a pattern in your Search buffer. New output will continuously cause the search to retrigger and throw you back down - I'm not sure if this is intended but seems like a bug to me.

I do really wish this could be configurable - something like act.CopyMode({ SearchFromCursor = 'Forward|Backward' }) would put me at perfect feature parity with tmux.

As it is, my compromise is to clear the search pattern in all contexts that matter to me - moving between windows/tabs, initiating new searches will both start fresh, and thus no more cursor problems. (I also fixed issue 2 with a small timeout workaround).

Extend above config with the below example for this functionality:

local function complete_search(should_clear)
    return wezterm.action_callback(function(window, pane, _)
        if should_clear then
            window:perform_action(act.CopyMode('ClearPattern'), pane)
        end
        window:perform_action(act.CopyMode('AcceptPattern'), pane)

        -- For some reason this just does not work unless we retry a few times.
        -- Probably something to do with state management between Search/Copy mode.
        for _ = 1, 3, 1 do
            wezterm.sleep_ms(100)
            window:perform_action(act.CopyMode('ClearSelectionMode'), pane)
        end
    end)
end

local function clear_and_move(cmd)
    return act.Multiple({
        act.CopyMode('ClearPattern'),
        cmd,
    })
end

c.keys = {
        -- ...
        { key = 'l', mods = 'CTRL', action = clear_and_move(act.ActivatePaneDirection('Right') },
        -- repeat for hjkl, 
        { key = 'n', mods = 'CTRL', action = clear_and_move(act.ActivateTabRelative(1) },  -- etc
}
c.key_tables.search_mode = extend_keys(default_keys.search_mode, {
        { key = 'Escape', mods = 'NONE', action = complete_search(true) },
        { key = 'Enter', mods = 'NONE', action = complete_search(false) },
})