Skip to content

Commit 5fcd5f2

Browse files
committed
fix terminal ctrl-c with selection
1 parent 667ddd6 commit 5fcd5f2

2 files changed

Lines changed: 95 additions & 14 deletions

File tree

src/gui/terminal_host.rs

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,7 @@ impl GuiTerminalHost {
901901

902902
/// Copies selected text while skipping alacritty wide-cell spacers.
903903
fn override_selected_copy_text(&mut self, ui: &Ui, response: &egui::Response) -> bool {
904-
if !response.has_focus() || !copy_requested(ui) {
904+
if !response.has_focus() || !terminal_selection_copy_requested(ui) {
905905
return false;
906906
}
907907
let term = self.backend.term.lock();
@@ -1880,22 +1880,51 @@ fn enable_terminal_ime(ui: &Ui, backend: &TerminalBackend, rect: egui::Rect) {
18801880
});
18811881
}
18821882

1883-
/// Detects copy requests so terminal selection text can be normalized.
1884-
fn copy_requested(ui: &Ui) -> bool {
1885-
ui.input(|input| {
1886-
input.events.iter().any(|event| match event {
1887-
egui::Event::Copy => true,
1888-
egui::Event::Key {
1889-
key,
1890-
pressed: true,
1891-
modifiers,
1892-
..
1893-
} => terminal_copy_key_event(*key, *modifiers),
1894-
_ => false,
1895-
})
1883+
/// Detects copy requests that should copy terminal selection text.
1884+
fn terminal_selection_copy_requested(ui: &Ui) -> bool {
1885+
ui.input(|input| terminal_selection_copy_requested_from_events(&input.events, input.modifiers))
1886+
}
1887+
1888+
/// Detects terminal-selection copy without stealing Ctrl+C interrupts.
1889+
fn terminal_selection_copy_requested_from_events(
1890+
events: &[Event],
1891+
active_modifiers: Modifiers,
1892+
) -> bool {
1893+
events.iter().any(|event| match event {
1894+
egui::Event::Copy => terminal_selection_copy_modifiers(active_modifiers),
1895+
egui::Event::Key {
1896+
key,
1897+
pressed: true,
1898+
modifiers,
1899+
..
1900+
} => terminal_selection_copy_key_event(*key, *modifiers),
1901+
_ => false,
18961902
})
18971903
}
18981904

1905+
/// Detects platform copy events that should copy terminal selection.
1906+
fn terminal_selection_copy_modifiers(modifiers: Modifiers) -> bool {
1907+
// 触发条件:Linux/Windows 上 Ctrl+C 可同时带 command alias。
1908+
// 不能把它按平台 Copy 处理:终端里 Ctrl+C 的主语义是 ETX。
1909+
// 防止回归:有选区时 Ctrl+C 只复制、不再中断子进程。
1910+
if modifiers.alt {
1911+
return false;
1912+
}
1913+
if modifiers.ctrl {
1914+
return modifiers.shift && !modifiers.mac_cmd;
1915+
}
1916+
modifiers.mac_cmd || modifiers.command
1917+
}
1918+
1919+
/// Detects key copy chords that should copy terminal selection.
1920+
fn terminal_selection_copy_key_event(key: Key, modifiers: Modifiers) -> bool {
1921+
terminal_copy_key_event(key, modifiers)
1922+
|| (key == Key::C
1923+
&& !modifiers.ctrl
1924+
&& !modifiers.alt
1925+
&& (modifiers.mac_cmd || modifiers.command))
1926+
}
1927+
18991928
/// Removes padding cells from the end of a copied terminal line.
19001929
fn trim_trailing_spaces(text: &mut String) {
19011930
let trimmed_len = text.trim_end_matches(' ').len();

src/gui/terminal_host_test.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,58 @@ fn command_copy_event_is_not_forwarded_as_interrupt_with_selection() {
200200
assert!(agent_input_bytes_from_events(&events, modifiers, false).is_empty());
201201
}
202202

203+
/// 验证带 command alias 的 Ctrl+C 不会被当作 terminal 选区复制。
204+
#[test]
205+
fn control_c_copy_event_with_selection_keeps_interrupt_semantics() {
206+
let events = vec![Event::Copy];
207+
let modifiers = Modifiers {
208+
ctrl: true,
209+
command: true,
210+
..Modifiers::default()
211+
};
212+
213+
assert!(!terminal_selection_copy_requested_from_events(
214+
&events, modifiers
215+
));
216+
assert_eq!(
217+
agent_input_bytes_from_events(&events, modifiers, true),
218+
b"\x03"
219+
);
220+
}
221+
222+
/// 验证 Ctrl+Shift+C 仍然作为明确的 terminal 选区复制手势。
223+
#[test]
224+
fn control_shift_c_copy_event_with_selection_keeps_copy_semantics() {
225+
let events = vec![Event::Copy];
226+
let modifiers = Modifiers {
227+
ctrl: true,
228+
shift: true,
229+
command: true,
230+
..Modifiers::default()
231+
};
232+
233+
assert!(terminal_selection_copy_requested_from_events(
234+
&events, modifiers
235+
));
236+
assert!(agent_input_bytes_from_events(&events, modifiers, false).is_empty());
237+
}
238+
239+
/// 验证 macOS Cmd+C 仍然可以复制 terminal 选区。
240+
#[test]
241+
fn mac_command_c_copy_event_with_selection_keeps_copy_semantics() {
242+
let events = vec![Event::Copy];
243+
let modifiers = Modifiers {
244+
mac_cmd: true,
245+
command: true,
246+
..Modifiers::default()
247+
};
248+
249+
assert!(terminal_selection_copy_requested_from_events(
250+
&events, modifiers
251+
));
252+
assert!(agent_input_bytes_from_events(&events, modifiers, false).is_empty());
253+
}
254+
203255
/// Verifies that Ctrl+C survives egui's command alias on Linux and Windows.
204256
#[test]
205257
fn control_c_copy_event_with_command_alias_is_forwarded_as_interrupt() {

0 commit comments

Comments
 (0)