fix(macos): open terminal links with Command-click
This commit is contained in:
1 parent
0110c7c139
commit
cddeec78b3
16 files changed
+274
-35
No files matched your search
@@ -45,6 +45,8 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"link_command_click": "Command+Linksklick zum Öffnen",
|
||||
"link_ctrl_click": "Strg+Linksklick zum Öffnen",
|
||||
"settings": "Einstellungen",
|
||||
"language": "Sprache",
|
||||
"about": "Über",
|
||||
|
||||
@@ -209,6 +209,8 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"link_command_click": "Command+left click to open",
|
||||
"link_ctrl_click": "Ctrl+left click to open",
|
||||
"settings": "Settings",
|
||||
"language": "Language",
|
||||
"about": "About",
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"link_command_click": "Command+clic izquierdo para abrir",
|
||||
"link_ctrl_click": "Ctrl+clic izquierdo para abrir",
|
||||
"settings": "Configuración",
|
||||
"language": "Idioma",
|
||||
"about": "Acerca de",
|
||||
|
||||
@@ -95,6 +95,8 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"link_command_click": "Command+clic gauche pour ouvrir",
|
||||
"link_ctrl_click": "Ctrl+clic gauche pour ouvrir",
|
||||
"settings": "Paramètres",
|
||||
"language": "Langue",
|
||||
"about": "À propos",
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"link_command_click": "Command+clic sinistro per aprire",
|
||||
"link_ctrl_click": "Ctrl+clic sinistro per aprire",
|
||||
"settings": "Impostazioni",
|
||||
"language": "Lingua",
|
||||
"about": "Informazioni",
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"link_command_click": "Command+左クリックで開く",
|
||||
"link_ctrl_click": "Ctrl+左クリックで開く",
|
||||
"settings": "設定",
|
||||
"language": "言語",
|
||||
"about": "このアプリについて",
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"link_command_click": "Command+왼쪽 클릭으로 열기",
|
||||
"link_ctrl_click": "Ctrl+왼쪽 클릭으로 열기",
|
||||
"settings": "설정",
|
||||
"language": "언어",
|
||||
"about": "정보",
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"link_command_click": "Command+clique esquerdo para abrir",
|
||||
"link_ctrl_click": "Ctrl+clique esquerdo para abrir",
|
||||
"settings": "Configurações",
|
||||
"language": "Idioma",
|
||||
"about": "Sobre",
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"link_command_click": "Command+щелчок левой кнопкой для открытия",
|
||||
"link_ctrl_click": "Ctrl+щелчок левой кнопкой для открытия",
|
||||
"settings": "Настройки",
|
||||
"language": "Язык",
|
||||
"about": "О программе",
|
||||
|
||||
@@ -209,6 +209,8 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"link_command_click": "Command+左键跳转",
|
||||
"link_ctrl_click": "Ctrl+左键跳转",
|
||||
"settings": "设置",
|
||||
"language": "语言",
|
||||
"about": "关于",
|
||||
|
||||
@@ -69,6 +69,8 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"link_command_click": "Command+左鍵開啟",
|
||||
"link_ctrl_click": "Ctrl+左鍵開啟",
|
||||
"settings": "設定",
|
||||
"language": "語言",
|
||||
"about": "關於",
|
||||
|
||||
@@ -469,6 +469,18 @@ pub fn highlighted_at<T>(
|
||||
config: &UiConfig,
|
||||
point: Point,
|
||||
mouse_mods: ModifiersState,
|
||||
) -> Option<HintMatch> {
|
||||
highlighted_at_with_mouse_override(term, config, point, mouse_mods, false)
|
||||
}
|
||||
|
||||
/// Allow a shell's explicit link gesture to override application mouse reporting.
|
||||
/// Configured hint modifiers and the mouse-enabled flag are still respected.
|
||||
pub fn highlighted_at_with_mouse_override<T>(
|
||||
term: &Term<T>,
|
||||
config: &UiConfig,
|
||||
point: Point,
|
||||
mouse_mods: ModifiersState,
|
||||
override_mouse_mode: bool,
|
||||
) -> Option<HintMatch> {
|
||||
let mouse_mode = term.mode().intersects(TermMode::MOUSE_MODE);
|
||||
|
||||
@@ -477,7 +489,9 @@ pub fn highlighted_at<T>(
|
||||
let highlight = hint.mouse.is_some_and(|mouse| {
|
||||
mouse.enabled
|
||||
&& mouse_mods.contains(mouse.mods.0)
|
||||
&& (!mouse_mode || mouse_mods.contains(ModifiersState::SHIFT))
|
||||
&& (!mouse_mode
|
||||
|| override_mouse_mode
|
||||
|| mouse_mods.contains(ModifiersState::SHIFT))
|
||||
});
|
||||
if !highlight {
|
||||
return None;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! GPUI 壳的 OSC 8 / 正则 hint 接线:虚线下划线、悬停预览、Ctrl+点击打开。
|
||||
//! GPUI 壳的 OSC 8 / 正则 hint 接线:虚线下划线、悬停预览、平台修饰键+点击打开。
|
||||
//!
|
||||
//! 匹配与动作全部复用旧壳 `display::hint` + `file_uri` + `daemon`,这里只做
|
||||
//! 视口坐标、GPUI 修饰键和打开入口。
|
||||
@@ -16,6 +16,15 @@ use winit::keyboard::ModifiersState;
|
||||
use crate::config::UiConfig;
|
||||
use crate::config::ui_config::{HintAction, HintInternalAction, default_hint_command};
|
||||
use crate::display::hint::{self, HintMatch};
|
||||
use crate::i18n::{Message, UiLanguage};
|
||||
use crate::platform::Platform;
|
||||
|
||||
pub(super) fn link_modifier(mods: &gpui::Modifiers) -> bool {
|
||||
match Platform::current() {
|
||||
Platform::MacOS => mods.platform,
|
||||
Platform::Windows | Platform::Linux => mods.control,
|
||||
}
|
||||
}
|
||||
|
||||
/// 悬停目标:旧壳 `highlighted_hint` + 已经解码好的预览文案。
|
||||
#[derive(Clone)]
|
||||
@@ -78,7 +87,13 @@ pub(super) fn highlighted_at<T: EventListener>(
|
||||
point: Point,
|
||||
mods: &gpui::Modifiers,
|
||||
) -> Option<HintMatch> {
|
||||
hint::highlighted_at(term, config, point, winit_mouse_mods(mods))
|
||||
hint::highlighted_at_with_mouse_override(
|
||||
term,
|
||||
config,
|
||||
point,
|
||||
winit_mouse_mods(mods),
|
||||
link_modifier(mods),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn hover_from_hint<T: EventListener>(
|
||||
@@ -86,6 +101,7 @@ pub(super) fn hover_from_hint<T: EventListener>(
|
||||
hint: HintMatch,
|
||||
rows: usize,
|
||||
cols: usize,
|
||||
language: UiLanguage,
|
||||
) -> Option<LinkHover> {
|
||||
let raw = hint
|
||||
.hyperlink()
|
||||
@@ -98,12 +114,15 @@ pub(super) fn hover_from_hint<T: EventListener>(
|
||||
point_to_viewport_from(origin, start).filter(|vp| vp.line < rows && vp.column.0 < cols);
|
||||
let (anchor_row, anchor_col) =
|
||||
vp.map(|vp| (vp.line as u16, vp.column.0 as u16)).unwrap_or((0, 0));
|
||||
const HINT: &str = " · Ctrl+点击";
|
||||
let gesture = language.text(match Platform::current() {
|
||||
Platform::MacOS => Message::CommonLinkCommandClick,
|
||||
Platform::Windows | Platform::Linux => Message::CommonLinkCtrlClick,
|
||||
});
|
||||
let width = |s: &str| -> usize { s.chars().map(|c| c.width().unwrap_or(0)).sum() };
|
||||
let target = crate::display::strip_file_scheme(uri);
|
||||
let budget = cols.saturating_sub(width(HINT) + 1);
|
||||
let budget = cols.saturating_sub(width(gesture) + width(" · ") + 1);
|
||||
let target = crate::display::fit_tail(&target, budget);
|
||||
Some(LinkHover { hint, preview: format!("{target}{HINT}"), anchor_row, anchor_col })
|
||||
Some(LinkHover { hint, preview: format!("{target} · {gesture}"), anchor_row, anchor_col })
|
||||
}
|
||||
|
||||
pub(super) fn open_hint_match(
|
||||
|
||||
@@ -402,7 +402,7 @@ pub struct TerminalView {
|
||||
selection_scroll_epoch: u64,
|
||||
/// 是否已有一条自动回滚定时器链在跑——每次 move 都开一条会叠出 N 倍速。
|
||||
selection_scroll_active: bool,
|
||||
/// OSC 8 / 正则 URL:虚线下划线、悬停预览、Ctrl+点击打开。
|
||||
/// OSC 8 / 正则 URL:虚线下划线、悬停预览、平台修饰键+点击打开。
|
||||
pub(super) hint_config: Arc<UiConfig>,
|
||||
pub(super) link_hover: Option<super::osc_links::LinkHover>,
|
||||
pending_link_open: bool,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::super::osc_links::link_modifier;
|
||||
use super::*;
|
||||
use gpui_component::WindowExt as _;
|
||||
|
||||
@@ -143,7 +144,13 @@ impl TerminalView {
|
||||
let term = session.term.lock();
|
||||
super::super::osc_links::highlighted_at(&term, &self.hint_config, point, mods)
|
||||
.and_then(|hint| {
|
||||
super::super::osc_links::hover_from_hint(&term, hint, self.rows, self.cols)
|
||||
super::super::osc_links::hover_from_hint(
|
||||
&term,
|
||||
hint,
|
||||
self.rows,
|
||||
self.cols,
|
||||
crate::gpui_shell::config::ui_language(cx),
|
||||
)
|
||||
})
|
||||
})
|
||||
};
|
||||
@@ -355,6 +362,7 @@ impl TerminalView {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
window.focus(&self.focus_handle, cx);
|
||||
self.pending_link_open = false;
|
||||
cx.emit(TerminalViewEvent::FocusRequested);
|
||||
if self.session.is_none() {
|
||||
return;
|
||||
@@ -400,6 +408,17 @@ impl TerminalView {
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
if link_modifier(&event.modifiers) && event.click_count == 1 {
|
||||
self.update_link_hover(event.position, &event.modifiers, cx);
|
||||
if self.link_hover.is_some() {
|
||||
if let Some(session) = &self.session {
|
||||
session.term.lock().selection = None;
|
||||
}
|
||||
self.selecting = false;
|
||||
self.pending_link_open = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if self.mouse_mode_active(&event.modifiers) {
|
||||
self.send_mouse_report(
|
||||
event.position,
|
||||
@@ -409,28 +428,6 @@ impl TerminalView {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if event.modifiers.control && event.click_count == 1 {
|
||||
let (point, _) = self.grid_point(event.position);
|
||||
let hit = self.session.as_ref().is_some_and(|session| {
|
||||
let term = session.term.lock();
|
||||
super::super::osc_links::highlighted_at(
|
||||
&term,
|
||||
&self.hint_config,
|
||||
point,
|
||||
&event.modifiers,
|
||||
)
|
||||
.is_some()
|
||||
});
|
||||
if hit {
|
||||
if let Some(session) = &self.session {
|
||||
session.term.lock().selection = None;
|
||||
}
|
||||
self.selecting = false;
|
||||
self.pending_link_open = true;
|
||||
self.update_link_hover(event.position, &event.modifiers, cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let (point, side) = self.grid_point(event.position);
|
||||
let ty = match event.click_count {
|
||||
1 => SelectionType::Simple,
|
||||
@@ -465,6 +462,11 @@ impl TerminalView {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
// Retain the pressed link until release; dragging must not retarget it
|
||||
// or leak part of the consumed gesture to the application.
|
||||
if self.pending_link_open {
|
||||
return;
|
||||
}
|
||||
if self.move_completion_popup_scrollbar(event, cx) {
|
||||
cx.stop_propagation();
|
||||
return;
|
||||
@@ -524,6 +526,12 @@ impl TerminalView {
|
||||
window.focus(&self.focus_handle, cx);
|
||||
cx.emit(TerminalViewEvent::FocusRequested);
|
||||
}
|
||||
if event.pressed_button.is_none() && link_modifier(&event.modifiers) {
|
||||
self.update_link_hover(event.position, &event.modifiers, cx);
|
||||
if self.link_hover.is_some() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if self.mouse_mode_active(&event.modifiers) {
|
||||
self.clear_link_hover(cx);
|
||||
// 鼠标模式的移动上报:拖动 = 按钮码+32(需 DRAG 或 MOTION 任一),
|
||||
@@ -574,7 +582,8 @@ impl TerminalView {
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
if !self.selecting && self.mouse_mode_active(&event.modifiers) {
|
||||
let pending_link_open = std::mem::take(&mut self.pending_link_open);
|
||||
if !pending_link_open && !self.selecting && self.mouse_mode_active(&event.modifiers) {
|
||||
self.send_mouse_report(
|
||||
event.position,
|
||||
mouse_protocol::BUTTON_LEFT,
|
||||
@@ -584,15 +593,15 @@ impl TerminalView {
|
||||
return;
|
||||
}
|
||||
self.selecting = false;
|
||||
let open_link = (self.pending_link_open || event.modifiers.control)
|
||||
let (point, _) = self.grid_point(event.position);
|
||||
let open_link = pending_link_open
|
||||
&& self.selection_is_empty()
|
||||
&& self.link_hover.is_some();
|
||||
&& self.link_hover.as_ref().is_some_and(|hover| hover.hint.bounds().contains(&point));
|
||||
if open_link {
|
||||
self.try_open_hovered_link(window, cx);
|
||||
} else if self.copy_on_select {
|
||||
self.copy_selection(false, window, cx);
|
||||
}
|
||||
self.pending_link_open = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -612,6 +621,7 @@ impl TerminalView {
|
||||
}
|
||||
self.stop_selection_scroll();
|
||||
let dragging_scrollbar = self.scrollbar_drag.take().is_some();
|
||||
self.pending_link_open = false;
|
||||
if !self.selecting {
|
||||
if dragging_scrollbar {
|
||||
cx.notify();
|
||||
@@ -619,7 +629,6 @@ impl TerminalView {
|
||||
return;
|
||||
}
|
||||
self.selecting = false;
|
||||
self.pending_link_open = false;
|
||||
// 指针不在终端上,链接打开不该发生;只补选中即复制这一条收尾。
|
||||
if self.copy_on_select {
|
||||
self.copy_selection(false, window, cx);
|
||||
|
||||
@@ -151,3 +151,178 @@ fn hover_does_not_steal_focus_during_drag_overlay_dialog_or_inactive_window(
|
||||
move_over(&mut cx, None);
|
||||
assert_eq!(probe.read_with(&cx, |probe, _| probe.requests.get()), 0);
|
||||
}
|
||||
|
||||
fn link_modifiers() -> Modifiers {
|
||||
match crate::platform::Platform::current() {
|
||||
crate::platform::Platform::MacOS => Modifiers { platform: true, ..Modifiers::default() },
|
||||
_ => Modifiers { control: true, ..Modifiers::default() },
|
||||
}
|
||||
}
|
||||
|
||||
fn link_fixture(
|
||||
cx: &mut TestAppContext,
|
||||
text: &[u8],
|
||||
) -> (Entity<TerminalView>, VisualTestContext, std::sync::mpsc::Receiver<Msg>) {
|
||||
cx.update(crate::gpui_shell::math_view::register);
|
||||
let (probe, mut cx) = open(cx);
|
||||
let terminal = probe.read_with(&cx, |probe, _| probe.terminal.clone());
|
||||
let receiver = terminal.update(&mut cx, |view, cx| {
|
||||
let (session, receiver) = session::test_session();
|
||||
view.session = Some(session);
|
||||
view.error = None;
|
||||
view.exited = None;
|
||||
view.copy_on_select = false;
|
||||
// Use the production matcher and dispatcher with a deterministic action;
|
||||
// tests must not launch a user's browser or editor.
|
||||
let mut config = UiConfig::default();
|
||||
Arc::make_mut(&mut config.hints.enabled[0]).action =
|
||||
crate::config::ui_config::HintAction::Action(
|
||||
crate::config::ui_config::HintInternalAction::Copy,
|
||||
);
|
||||
view.hint_config = Arc::new(config);
|
||||
super::super::startup_tests::feed(view, text);
|
||||
cx.notify();
|
||||
receiver
|
||||
});
|
||||
draw(&mut cx);
|
||||
cx.update(|_, cx| cx.write_to_clipboard(gpui::ClipboardItem::new_string("before".into())));
|
||||
(terminal, cx, receiver)
|
||||
}
|
||||
|
||||
fn cell(view: &Entity<TerminalView>, cx: &VisualTestContext, col: usize) -> Point<Pixels> {
|
||||
view.read_with(cx, |view, _| {
|
||||
point(
|
||||
view.origin.x + view.cell_width * (col as f32 + 0.5),
|
||||
view.origin.y + view.line_height * 0.5,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn clipboard(cx: &mut VisualTestContext) -> Option<String> {
|
||||
cx.update(|_, cx| cx.read_from_clipboard().and_then(|item| item.text()))
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn link_gesture_opens_regex_files_and_osc8_with_or_without_mouse_reporting(
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
for (output, target) in [
|
||||
("https://example.com", "https://example.com"),
|
||||
("file:///tmp/pebrel-notes.md", "file:///tmp/pebrel-notes.md"),
|
||||
("[notes](./notes.md)", "[notes](./notes.md)"),
|
||||
("\x1b]8;;https://example.com/osc8\x1b\\site\x1b]8;;\x1b\\", "https://example.com/osc8"),
|
||||
] {
|
||||
for mouse_mode in [false, true] {
|
||||
let output = if mouse_mode {
|
||||
format!("\x1b[?1003h\x1b[?1006h{output}")
|
||||
} else {
|
||||
output.to_owned()
|
||||
};
|
||||
let (view, mut window, receiver) = link_fixture(cx, output.as_bytes());
|
||||
let position = cell(&view, &window, 1);
|
||||
window.simulate_mouse_move(position, None, link_modifiers());
|
||||
assert!(view.read_with(&window, |view, _| view.link_hover.is_some()));
|
||||
window.simulate_mouse_down(position, MouseButton::Left, link_modifiers());
|
||||
assert_eq!(clipboard(&mut window).as_deref(), Some("before"));
|
||||
assert!(view.read_with(&window, |view, _| view.pending_link_open));
|
||||
// Releasing Command/Ctrl first must not send an orphan mouse-up to the TUI.
|
||||
window.simulate_mouse_up(position, MouseButton::Left, Modifiers::default());
|
||||
assert_eq!(clipboard(&mut window).as_deref(), Some(target));
|
||||
assert!(!receiver.try_iter().any(|event| matches!(event, Msg::Input(_))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn plain_click_and_wrong_modifier_do_not_open_links(cx: &mut TestAppContext) {
|
||||
let (view, mut window, _) = link_fixture(cx, b"https://example.com");
|
||||
let position = cell(&view, &window, 1);
|
||||
let wrong = if link_modifiers().platform {
|
||||
Modifiers { control: true, ..Modifiers::default() }
|
||||
} else {
|
||||
Modifiers { platform: true, ..Modifiers::default() }
|
||||
};
|
||||
for modifiers in [Modifiers::default(), wrong] {
|
||||
window.simulate_mouse_move(position, None, modifiers);
|
||||
window.simulate_mouse_down(position, MouseButton::Left, modifiers);
|
||||
window.simulate_mouse_up(position, MouseButton::Left, modifiers);
|
||||
assert_eq!(clipboard(&mut window).as_deref(), Some("before"));
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn link_drag_cannot_retarget_or_leave_a_pending_open(cx: &mut TestAppContext) {
|
||||
let (view, mut window, _) = link_fixture(cx, b"https://one.test https://two.test");
|
||||
let start = cell(&view, &window, 1);
|
||||
for end in [cell(&view, &window, 20), point(px(10.0), px(10.0))] {
|
||||
window.simulate_mouse_down(start, MouseButton::Left, link_modifiers());
|
||||
window.simulate_mouse_move(end, Some(MouseButton::Left), link_modifiers());
|
||||
window.simulate_mouse_up(end, MouseButton::Left, link_modifiers());
|
||||
assert_eq!(clipboard(&mut window).as_deref(), Some("before"));
|
||||
assert!(!view.read_with(&window, |view, _| view.pending_link_open));
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn mouse_reporting_still_receives_ordinary_and_non_link_clicks(cx: &mut TestAppContext) {
|
||||
let (view, mut window, receiver) =
|
||||
link_fixture(cx, b"\x1b[?1000h\x1b[?1006hhttps://example.com plain");
|
||||
for (col, modifiers) in [(1, Modifiers::default()), (22, link_modifiers())] {
|
||||
let position = cell(&view, &window, col);
|
||||
window.simulate_mouse_down(position, MouseButton::Left, modifiers);
|
||||
window.simulate_mouse_up(position, MouseButton::Left, modifiers);
|
||||
let reports: Vec<_> = receiver
|
||||
.try_iter()
|
||||
.filter_map(|event| match event {
|
||||
Msg::Input(bytes) => Some(bytes.into_owned()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(reports.len(), 2);
|
||||
assert!(reports[0].ends_with(b"M"));
|
||||
assert!(reports[1].ends_with(b"m"));
|
||||
assert_eq!(clipboard(&mut window).as_deref(), Some("before"));
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn link_hover_uses_current_language_and_platform(cx: &mut TestAppContext) {
|
||||
use crate::i18n::UiLanguage;
|
||||
let (view, mut window, _) = link_fixture(cx, b"https://example.com");
|
||||
let position = cell(&view, &window, 1);
|
||||
let modifier = if link_modifiers().platform { "Command" } else { "Ctrl" };
|
||||
for (language, suffix) in [
|
||||
(UiLanguage::ZhCn, "+左键跳转"),
|
||||
(UiLanguage::EnUs, "+left click to open"),
|
||||
(UiLanguage::KoKr, "+왼쪽 클릭으로 열기"),
|
||||
] {
|
||||
window.update(|_, cx| cx.global_mut::<Settings>().ui_language = language);
|
||||
window.simulate_mouse_move(position, None, Modifiers::default());
|
||||
let preview =
|
||||
view.read_with(&window, |view, _| view.link_hover.as_ref().unwrap().preview.clone());
|
||||
assert!(preview.ends_with(&format!(" · {modifier}{suffix}")), "{preview}");
|
||||
draw(&mut window);
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn explicit_link_gesture_respects_disabled_hints_and_required_modifiers(cx: &mut TestAppContext) {
|
||||
let (view, mut window, _) = link_fixture(cx, b"\x1b[?1000hhttps://example.com");
|
||||
let position = cell(&view, &window, 1);
|
||||
for enabled in [false, true] {
|
||||
view.update(&mut window, |view, _| {
|
||||
let config = Arc::make_mut(&mut view.hint_config);
|
||||
let hint = Arc::make_mut(&mut config.hints.enabled[0]);
|
||||
let mouse = hint.mouse.as_mut().unwrap();
|
||||
mouse.enabled = enabled;
|
||||
mouse.mods.0 = winit::keyboard::ModifiersState::SHIFT;
|
||||
});
|
||||
window.simulate_mouse_down(position, MouseButton::Left, link_modifiers());
|
||||
window.simulate_mouse_up(position, MouseButton::Left, link_modifiers());
|
||||
assert_eq!(clipboard(&mut window).as_deref(), Some("before"));
|
||||
}
|
||||
let modifiers = Modifiers { shift: true, ..link_modifiers() };
|
||||
window.simulate_mouse_down(position, MouseButton::Left, modifiers);
|
||||
window.simulate_mouse_up(position, MouseButton::Left, modifiers);
|
||||
assert_eq!(clipboard(&mut window).as_deref(), Some("https://example.com"));
|
||||
}
|
||||
Reference in new issue
Block a user