aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/Surface.zig
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-16 23:28:05 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-16 23:29:28 +0300
commit4a3302d19a2baa3a33e910e0088c24e780905b90 (patch)
treedcaf76fae1c82102d933c9225b17ca7ac5d3a847 /vendor/libghostty-vt/src/Surface.zig
parent550e9f8cc085f428429f5cf2da0bb2dba897e8b8 (diff)
chore: update vendored libghostty-vt
refs #276
Diffstat (limited to 'vendor/libghostty-vt/src/Surface.zig')
-rw-r--r--vendor/libghostty-vt/src/Surface.zig77
1 files changed, 52 insertions, 25 deletions
diff --git a/vendor/libghostty-vt/src/Surface.zig b/vendor/libghostty-vt/src/Surface.zig
index 410f717b..2c4edc49 100644
--- a/vendor/libghostty-vt/src/Surface.zig
+++ b/vendor/libghostty-vt/src/Surface.zig
@@ -1217,7 +1217,7 @@ fn selectionScrollTick(self: *Surface) !void {
// We modified our viewport and selection so we need to queue
// a render.
- try self.io.terminal.screens.active.select(selection);
+ try self.setSelection(selection);
try self.queueRender();
}
@@ -2326,23 +2326,46 @@ fn copySelectionToClipboards(
};
}
-/// Set the selection contents.
+/// Set the active selection and notify the apprt on a genuine state
+/// transition. All selection mutations route through here rather than
+/// `screen.select` directly so the notification fires consistently. To
+/// also copy per `copy_on_select`, use `setSelectionAndCopy`.
///
/// This must be called with the renderer mutex held.
fn setSelection(self: *Surface, sel_: ?terminal.Selection) !void {
+ // Compute the transition before `select` below, which untracks (frees)
+ // the previous selection's tracked pins; reading them after would be a
+ // use-after-free.
const prev_ = self.io.terminal.screens.active.selection;
+ const changed = changed: {
+ const prev = prev_ orelse break :changed sel_ != null;
+ const sel = sel_ orelse break :changed true;
+ break :changed !sel.eql(prev);
+ };
+
try self.io.terminal.screens.active.select(sel_);
+ if (changed) {
+ _ = self.rt_app.performAction(
+ .{ .surface = self },
+ .selection_changed,
+ {},
+ ) catch |err| {
+ log.warn("apprt failed selection_changed notification err={}", .{err});
+ };
+ }
+}
+
+/// Set a selection and, per `copy_on_select`, copy it to the clipboard.
+/// For committing selection gestures (mouse release, select-all binding).
+///
+/// This must be called with the renderer mutex held.
+fn setSelectionAndCopy(self: *Surface, sel: terminal.Selection) !void {
+ try self.setSelection(sel);
+
// If copy on select is false then exit early.
if (self.config.copy_on_select == .false) return;
- // Set our selection clipboard. If the selection is cleared we do not
- // clear the clipboard. If the selection is set, we only set the clipboard
- // again if it changed, since setting the clipboard can be an expensive
- // operation.
- const sel = sel_ orelse return;
- if (prev_) |prev| if (sel.eql(prev)) return;
-
switch (self.config.copy_on_select) {
.false => unreachable, // handled above with an early exit
@@ -3840,7 +3863,7 @@ pub fn mouseButtonCallback(
if (self.config.copy_on_select != .false) {
const prev_ = self.io.terminal.screens.active.selection;
if (prev_) |prev| {
- try self.setSelection(terminal.Selection.init(
+ try self.setSelectionAndCopy(terminal.Selection.init(
prev.start(),
prev.end(),
prev.rectangle,
@@ -3985,16 +4008,16 @@ pub fn mouseButtonCallback(
else => unreachable,
}
- // We set the selection directly rather than use `setSelection` because
- // we want to avoid copying the selection to the selection clipboard.
- // For left mouse clicks we only set the clipboard on release.
+ // Use `setSelection` (not `setSelectionAndCopy`) here to avoid
+ // touching the selection clipboard: for left mouse clicks we only
+ // copy on release.
if (press_selection) |selection| {
- try self.io.terminal.screens.active.select(selection);
+ try self.setSelection(selection);
try self.queueRender();
} else if (self.mouse.selection_gesture.left_click_count == 1 and
self.io.terminal.screens.active.selection != null)
{
- try self.io.terminal.screens.active.select(null);
+ try self.setSelection(null);
try self.queueRender();
}
}
@@ -4060,13 +4083,13 @@ pub fn mouseButtonCallback(
// If there is a link at this position, we want to
// select the link. Otherwise, select the word.
if (try self.linkAtPos(pos)) |link| {
- try self.setSelection(link.selection);
+ try self.setSelectionAndCopy(link.selection);
} else {
const sel = screen.selectWord(
pin,
self.config.selection_word_chars,
) orelse break :sel;
- try self.setSelection(sel);
+ try self.setSelectionAndCopy(sel);
}
try self.queueRender();
@@ -4195,20 +4218,24 @@ fn maybePromptClick(self: *Surface) !bool {
// Guarded at the start of this function
.none => unreachable,
- .click_events => {
+ .click_events => |v| {
// For the event, we always send a left-click press event.
// This matches what Kitty sends.
+ const key: u8, const y: u32 = switch (v) {
+ .absolute => .{ 1, pos_vp.y +| 1 },
+ .relative => .{ 2, pos_vp.y -| prompt_pin.y +| 1 },
+ };
var data: termio.Message.WriteReq.Small.Array = undefined;
const resp = try std.fmt.bufPrint(
&data,
"\x1B[<0;{d};{d}M",
- .{ pos_vp.x + 1, pos_vp.y + 1 },
+ .{ pos_vp.x + 1, y },
);
// Not that noisy since this only happens on prompt clicks.
log.debug(
- "sending click_events=1 event=ESC{s}",
- .{resp[1..]},
+ "sending click_events={} event=ESC{s}",
+ .{ key, resp[1..] },
);
// Ask our IO thread to write the data
@@ -4411,7 +4438,7 @@ fn openUrl(
/// if there is no hyperlink.
fn osc8URI(self: *Surface, pin: terminal.Pin) ?[]const u8 {
_ = self;
- const page = &pin.node.data;
+ const page = pin.node.page();
const cell = pin.rowAndCell().cell;
const link_id = page.lookupHyperlink(cell) orelse return null;
const entry = page.hyperlink_set.get(page.memory, link_id);
@@ -4464,7 +4491,7 @@ pub fn mousePressureCallback(
);
}
- try self.io.terminal.screens.active.select(sel orelse break :select);
+ try self.setSelection(sel orelse break :select);
try self.queueRender();
}
}
@@ -4669,7 +4696,7 @@ pub fn cursorPosCallback(
}
// Update our selection based on the gesture state
- try self.io.terminal.screens.active.select(drag_selection);
+ try self.setSelection(drag_selection);
}
}
@@ -5420,7 +5447,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
const sel = self.io.terminal.screens.active.selectAll();
if (sel) |s| {
- try self.setSelection(s);
+ try self.setSelectionAndCopy(s);
try self.queueRender();
}
},