Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2010,6 +2010,46 @@ pub fn hasSelection(self: *const Surface) bool {
return self.io.terminal.screens.active.selection != null;
}

/// Start a selection anchored at the active cursor position.
pub fn selectCursorCell(self: *Surface) !bool {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();

const screen: *terminal.Screen = self.io.terminal.screens.active;
const pin = pin: {
// pointFromPin(.viewport, ...) can return points beyond the visible rows
// when the viewport includes additional written history; only anchor to
// the live cursor if it is truly visible in the viewport.
if (screen.pages.pointFromPin(.viewport, screen.cursor.page_pin.*)) |pt| {
if (pt.viewport.y < @as(u32, screen.pages.rows)) {
break :pin screen.cursor.page_pin.*;
}
}

// If we've scrolled away from the live cursor, start at the viewport origin
// so copy mode can select from the currently visible history.
break :pin screen.pages.pin(.{ .viewport = .{} }) orelse return false;
};
// Entering keyboard copy mode should not clobber clipboard contents.
try screen.select(terminal.Selection.init(pin, pin, false));
screen.dirty.selection = true;
try self.queueRender();
return true;
}

/// Clear the active selection, if any.
pub fn clearSelection(self: *Surface) !bool {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();

const screen: *terminal.Screen = self.io.terminal.screens.active;
if (screen.selection == null) return false;
try self.setSelection(null);
screen.dirty.selection = true;
try self.queueRender();
return true;
}

/// Returns the selected text. This is allocated.
pub fn selectionString(self: *Surface, alloc: Allocator) !?[:0]const u8 {
self.renderer_state.mutex.lock();
Expand Down
16 changes: 16 additions & 0 deletions src/apprt/embedded.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1584,6 +1584,22 @@ pub const CAPI = struct {
return surface.core_surface.hasSelection();
}

/// Start a selection at the active cursor cell.
export fn ghostty_surface_select_cursor_cell(surface: *Surface) bool {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Declare new selection APIs in the public C header

These two new exports are not declared in include/ghostty.h, so embedders using the shipped header cannot call them directly; in C99+ this typically surfaces as an implicit-declaration build error (often fatal under -Werror) even though the symbols exist at link time. I checked the header section around the existing selection APIs (ghostty_surface_has_selection/ghostty_surface_read_selection) and there are no prototypes for ghostty_surface_select_cursor_cell or ghostty_surface_clear_selection.

Useful? React with 👍 / 👎.

return surface.core_surface.selectCursorCell() catch |err| {
log.warn("error selecting cursor cell err={}", .{err});
return false;
};
}

/// Clear the active selection.
export fn ghostty_surface_clear_selection(surface: *Surface) bool {
return surface.core_surface.clearSelection() catch |err| {
log.warn("error clearing selection err={}", .{err});
return false;
};
}

/// Same as ghostty_surface_read_text but reads from the user selection,
/// if any.
export fn ghostty_surface_read_selection(
Expand Down
Loading