mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
apprt: convert selection info to a single struct and C API
This commit is contained in:
@ -373,6 +373,13 @@ typedef struct {
|
|||||||
const char* message;
|
const char* message;
|
||||||
} ghostty_error_s;
|
} ghostty_error_s;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
double tl_px_x;
|
||||||
|
double tl_px_y;
|
||||||
|
uint32_t offset_start;
|
||||||
|
uint32_t offset_len;
|
||||||
|
} ghostty_selection_s;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void* nsview;
|
void* nsview;
|
||||||
} ghostty_platform_macos_s;
|
} ghostty_platform_macos_s;
|
||||||
@ -557,8 +564,7 @@ uintptr_t ghostty_surface_selection(ghostty_surface_t, char*, uintptr_t);
|
|||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
void ghostty_surface_set_display_id(ghostty_surface_t, uint32_t);
|
void ghostty_surface_set_display_id(ghostty_surface_t, uint32_t);
|
||||||
void* ghostty_surface_quicklook_font(ghostty_surface_t);
|
void* ghostty_surface_quicklook_font(ghostty_surface_t);
|
||||||
void ghostty_surface_selection_range(ghostty_surface_t, uint32_t*, uint32_t*);
|
bool ghostty_surface_selection_info(ghostty_surface_t, ghostty_selection_s*);
|
||||||
void ghostty_surface_selection_point(ghostty_surface_t, double*, double*);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ghostty_inspector_t ghostty_surface_inspector(ghostty_surface_t);
|
ghostty_inspector_t ghostty_surface_inspector(ghostty_surface_t);
|
||||||
|
@ -935,10 +935,9 @@ extension Ghostty.SurfaceView: NSTextInputClient {
|
|||||||
// Get our range from the Ghostty API. There is a race condition between getting the
|
// Get our range from the Ghostty API. There is a race condition between getting the
|
||||||
// range and actually using it since our selection may change but there isn't a good
|
// range and actually using it since our selection may change but there isn't a good
|
||||||
// way I can think of to solve this for AppKit.
|
// way I can think of to solve this for AppKit.
|
||||||
var start: UInt32 = 0;
|
var sel: ghostty_selection_s = ghostty_selection_s();
|
||||||
var len: UInt32 = 0;
|
guard ghostty_surface_selection_info(surface, &sel) else { return NSRange() }
|
||||||
ghostty_surface_selection_range(surface, &start, &len);
|
return NSRange(location: Int(sel.offset_start), length: Int(sel.offset_len))
|
||||||
return NSRange(location: Int(start), length: Int(len))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setMarkedText(_ string: Any, selectedRange: NSRange, replacementRange: NSRange) {
|
func setMarkedText(_ string: Any, selectedRange: NSRange, replacementRange: NSRange) {
|
||||||
@ -1017,7 +1016,13 @@ extension Ghostty.SurfaceView: NSTextInputClient {
|
|||||||
// point right now. I'm sure I'm missing something fundamental...
|
// point right now. I'm sure I'm missing something fundamental...
|
||||||
if range.length > 0 && range != self.selectedRange() {
|
if range.length > 0 && range != self.selectedRange() {
|
||||||
// QuickLook
|
// QuickLook
|
||||||
ghostty_surface_selection_point(surface, &x, &y)
|
var sel: ghostty_selection_s = ghostty_selection_s();
|
||||||
|
if ghostty_surface_selection_info(surface, &sel) {
|
||||||
|
x = sel.tl_px_x;
|
||||||
|
y = sel.tl_px_y;
|
||||||
|
} else {
|
||||||
|
ghostty_surface_ime_point(surface, &x, &y)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ghostty_surface_ime_point(surface, &x, &y)
|
ghostty_surface_ime_point(surface, &x, &y)
|
||||||
}
|
}
|
||||||
|
108
src/Surface.zig
108
src/Surface.zig
@ -849,26 +849,23 @@ pub fn selectionString(self: *Surface, alloc: Allocator) !?[]const u8 {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This returns the selection range offset from the beginning of the
|
/// Return the apprt selection metadata used by apprt's for implementing
|
||||||
/// viewport. If the selection is not entirely within the viewport then
|
/// things like contextual information on right click and so on.
|
||||||
/// this will return null.
|
|
||||||
///
|
///
|
||||||
/// This is a oddly specific function that is used with macOS to enable
|
/// This only returns non-null if the selection is fully contained within
|
||||||
/// NSTextInputClient to work properly for features such as the IME Emoji
|
/// the viewport. The use case for this function at the time of authoring
|
||||||
/// keyboard and QuickLook amongst other things.
|
/// it is for apprt's to implement right-click contextual menus and
|
||||||
pub fn selectionRange(self: *Surface) ?struct {
|
/// those only make sense for selections fully contained within the
|
||||||
start: u32,
|
/// viewport. We don't handle the case where you right click a word-wrapped
|
||||||
len: u32,
|
/// word at the end of the viewport yet.
|
||||||
} {
|
pub fn selectionInfo(self: *const Surface) ?apprt.Selection {
|
||||||
self.renderer_state.mutex.lock();
|
self.renderer_state.mutex.lock();
|
||||||
defer self.renderer_state.mutex.unlock();
|
defer self.renderer_state.mutex.unlock();
|
||||||
|
|
||||||
// Get the TL/BR pins for the selection
|
|
||||||
const sel = self.io.terminal.screen.selection orelse return null;
|
const sel = self.io.terminal.screen.selection orelse return null;
|
||||||
|
|
||||||
|
// Get the TL/BR pins for the selection and convert to viewport.
|
||||||
const tl = sel.topLeft(&self.io.terminal.screen);
|
const tl = sel.topLeft(&self.io.terminal.screen);
|
||||||
const br = sel.bottomRight(&self.io.terminal.screen);
|
const br = sel.bottomRight(&self.io.terminal.screen);
|
||||||
|
|
||||||
// Convert the pins to coordinates (x,y)
|
|
||||||
const tl_pt = self.io.terminal.screen.pages.pointFromPin(.viewport, tl) orelse return null;
|
const tl_pt = self.io.terminal.screen.pages.pointFromPin(.viewport, tl) orelse return null;
|
||||||
const br_pt = self.io.terminal.screen.pages.pointFromPin(.viewport, br) orelse return null;
|
const br_pt = self.io.terminal.screen.pages.pointFromPin(.viewport, br) orelse return null;
|
||||||
const tl_coord = tl_pt.coord();
|
const tl_coord = tl_pt.coord();
|
||||||
@ -878,7 +875,41 @@ pub fn selectionRange(self: *Surface) ?struct {
|
|||||||
const start = tl_coord.y * self.io.terminal.screen.pages.cols + tl_coord.x;
|
const start = tl_coord.y * self.io.terminal.screen.pages.cols + tl_coord.x;
|
||||||
const end = br_coord.y * self.io.terminal.screen.pages.cols + br_coord.x;
|
const end = br_coord.y * self.io.terminal.screen.pages.cols + br_coord.x;
|
||||||
|
|
||||||
return .{ .start = start, .len = end - start };
|
// Our sizes are all scaled so we need to send the unscaled values back.
|
||||||
|
const content_scale = self.rt_surface.getContentScale() catch .{ .x = 1, .y = 1 };
|
||||||
|
|
||||||
|
const x: f64 = x: {
|
||||||
|
// Simple x * cell width gives the top-left corner
|
||||||
|
var x: f64 = @floatFromInt(tl_coord.x * self.cell_size.width);
|
||||||
|
|
||||||
|
// We want the midpoint
|
||||||
|
x += @as(f64, @floatFromInt(self.cell_size.width)) / 2;
|
||||||
|
|
||||||
|
// And scale it
|
||||||
|
x /= content_scale.x;
|
||||||
|
|
||||||
|
break :x x;
|
||||||
|
};
|
||||||
|
|
||||||
|
const y: f64 = y: {
|
||||||
|
// Simple x * cell width gives the top-left corner
|
||||||
|
var y: f64 = @floatFromInt(tl_coord.y * self.cell_size.height);
|
||||||
|
|
||||||
|
// We want the bottom
|
||||||
|
y += @floatFromInt(self.cell_size.height);
|
||||||
|
|
||||||
|
// And scale it
|
||||||
|
y /= content_scale.y;
|
||||||
|
|
||||||
|
break :y y;
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.tl_x_px = x,
|
||||||
|
.tl_y_px = y,
|
||||||
|
.offset_start = start,
|
||||||
|
.offset_len = end - start,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the pwd of the terminal, if any. This is always copied because
|
/// Returns the pwd of the terminal, if any. This is always copied because
|
||||||
@ -933,53 +964,6 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
|||||||
return .{ .x = x, .y = y };
|
return .{ .x = x, .y = y };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the x/y coordinate of where the selection top-left is. This is
|
|
||||||
/// used currently only by macOS to render the QuickLook highlight in the
|
|
||||||
/// proper location.
|
|
||||||
pub fn selectionPoint(self: *const Surface) ?apprt.IMEPos {
|
|
||||||
self.renderer_state.mutex.lock();
|
|
||||||
defer self.renderer_state.mutex.unlock();
|
|
||||||
|
|
||||||
// Get the top-left coordinate of the selection in the viewport.
|
|
||||||
const sel = self.io.terminal.screen.selection orelse return null;
|
|
||||||
const tl_pt = self.io.terminal.screen.pages.pointFromPin(
|
|
||||||
.viewport,
|
|
||||||
sel.topLeft(&self.io.terminal.screen),
|
|
||||||
) orelse return null;
|
|
||||||
const tl_coord = tl_pt.coord();
|
|
||||||
|
|
||||||
// Our sizes are all scaled so we need to send the unscaled values back.
|
|
||||||
const content_scale = self.rt_surface.getContentScale() catch .{ .x = 1, .y = 1 };
|
|
||||||
|
|
||||||
const x: f64 = x: {
|
|
||||||
// Simple x * cell width gives the top-left corner
|
|
||||||
var x: f64 = @floatFromInt(tl_coord.x * self.cell_size.width);
|
|
||||||
|
|
||||||
// We want the midpoint
|
|
||||||
x += @as(f64, @floatFromInt(self.cell_size.width)) / 2;
|
|
||||||
|
|
||||||
// And scale it
|
|
||||||
x /= content_scale.x;
|
|
||||||
|
|
||||||
break :x x;
|
|
||||||
};
|
|
||||||
|
|
||||||
const y: f64 = y: {
|
|
||||||
// Simple x * cell width gives the top-left corner
|
|
||||||
var y: f64 = @floatFromInt(tl_coord.y * self.cell_size.height);
|
|
||||||
|
|
||||||
// We want the bottom
|
|
||||||
y += @floatFromInt(self.cell_size.height);
|
|
||||||
|
|
||||||
// And scale it
|
|
||||||
y /= content_scale.y;
|
|
||||||
|
|
||||||
break :y y;
|
|
||||||
};
|
|
||||||
|
|
||||||
return .{ .x = x, .y = y };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard) !void {
|
fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard) !void {
|
||||||
if (self.config.clipboard_write == .deny) {
|
if (self.config.clipboard_write == .deny) {
|
||||||
log.info("application attempted to write clipboard, but 'clipboard-write' is set to deny", .{});
|
log.info("application attempted to write clipboard, but 'clipboard-write' is set to deny", .{});
|
||||||
|
@ -1376,6 +1376,13 @@ pub const CAPI = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Selection = extern struct {
|
||||||
|
tl_x_px: f64,
|
||||||
|
tl_y_px: f64,
|
||||||
|
offset_start: u32,
|
||||||
|
offset_len: u32,
|
||||||
|
};
|
||||||
|
|
||||||
/// Create a new app.
|
/// Create a new app.
|
||||||
export fn ghostty_app_new(
|
export fn ghostty_app_new(
|
||||||
opts: *const apprt.runtime.App.Options,
|
opts: *const apprt.runtime.App.Options,
|
||||||
@ -1803,34 +1810,24 @@ pub const CAPI = struct {
|
|||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This returns the start and length of the current selection range
|
/// This returns the selection metadata for the current selection.
|
||||||
/// in viewport coordinates. If the selection is not visible in the
|
/// This will return false if there is no selection or the
|
||||||
/// viewport completely then this will return 0,0. This rather odd
|
/// selection is not fully contained in the viewport (since the
|
||||||
/// detail is due to the current usage of this in the macOS app where
|
/// metadata is all about that).
|
||||||
/// selections are only meaningful if they're in the viewport. We can
|
export fn ghostty_surface_selection_info(
|
||||||
/// change this behavior if we have something useful to do with the
|
|
||||||
/// selection range outside of the viewport in the future.
|
|
||||||
export fn ghostty_surface_selection_range(
|
|
||||||
ptr: *Surface,
|
ptr: *Surface,
|
||||||
start: *u32,
|
info: *Selection,
|
||||||
len: *u32,
|
) bool {
|
||||||
) void {
|
const sel = ptr.core_surface.selectionInfo() orelse
|
||||||
start.* = 0;
|
return false;
|
||||||
len.* = 0;
|
|
||||||
|
|
||||||
const range = ptr.core_surface.selectionRange() orelse return;
|
info.* = .{
|
||||||
start.* = range.start;
|
.tl_x_px = sel.tl_x_px,
|
||||||
len.* = range.len;
|
.tl_y_px = sel.tl_y_px,
|
||||||
}
|
.offset_start = sel.offset_start,
|
||||||
|
.offset_len = sel.offset_len,
|
||||||
export fn ghostty_surface_selection_point(
|
};
|
||||||
ptr: *Surface,
|
return true;
|
||||||
x: *f64,
|
|
||||||
y: *f64,
|
|
||||||
) void {
|
|
||||||
const point = ptr.core_surface.selectionPoint() orelse return;
|
|
||||||
x.* = point.x;
|
|
||||||
y.* = point.y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn ghostty_inspector_metal_init(ptr: *Inspector, device: objc.c.id) bool {
|
export fn ghostty_inspector_metal_init(ptr: *Inspector, device: objc.c.id) bool {
|
||||||
|
@ -74,3 +74,18 @@ pub const ColorScheme = enum(u2) {
|
|||||||
light = 0,
|
light = 0,
|
||||||
dark = 1,
|
dark = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Selection information
|
||||||
|
pub const Selection = struct {
|
||||||
|
/// Top-left point of the selection in the viewport in scaled
|
||||||
|
/// window pixels. (0,0) is the top-left of the window.
|
||||||
|
tl_x_px: f64,
|
||||||
|
tl_y_px: f64,
|
||||||
|
|
||||||
|
/// The offset of the selection start in cells from the top-left
|
||||||
|
/// of the viewport.
|
||||||
|
///
|
||||||
|
/// This is a strange metric but its used by macOS.
|
||||||
|
offset_start: u32,
|
||||||
|
offset_len: u32,
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user