mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
Merge pull request #11 from l0ng-ai/feat/menu-hints-reverse-search
Right-click menu shortcut hints + Ctrl+R ASCII keystroke fix
This commit is contained in:
@@ -65,7 +65,10 @@ impl ReverseSearch {
|
||||
}
|
||||
}
|
||||
|
||||
/// Append typed text (arriving via the IME path) to the query and re-search.
|
||||
/// Append typed text to the query and re-search. Text arrives either via the
|
||||
/// IME path (`replace_text_in_range` → the view's `input_text`) or, for a plain
|
||||
/// ASCII input source, as a direct `key_char` the view forwards from
|
||||
/// `handle_reverse_search_key`.
|
||||
pub(super) fn push_query(&mut self, text: &str, history: &[String]) {
|
||||
self.query.push_str(text);
|
||||
self.update(history, false);
|
||||
|
||||
+103
-13
@@ -11,8 +11,9 @@ use gpui::{
|
||||
KeyDownEvent, Modifiers, MouseButton, MouseDownEvent, Pixels, ScrollDelta, ScrollWheelEvent,
|
||||
Window, actions, div, prelude::*, px,
|
||||
};
|
||||
use gpui_component::kbd::Kbd;
|
||||
use gpui_component::menu::ContextMenuExt;
|
||||
use gpui_component::{ActiveTheme as _, Icon, IconName, Size};
|
||||
use gpui_component::{ActiveTheme as _, Icon, IconName, Size, h_flex};
|
||||
|
||||
use super::TermSize;
|
||||
use super::cmd_editor::CmdEditor;
|
||||
@@ -910,12 +911,7 @@ impl TerminalView {
|
||||
"a" => {
|
||||
// At the prompt, ⌘A selects the whole edited line; otherwise it
|
||||
// selects the whole terminal buffer (scrollback included).
|
||||
if self.input_active() {
|
||||
self.cmd.select_all();
|
||||
cx.notify();
|
||||
} else {
|
||||
self.select_all(cx);
|
||||
}
|
||||
self.select_all_contextual(cx);
|
||||
CmdKey::Consumed
|
||||
}
|
||||
// The following are editor-only (macOS line editing); they're swallowed
|
||||
@@ -1302,7 +1298,7 @@ impl TerminalView {
|
||||
}
|
||||
|
||||
/// Select the entire buffer — from the top of scrollback to the last cell —
|
||||
/// so Cmd+A then Cmd+C copies everything. Also used by the right-click menu.
|
||||
/// so Cmd+A then Cmd+C copies everything.
|
||||
pub fn select_all(&mut self, cx: &mut Context<Self>) {
|
||||
let mut term = self.terminal.term.lock();
|
||||
let grid = term.grid();
|
||||
@@ -1315,6 +1311,19 @@ impl TerminalView {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// "Select All" as the user means it in context: at the prompt, select the
|
||||
/// edited command line; otherwise select the whole terminal buffer. Shared by
|
||||
/// the ⌘A shortcut and the right-click "Select All" item so the two never
|
||||
/// drift apart.
|
||||
pub fn select_all_contextual(&mut self, cx: &mut Context<Self>) {
|
||||
if self.input_active() {
|
||||
self.cmd.select_all();
|
||||
cx.notify();
|
||||
} else {
|
||||
self.select_all(cx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Paste clipboard text. While idle at the prompt it goes into the local
|
||||
/// command editor (a single trailing newline is dropped so a copied line
|
||||
/// doesn't auto-submit). Otherwise it's written to the PTY, wrapped in
|
||||
@@ -1980,6 +1989,26 @@ impl TerminalView {
|
||||
/// query/match logic (`reverse_search` module); the view just applies the
|
||||
/// resulting [`reverse_search::Action`] and repaints.
|
||||
fn handle_reverse_search_key(&mut self, ks: &gpui::Keystroke, cx: &mut Context<Self>) {
|
||||
// Printable text typed into the query. A CJK input source routes it through
|
||||
// the IME (`input_text` → `push_query`), but a plain ASCII input source —
|
||||
// and Linux, where `prefers_ime_for_printable_keys` is false — delivers it
|
||||
// here as an ordinary key event carrying `key_char`. Without this the search
|
||||
// field can only be typed into via an IME: Ctrl+R opens, but ASCII
|
||||
// keystrokes vanish. Mirror the editor's `key_char` path (`handle_editor_key`);
|
||||
// control / Cmd / Alt chords and non-printable keys (Enter/Backspace/Esc have
|
||||
// no printable `key_char`) fall through to the control-key handling below.
|
||||
let m = &ks.modifiers;
|
||||
if !m.control && !m.platform && !m.alt {
|
||||
if let Some(ch) = ks.key_char.as_deref() {
|
||||
if !ch.is_empty() && ch.chars().all(|c| c >= '\u{20}' && c != '\u{7f}') {
|
||||
if let Some(rs) = self.reverse_search.as_mut() {
|
||||
rs.push_query(ch, &self.history);
|
||||
}
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(rs) = self.reverse_search.as_mut() else {
|
||||
return;
|
||||
};
|
||||
@@ -2874,7 +2903,7 @@ impl Render for TerminalView {
|
||||
// same menu fall through to `Tty7App`.
|
||||
.on_action(cx.listener(|this, _: &CopyText, _w, cx| this.copy_selection(cx)))
|
||||
.on_action(cx.listener(|this, _: &PasteText, _w, cx| this.paste_from_clipboard(cx)))
|
||||
.on_action(cx.listener(|this, _: &SelectAll, _w, cx| this.select_all(cx)))
|
||||
.on_action(cx.listener(|this, _: &SelectAll, _w, cx| this.select_all_contextual(cx)))
|
||||
.on_action(
|
||||
cx.listener(|this, _: &FindInTerminal, window, cx| this.open_search(window, cx)),
|
||||
)
|
||||
@@ -2912,14 +2941,33 @@ impl Render for TerminalView {
|
||||
// Small size = tighter 20px rows; the default 26px felt too airy.
|
||||
// A fixed min-width keeps the menu a consistent, intentional size
|
||||
// instead of hugging the longest label (which reads ragged).
|
||||
// Copy/Paste/Select All/Find are dispatched inline (see
|
||||
// `handle_cmd_shortcut`) with no registered `KeyBinding`, so the menu
|
||||
// can't auto-derive their hints the way it does for the items below.
|
||||
// We render the hint ourselves via `menu_row_with_hint` to keep the
|
||||
// whole menu consistent, rather than register real bindings (which
|
||||
// would risk the Ctrl+C SIGINT fall-through on Windows/Linux).
|
||||
menu.with_size(Size::Small)
|
||||
.min_w(px(220.))
|
||||
.action_context(menu_focus.clone())
|
||||
.menu_with_disabled("Copy", Box::new(CopyText), !has_selection)
|
||||
.menu("Paste", Box::new(PasteText))
|
||||
.menu("Select All", Box::new(SelectAll))
|
||||
.menu_element_with_disabled(
|
||||
Box::new(CopyText),
|
||||
!has_selection,
|
||||
menu_row_with_hint("Copy", Some("secondary-c")),
|
||||
)
|
||||
.menu_element(
|
||||
Box::new(PasteText),
|
||||
menu_row_with_hint("Paste", Some("secondary-v")),
|
||||
)
|
||||
.menu_element(
|
||||
Box::new(SelectAll),
|
||||
menu_row_with_hint("Select All", mac_only("secondary-a")),
|
||||
)
|
||||
.separator()
|
||||
.menu("Find…", Box::new(FindInTerminal))
|
||||
.menu_element(
|
||||
Box::new(FindInTerminal),
|
||||
menu_row_with_hint("Find…", mac_only("secondary-f")),
|
||||
)
|
||||
.menu("Clear", Box::new(ClearScrollback))
|
||||
.separator()
|
||||
.menu("Split Right", Box::new(SplitRight))
|
||||
@@ -2932,6 +2980,48 @@ impl Render for TerminalView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a context-menu row that shows its shortcut right-aligned, matching the
|
||||
/// hint gpui-component auto-renders for items whose action has a registered
|
||||
/// keybinding. `key` is `None` when the action has no shortcut on this platform,
|
||||
/// leaving the row hint-less like a plain item.
|
||||
fn menu_row_with_hint(
|
||||
label: &'static str,
|
||||
key: Option<&'static str>,
|
||||
) -> impl Fn(&mut Window, &mut App) -> gpui::AnyElement {
|
||||
move |_window, _cx| {
|
||||
let hint = key.map(|k| {
|
||||
// Strip Kbd's keycap box (filled bg + border) so it reads as the same
|
||||
// quiet muted-foreground hint the auto-rendered items show — see
|
||||
// gpui-component's `PopupMenu::render_key_binding`.
|
||||
Kbd::new(gpui::Keystroke::parse(k).expect("valid static keystroke"))
|
||||
.p_0()
|
||||
.flex_nowrap()
|
||||
.border_0()
|
||||
.bg(gpui::transparent_white())
|
||||
});
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_3()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.child(label)
|
||||
.children(hint)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
/// `Some(key)` on macOS, `None` elsewhere. ⌘A (Select All) and ⌘F (Find) are
|
||||
/// wired only on macOS; on Windows/Linux those chords keep their readline meaning
|
||||
/// (line-start / forward-char), so the menu must not advertise them there.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn mac_only(key: &'static str) -> Option<&'static str> {
|
||||
Some(key)
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn mac_only(_key: &'static str) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Approximate terminal display width of a char in cells: 2 for East-Asian
|
||||
/// wide / fullwidth glyphs and most emoji, 1 otherwise. Mirrors how the grid
|
||||
/// (alacritty) lays out wide characters, so the editor's per-char cells and
|
||||
|
||||
Reference in New Issue
Block a user