fix(links): open links on Ctrl+click on Windows and Linux

The link modifier was `mods.platform`, which gpui maps to ⌘ on macOS but
to the Win/Super key elsewhere — a key the OS mostly swallows, so on
Windows and Linux neither the hover underline nor click-to-open could be
triggered at all. `config.json`'s own docs already promised "⌘/Ctrl-click";
this is the implementation catching up.

Use `Modifiers::secondary()` (⌘ on macOS, Ctrl elsewhere) at all three
sites: the click handler, the hover probe, and the app-level modifier
tracking that pushes the state down to background panes. Not
`platform || control` — that would steal ⌃-click on macOS, where it means
"right click".

Ctrl+click still falls through to mouse-tracking TUIs when there's no
link under the cursor, since `open_link_at` reports whether it consumed
the click.

Fixes #183
This commit is contained in:
l0ng-ai
2026-07-26 20:36:05 +08:00
parent 4dd61be56f
commit 7aeca8adf7
4 changed files with 27 additions and 12 deletions
+2 -2
View File
@@ -17,7 +17,7 @@
- **Tabs & splits** — always open in the current directory
- **Repo-grouped sidebar** — the left tab sidebar groups rows under a header per git repository, non-repo tabs in a trailing *Scratch* section; branch switches and in-repo `cd`s never move a row (`sidebar_grouping` in `config.json`: `repo` default, `none` for a flat list)
- **Command palette** <kbd>⌘ P</kbd> · scrollback search <kbd>⌘ F</kbd>
- **⌘-click links** · desktop notifications · copy on select (opt-in, Settings → Terminal → Clipboard)
- **⌘/Ctrl-click links** (⌘ on macOS, Ctrl on Windows/Linux) · desktop notifications · copy on select (opt-in, Settings → Terminal → Clipboard)
- **Smart double-click selection** — double-click grabs the whole URL, file path, bracket/quote pair, or dictionary-segmented CJK word under the cursor; Shift-click extends a selection (toggle in Settings → Terminal → Mouse; word separators via `word_separators` in `config.json`)
- **Eight themes, plus your own** — YAML seed themes with solid, gradient, or image backgrounds; iTerm2 `.itermcolors` import; in-app color editor with a background-image picker
- **Sync with system** — Settings → Appearance; pick separate light and dark themes and tty7 follows the OS appearance live (`theme_follow_system`, `theme_preset_light` / `theme_preset_dark` in `config.json`)
@@ -48,7 +48,7 @@ and SFTP without shelling out to `ssh`. There is no system-ssh compat mode.
- **`~/.ssh/config` aliases** — type one to connect (resolved natively — common fields, best-effort — over russh), or import them as profiles in Settings
- **GUI auth** — in-pane sheets for password, key passphrase, 2FA, and host-key confirmation (new vs. changed)
- **Built-in SFTP** — a slide-in file panel: browse, upload / download, rename / delete / chmod, drag to Finder
- **Port forwarding** — Local / Remote / Dynamic, preconfigured or added live, plus ⌘-click `localhost:PORT` to auto-forward
- **Port forwarding** — Local / Remote / Dynamic, preconfigured or added live, plus ⌘/Ctrl-click `localhost:PORT` to auto-forward
- **Jump hosts & proxies** — multi-hop via profile references or `ProxyJump`, ProxyCommand, SOCKS5 / HTTP
| Entry point | Connects via |
+6 -3
View File
@@ -1189,8 +1189,11 @@ impl TerminalElement {
let button = ev.button;
let clicks = ev.click_count;
view.update(cx, |v, cx| {
// Cmd+click opens a URL under the cursor.
let link_modifier = mods.platform || v.link_modifier_down();
// Secondary+click (⌘ on macOS, Ctrl on Windows/Linux) opens a
// URL under the cursor. Not the raw platform key: that's Win/
// Super off macOS, which the OS mostly swallows — and every
// other terminal there opens links on Ctrl+click.
let link_modifier = mods.secondary() || v.link_modifier_down();
if link_modifier && button == MouseButton::Left && v.open_link_at(col, row, cx) {
return;
}
@@ -1244,7 +1247,7 @@ impl TerminalElement {
if !mods.shift {
v.mouse_motion(col, row, &mods);
}
let include_files = mods.platform || v.link_modifier_down();
let include_files = mods.secondary() || v.link_modifier_down();
v.hover_link_at(col, row, include_files, cx);
} else {
v.clear_hovered_link(cx);
+3 -2
View File
@@ -44,7 +44,7 @@ impl Tty7App {
cx: &mut Context<Self>,
) {
let m = &ev.modifiers;
self.set_link_modifier(m.platform, cx);
self.set_link_modifier(m.secondary(), cx);
// Mirror `on_key_down`'s chord test: reject the other platform-ish key
// (⌃ on macOS, Win/Super elsewhere), Alt, and Shift, so only the bare
@@ -79,7 +79,8 @@ impl Tty7App {
.detach();
}
/// Push the platform-modifier state down to every pane's link tracking.
/// Push the secondary-modifier state (⌘ on macOS, Ctrl elsewhere — the
/// same key that opens a link on click) down to every pane's link tracking.
/// Every tab, not just the active one: `on_modifiers_changed` only fires on
/// the frontmost window state, so a background tab that saw "⌘ down" but
/// never the matching release would keep a stale `true` — and a stale
+16 -5
View File
@@ -467,7 +467,7 @@ pub(crate) struct SettingsState {
pub(crate) shell_args_input: Entity<InputState>,
/// Custom working-directory path (used when the strategy is `Custom`).
pub(crate) wd_path_input: Entity<InputState>,
/// Command template run when ⌘-clicking a file link (Links section). Empty
/// Command template run when ⌘/Ctrl-clicking a file link (Links section). Empty
/// clears the override, restoring the built-in "open in default app".
pub(crate) link_file_command_input: Entity<InputState>,
/// Mouse-scroll multiplier slider (Terminal section).
@@ -622,6 +622,14 @@ pub(crate) struct Recording {
/// unlikely real font name.
pub(crate) const FONT_DEFAULT_LABEL: &str = "Default (match primary)";
/// How the link-click modifier is spelled in the Links copy. It's gpui's
/// `secondary` (see `Modifiers::secondary`), so it must read ⌘ on macOS and
/// Ctrl on Windows/Linux — same split `key_tokens` uses for keycaps.
#[cfg(target_os = "macos")]
const LINK_MODIFIER_LABEL: &str = "";
#[cfg(not(target_os = "macos"))]
const LINK_MODIFIER_LABEL: &str = "Ctrl";
/// Humanize a CamelCase action name for display: "CloseActiveTab" → "Close
/// Active Tab".
pub(crate) fn humanize_action(action: &str) -> String {
@@ -3093,7 +3101,7 @@ impl Tty7App {
.child(self.section_header("Links", cx))
.child(self.settings_row(
"Detect URLs",
"Underline links on hover and open them on ⌘-click.",
format!("Underline links on hover and open them on {LINK_MODIFIER_LABEL}-click."),
link_switch,
cx,
))
@@ -3105,9 +3113,12 @@ impl Tty7App {
))
.child(self.settings_row(
"Open files with",
"Command run when ⌘-clicking a file link, instead of the default app. \
Use {path}, {line}, {column}; a flag whose value is absent is dropped \
(e.g. herdr edit {path} --line={line}). Empty uses the default app.",
format!(
"Command run when {LINK_MODIFIER_LABEL}-clicking a file link, instead of \
the default app. Use {{path}}, {{line}}, {{column}}; a flag whose value \
is absent is dropped (e.g. herdr edit {{path}} --line={{line}}). Empty \
uses the default app."
),
link_file_command_control,
cx,
))