mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Add link-previews option (#7831)
Implement the `link-previews` option from https://github.com/ghostty-org/ghostty/discussions/7727. The feature request isn't accepted yet, but I had a bit of free time to write this up and it was pretty quick, so I figure there's no harm in proposing an implementation. Demo of `link-previews = osc8`: https://github.com/user-attachments/assets/17a72ab2-c727-4a85-9edd-9b6e7da05d98
This commit is contained in:
@ -270,6 +270,7 @@ const DerivedConfig = struct {
|
|||||||
title: ?[:0]const u8,
|
title: ?[:0]const u8,
|
||||||
title_report: bool,
|
title_report: bool,
|
||||||
links: []Link,
|
links: []Link,
|
||||||
|
link_previews: configpkg.LinkPreviews,
|
||||||
|
|
||||||
const Link = struct {
|
const Link = struct {
|
||||||
regex: oni.Regex,
|
regex: oni.Regex,
|
||||||
@ -336,6 +337,7 @@ const DerivedConfig = struct {
|
|||||||
.title = config.title,
|
.title = config.title,
|
||||||
.title_report = config.@"title-report",
|
.title_report = config.@"title-report",
|
||||||
.links = links,
|
.links = links,
|
||||||
|
.link_previews = config.@"link-previews",
|
||||||
|
|
||||||
// Assignments happen sequentially so we have to do this last
|
// Assignments happen sequentially so we have to do this last
|
||||||
// so that the memory is captured from allocs above.
|
// so that the memory is captured from allocs above.
|
||||||
@ -1242,7 +1244,7 @@ fn mouseRefreshLinks(
|
|||||||
// Get our link at the current position. This returns null if there
|
// Get our link at the current position. This returns null if there
|
||||||
// isn't a link OR if we shouldn't be showing links for some reason
|
// isn't a link OR if we shouldn't be showing links for some reason
|
||||||
// (see further comments for cases).
|
// (see further comments for cases).
|
||||||
const link_: ?apprt.action.MouseOverLink = link: {
|
const link_: ?apprt.action.MouseOverLink, const preview: bool = link: {
|
||||||
// If we clicked and our mouse moved cells then we never
|
// If we clicked and our mouse moved cells then we never
|
||||||
// highlight links until the mouse is unclicked. This follows
|
// highlight links until the mouse is unclicked. This follows
|
||||||
// standard macOS and Linux behavior where a click and drag cancels
|
// standard macOS and Linux behavior where a click and drag cancels
|
||||||
@ -1257,18 +1259,21 @@ fn mouseRefreshLinks(
|
|||||||
|
|
||||||
if (!click_pt.coord().eql(pos_vp)) {
|
if (!click_pt.coord().eql(pos_vp)) {
|
||||||
log.debug("mouse moved while left click held, ignoring link hover", .{});
|
log.debug("mouse moved while left click held, ignoring link hover", .{});
|
||||||
break :link null;
|
break :link .{ null, false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = (try self.linkAtPos(pos)) orelse break :link null;
|
const link = (try self.linkAtPos(pos)) orelse break :link .{ null, false };
|
||||||
switch (link[0]) {
|
switch (link[0]) {
|
||||||
.open => {
|
.open => {
|
||||||
const str = try self.io.terminal.screen.selectionString(alloc, .{
|
const str = try self.io.terminal.screen.selectionString(alloc, .{
|
||||||
.sel = link[1],
|
.sel = link[1],
|
||||||
.trim = false,
|
.trim = false,
|
||||||
});
|
});
|
||||||
break :link .{ .url = str };
|
break :link .{
|
||||||
|
.{ .url = str },
|
||||||
|
self.config.link_previews == .true,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
._open_osc8 => {
|
._open_osc8 => {
|
||||||
@ -1276,9 +1281,14 @@ fn mouseRefreshLinks(
|
|||||||
const pin = link[1].start();
|
const pin = link[1].start();
|
||||||
const uri = self.osc8URI(pin) orelse {
|
const uri = self.osc8URI(pin) orelse {
|
||||||
log.warn("failed to get URI for OSC8 hyperlink", .{});
|
log.warn("failed to get URI for OSC8 hyperlink", .{});
|
||||||
break :link null;
|
break :link .{ null, false };
|
||||||
|
};
|
||||||
|
break :link .{
|
||||||
|
.{
|
||||||
|
.url = uri,
|
||||||
|
},
|
||||||
|
self.config.link_previews != .false,
|
||||||
};
|
};
|
||||||
break :link .{ .url = uri };
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1294,11 +1304,15 @@ fn mouseRefreshLinks(
|
|||||||
.mouse_shape,
|
.mouse_shape,
|
||||||
.pointer,
|
.pointer,
|
||||||
);
|
);
|
||||||
_ = try self.rt_app.performAction(
|
|
||||||
.{ .surface = self },
|
if (preview) {
|
||||||
.mouse_over_link,
|
_ = try self.rt_app.performAction(
|
||||||
link,
|
.{ .surface = self },
|
||||||
);
|
.mouse_over_link,
|
||||||
|
link,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ pub const ShellIntegrationFeatures = Config.ShellIntegrationFeatures;
|
|||||||
pub const WindowPaddingColor = Config.WindowPaddingColor;
|
pub const WindowPaddingColor = Config.WindowPaddingColor;
|
||||||
pub const BackgroundImagePosition = Config.BackgroundImagePosition;
|
pub const BackgroundImagePosition = Config.BackgroundImagePosition;
|
||||||
pub const BackgroundImageFit = Config.BackgroundImageFit;
|
pub const BackgroundImageFit = Config.BackgroundImageFit;
|
||||||
|
pub const LinkPreviews = Config.LinkPreviews;
|
||||||
|
|
||||||
// Alternate APIs
|
// Alternate APIs
|
||||||
pub const CAPI = @import("config/CAPI.zig");
|
pub const CAPI = @import("config/CAPI.zig");
|
||||||
|
@ -1046,6 +1046,14 @@ link: RepeatableLink = .{},
|
|||||||
/// `link`). If you want to customize URL matching, use `link` and disable this.
|
/// `link`). If you want to customize URL matching, use `link` and disable this.
|
||||||
@"link-url": bool = true,
|
@"link-url": bool = true,
|
||||||
|
|
||||||
|
/// Show link previews for a matched URL.
|
||||||
|
///
|
||||||
|
/// When true, link previews are shown for all matched URLs. When false, link
|
||||||
|
/// previews are never shown. When set to "osc8", link previews are only shown
|
||||||
|
/// for hyperlinks created with the OSC 8 sequence (in this case, the link text
|
||||||
|
/// can differ from the link destination).
|
||||||
|
@"link-previews": LinkPreviews = .true,
|
||||||
|
|
||||||
/// Whether to start the window in a maximized state. This setting applies
|
/// Whether to start the window in a maximized state. This setting applies
|
||||||
/// to new windows and does not apply to tabs, splits, etc. However, this setting
|
/// to new windows and does not apply to tabs, splits, etc. However, this setting
|
||||||
/// will apply to all new windows, not just the first one.
|
/// will apply to all new windows, not just the first one.
|
||||||
@ -4326,6 +4334,12 @@ pub const WindowSubtitle = enum {
|
|||||||
@"working-directory",
|
@"working-directory",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const LinkPreviews = enum {
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
osc8,
|
||||||
|
};
|
||||||
|
|
||||||
/// Color represents a color using RGB.
|
/// Color represents a color using RGB.
|
||||||
///
|
///
|
||||||
/// This is a packed struct so that the C API to read color values just
|
/// This is a packed struct so that the C API to read color values just
|
||||||
|
Reference in New Issue
Block a user