Add a default mapping for searching the scrollback

Opens the scrollback pager in search mode. Particularly useful for
newbies on macOS that are used to using cmd+f to trigger search mode.

If there is a current selection, it is automatically searched for.
This commit is contained in:
Kovid Goyal 2025-12-01 08:10:07 +05:30
parent 9b6b9733b9
commit 20b39ee163
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
10 changed files with 54 additions and 6 deletions

View file

@ -25,6 +25,7 @@ Previous shell prompt :sc:`scroll_to_previous_prompt` (see :ref:`shell_int
Next shell prompt :sc:`scroll_to_next_prompt` (see :ref:`shell_integration`)
Browse scrollback in less :sc:`show_scrollback`
Browse last cmd output :sc:`show_last_command_output` (see :ref:`shell_integration`)
Search scrollback in less :sc:`search_scrollback` (also :kbd:`⌘+F` on macOS)
========================= =======================
The scroll actions only take effect when the terminal is in the main screen.

View file

@ -177,6 +177,10 @@ Detailed list of changes
- Do not rewrap the text in the alternate screen buffer. Avoids flicker during
live resize with no :opt:`resize_debounce_time` (:disc:`9142`)
- Add a default mapping :ac:`search_scrollback` to open the scrollback in a
pager in search mode. If any text is currently selected it is automatically
searched for.
0.44.0 [2025-11-03]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -3402,4 +3402,6 @@ class Boss:
def ungrab_keyboard(self) -> None:
grab_keyboard(False)
def search_scrollback_in_active(self) -> None:
if w := self.active_window:
w.search_scrollback()

View file

@ -1242,6 +1242,7 @@ process_cocoa_pending_actions(void) {
if (cocoa_pending_actions[TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY]) { call_boss(toggle_macos_secure_keyboard_entry, NULL); }
if (cocoa_pending_actions[MACOS_CYCLE_THROUGH_OS_WINDOWS]) { call_boss(macos_cycle_through_os_windows, NULL); }
if (cocoa_pending_actions[MACOS_CYCLE_THROUGH_OS_WINDOWS_BACKWARDS]) { call_boss(macos_cycle_through_os_windows_backwards, NULL); }
if (cocoa_pending_actions[SEARCH_SCROLLBACK]) { call_boss(search_scrollback_in_active, NULL); }
if (cocoa_pending_actions[TOGGLE_FULLSCREEN]) { call_boss(toggle_fullscreen, NULL); }
if (cocoa_pending_actions[OPEN_KITTY_WEBSITE]) { call_boss(open_kitty_website, NULL); }
if (cocoa_pending_actions[HIDE]) { call_boss(hide_macos_app, NULL); }

View file

@ -32,6 +32,7 @@ typedef enum {
TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY,
MACOS_CYCLE_THROUGH_OS_WINDOWS,
MACOS_CYCLE_THROUGH_OS_WINDOWS_BACKWARDS,
SEARCH_SCROLLBACK,
TOGGLE_FULLSCREEN,
OPEN_KITTY_WEBSITE,
HIDE,

View file

@ -260,6 +260,7 @@ PENDING(reload_config, RELOAD_CONFIG)
PENDING(toggle_macos_secure_keyboard_entry, TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY)
PENDING(macos_cycle_through_os_windows, MACOS_CYCLE_THROUGH_OS_WINDOWS)
PENDING(macos_cycle_through_os_windows_backwards, MACOS_CYCLE_THROUGH_OS_WINDOWS_BACKWARDS)
PENDING(search_scrollback, SEARCH_SCROLLBACK)
PENDING(toggle_fullscreen, TOGGLE_FULLSCREEN)
PENDING(open_kitty_website, OPEN_KITTY_WEBSITE)
PENDING(hide_macos_app, HIDE)
@ -320,7 +321,7 @@ typedef struct {
GlobalShortcut previous_tab, next_tab, new_tab, new_window, close_window, reset_terminal;
GlobalShortcut clear_terminal_and_scrollback, clear_screen, clear_scrollback, clear_last_command;
GlobalShortcut toggle_macos_secure_keyboard_entry, toggle_fullscreen, open_kitty_website;
GlobalShortcut hide_macos_app, hide_macos_other_apps, minimize_macos_window, quit;
GlobalShortcut hide_macos_app, hide_macos_other_apps, minimize_macos_window, quit, search_scrollback;
GlobalShortcut macos_cycle_through_os_windows, macos_cycle_through_os_windows_backwards;
} GlobalShortcuts;
static GlobalShortcuts global_shortcuts;
@ -339,7 +340,7 @@ cocoa_set_global_shortcut(PyObject *self UNUSED, PyObject *args) {
else Q(clear_terminal_and_scrollback); else Q(clear_scrollback); else Q(clear_screen); else Q(clear_last_command);
else Q(reload_config); else Q(toggle_macos_secure_keyboard_entry); else Q(toggle_fullscreen);
else Q(open_kitty_website); else Q(hide_macos_app); else Q(hide_macos_other_apps);
else Q(minimize_macos_window); else Q(quit);
else Q(minimize_macos_window); else Q(quit); else Q(search_scrollback);
else Q(macos_cycle_through_os_windows); else Q(macos_cycle_through_os_windows_backwards);
#undef Q
if (gs == NULL) { PyErr_SetString(PyExc_KeyError, "Unknown shortcut name"); return NULL; }
@ -795,6 +796,7 @@ cocoa_create_global_menu(void) {
MENU_ITEM(editMenu, @"Clear Scrollback", clear_scrollback);
MENU_ITEM(editMenu, @"Clear Screen", clear_screen);
MENU_ITEM(editMenu, @"Clear Last Command", clear_last_command);
MENU_ITEM(editMenu, @"Find", search_scrollback);
[editMenu release];
NSMenuItem* windowMenuItem =

View file

@ -194,7 +194,7 @@ def set_cocoa_global_shortcuts(opts: Options) -> dict[str, SingleKey]:
for ac in ('new_os_window', 'close_os_window', 'close_tab', 'edit_config_file', 'previous_tab',
'next_tab', 'new_tab', 'new_window', 'close_window', 'toggle_macos_secure_keyboard_entry',
'toggle_fullscreen', 'macos_cycle_through_os_windows', 'macos_cycle_through_os_windows_backwards',
'hide_macos_app', 'hide_macos_other_apps', 'minimize_macos_window', 'quit'):
'hide_macos_app', 'hide_macos_other_apps', 'minimize_macos_window', 'quit', 'search_scrollback'):
val = get_macos_shortcut_for(func_map, ac)
if val is not None:
global_shortcuts[ac] = val

View file

@ -3984,6 +3984,22 @@ To get the output of the last jumped to command, use :code:`@last_visited_cmd_ou
Requires :ref:`shell integration <shell_integration>` to work.
'''
)
map('Search the scrollback within a pager',
'search_scrollback kitty_mod+/ search_scrollback',
long_text='''
Search for currently selected text in the scrollback using the configured :opt:`scrollback_pager`.
Assumes that pressing the :kbd:`/` key triggers search mode in the pager. If you want to create
a manual mapping with a special pager for this, you can use something like:
map f1 combine : launch --stdin-source=@screen_scrollback --stdin-add-formatting --type=overlay mypager : send_key /
For more sophisticated control, such as using the current selection, use :ac:`remote_control_script`.
''')
map('Search the scrollback within a pager', 'search_scrollback cmd+f search_scrollback', only='macos')
egr() # }}}

View file

@ -849,6 +849,8 @@ defaults.map = [
KeyDefinition(trigger=SingleKey(mods=256, key=104), definition='show_scrollback'),
# show_last_command_output
KeyDefinition(trigger=SingleKey(mods=256, key=103), definition='show_last_command_output'),
# search_scrollback
KeyDefinition(trigger=SingleKey(mods=256, key=47), definition='search_scrollback'),
# new_window
KeyDefinition(trigger=SingleKey(mods=256, key=57345), definition='new_window'),
# new_os_window
@ -982,6 +984,7 @@ if is_macos:
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57355), definition='scroll_page_down'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57356), definition='scroll_home'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57357), definition='scroll_end'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=102), definition='search_scrollback'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57345), definition='new_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=110), definition='new_os_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=100), definition='close_window'))

View file

@ -2101,11 +2101,29 @@ class Window:
# actions {{{
@ac('cp', 'Show scrollback in a pager like less')
def show_scrollback(self) -> None:
def show_scrollback(self) -> Optional['Window']:
text = self.as_text(as_ansi=True, add_history=True, add_wrap_markers=True)
data = self.pipe_data(text, has_wrap_markers=True)
cursor_on_screen = self.screen.scrolled_by < self.screen.lines - self.screen.cursor.y
get_boss().display_scrollback(self, data['text'], data['input_line_number'], report_cursor=cursor_on_screen)
return get_boss().display_scrollback(self, data['text'], data['input_line_number'], report_cursor=cursor_on_screen)
@ac('cp', '''
Search scrollback in a pager like less. If there is selected text, it is automatically searched for.
Note that this assumes that pressing the / key triggers search mode in the page configured as the
scrollback pager.
''')
def search_scrollback(self) -> None:
text = self.text_for_selection()
w = self.show_scrollback()
if w is not None:
w.send_key('/')
if text:
btext = text.encode()
sanitized = replace_c0_codes_except_nl_space_tab(btext)
if not w.screen.in_bracketed_paste_mode:
sanitized = sanitized.replace(b'\n', b'\x1bE')
w.screen.paste_bytes(sanitized)
w.send_key('enter')
def show_cmd_output(self, which: CommandOutput, title: str = 'Command output', as_ansi: bool = True, add_wrap_markers: bool = True) -> None:
text = self.cmd_output(which, as_ansi=as_ansi, add_wrap_markers=add_wrap_markers)