Skip to content

Conversation

@mkasberg
Copy link

@mkasberg mkasberg commented Oct 21, 2025

This is at least a partial solution to #2107. (It's not totally clear to me whether we'd want to leave #2107 open even after this change to support additional right-click actions for OSC8 URLs...)

AI Disclosure: I used Gemini CLI to help me with this PR because while I have many years of programming experience, this is my first time writing Zig. I prototyped a couple different approaches with AI before landing on this one, so AI generated various prototypes and I chose the final imlementation. I've verified that my code compiles and works as intended.

When a user right-clicks, and there's no existing selection, the existing behavior is to try to select the word under the cursor:

ghostty/src/Surface.zig

Lines 3740 to 3742 in 3548acf

const sel = screen.selectWord(pin) orelse break :sel;
try self.setSelection(sel);
try self.queueRender();

This PR proposes tweaking that behavior slightly: If there's a link under our cursor, as determined by linkAtPos, select the link. Otherwise, select the word as before.

I think this behavior provides a better user experience because it seems exceptionally rare that a user is right-clicking on a link but would prefer to take action on the word rather than the (underlined) link. The new behavior also matches other terminals like iTerm and Gnome Terminal, as shown in #2107.

It's worth noting that linkAtPos already does the right thing in terms of checking the links from config and their highlight/hover states (modified by Ctrl or Super depending on platform). (Docs)

ghostty/src/Surface.zig

Lines 3896 to 3901 in 3548acf

// Go through each link and see if we clicked it
for (self.config.links) |link| {
switch (link.highlight) {
.always, .hover => {},
.always_mods, .hover_mods => |v| if (!v.equal(mouse_mods)) continue,
}

It also therefore respects link-url from config.

ghostty/src/config/Config.zig

Lines 3411 to 3416 in 3548acf

// Add our default link for URL detection
try result.link.links.append(alloc, .{
.regex = url.regex,
.action = .{ .open = {} },
.highlight = .{ .hover_mods = inputpkg.ctrlOrSuper(.{}) },
});

By using linkAtPos, we get all that behavior for free. In practical terms, that means:

  • If I'm holding Ctrl so a link is underlined and I right click on it, it selects the underlined link.
  • If I'm not holding Ctrl and I right click on a link that is not underlined, it selects the word as before.
  • This behavior respects per-platform key bindings and user config settings.

linkAtPos requires that the render state mutex is held. I believe it's safe to call because we're inside a block holding the mutex:

ghostty/src/Surface.zig

Lines 3702 to 3704 in 3548acf

if (button == .right and action == .press) sel: {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();

Original Behavior:
(first without ctrl, then with ctrl)

existing_behavior.mp4

New Behavior:
(first without ctrl, then with ctrl, then pasting)

new_behavior.mp4

@mkasberg mkasberg marked this pull request as ready for review October 21, 2025 23:44
@mkasberg mkasberg requested a review from a team as a code owner October 21, 2025 23:44
This is at least a partial solution to ghostty-org#2107. (It's not totally clear to
me whether we'd want to leave ghostty-org#2107 open even after this change to
support additional right-click actions for OSC8 URLs...)

When a user right-clicks, and there's no existing selection, the
existing behavior is to try to select the word under the cursor:

https://github.com/ghostty-org/ghostty/blob/3548acfac63e7674b5e25896f6b393474fe8ea65/src/Surface.zig#L3740-L3742

This PR proposes tweaking that behavior _slightly_: If there's a link
under our cursor, as determined by `linkAtPos`, select the link.
Otherwise, select the word as before.

I think this behavior provides a better user experience because it seems
exceptionally rare that a user is right-clicking _on a link_ but would
prefer to take action on the word rather than the (underlined) link. The
new behavior also matches other terminals like iTerm and Gnome Terminal,
as shown in ghostty-org#2107.

It's worth noting that `linkAtPos` already does the right thing in terms
of checking the links from config and their highlight/hover states
(modified by Ctrl or Super depending on platform).

https://github.com/ghostty-org/ghostty/blob/3548acfac63e7674b5e25896f6b393474fe8ea65/src/Surface.zig#L3896-L3901

It also therefore respects `link-url` from config.

https://github.com/ghostty-org/ghostty/blob/3548acfac63e7674b5e25896f6b393474fe8ea65/src/config/Config.zig#L3411-L3416

By using `linkAtPos`, we get all that behavior for free. In practical
terms, that means:
 - If I'm holding Ctrl so a link is underlined and I right click on it,
   it selects the underlined link.
 - If I'm not holding Ctrl and I right click on a link that is not
   underlined, it selects the word as before.
 - This behavior respects per-platform key bindings and user config
   settings.

`linkAtPos` requires that the render state mutex is held. I believe it's
safe to call because we're inside a block holding the mutex:
https://github.com/ghostty-org/ghostty/blob/3548acfac63e7674b5e25896f6b393474fe8ea65/src/Surface.zig#L3702-L3704

**AI Disclosure:** I used Gemini CLI to help me with this PR because
while I have many years of programming experience, this is my first time
writing Zig. I prototyped a couple different approaches with AI before
landing on this one, so AI generated various prototypes and I chose the
final imlementation. I've verified that my code compiles and works as
intended.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant