mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-04 02:18:17 +00:00
Compare commits
16 Commits
d316449ebf
...
8d11c08db3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8d11c08db3 | ||
![]() |
90c0fc2590 | ||
![]() |
e909e28876 | ||
![]() |
cf0390bab5 | ||
![]() |
4614e5fdad | ||
![]() |
ce94bb9f6a | ||
![]() |
ac104a3dfc | ||
![]() |
16e47e7586 | ||
![]() |
e8217aa007 | ||
![]() |
9aa1698e5a | ||
![]() |
3664ee9f87 | ||
![]() |
a72995590b | ||
![]() |
2bf0d3f4c7 | ||
![]() |
4af290d5f0 | ||
![]() |
ef7857f9be | ||
![]() |
7dcf2c9b62 |
@@ -964,7 +964,7 @@ void ghostty_surface_mouse_scroll(ghostty_surface_t,
|
||||
double,
|
||||
ghostty_input_scroll_mods_t);
|
||||
void ghostty_surface_mouse_pressure(ghostty_surface_t, uint32_t, double);
|
||||
void ghostty_surface_ime_point(ghostty_surface_t, double*, double*);
|
||||
void ghostty_surface_ime_point(ghostty_surface_t, double*, double*, double*, double*);
|
||||
void ghostty_surface_request_close(ghostty_surface_t);
|
||||
void ghostty_surface_split(ghostty_surface_t, ghostty_action_split_direction_e);
|
||||
void ghostty_surface_split_focus(ghostty_surface_t,
|
||||
|
@@ -1683,8 +1683,10 @@ extension Ghostty.SurfaceView: NSTextInputClient {
|
||||
}
|
||||
|
||||
// Ghostty will tell us where it thinks an IME keyboard should render.
|
||||
var x: Double = 0;
|
||||
var y: Double = 0;
|
||||
var x: Double = 0
|
||||
var y: Double = 0
|
||||
var width: Double = cellSize.width
|
||||
var height: Double = cellSize.height
|
||||
|
||||
// QuickLook never gives us a matching range to our selection so if we detect
|
||||
// this then we return the top-left selection point rather than the cursor point.
|
||||
@@ -1702,15 +1704,19 @@ extension Ghostty.SurfaceView: NSTextInputClient {
|
||||
// Free our text
|
||||
ghostty_surface_free_text(surface, &text)
|
||||
} else {
|
||||
ghostty_surface_ime_point(surface, &x, &y)
|
||||
ghostty_surface_ime_point(surface, &x, &y, &width, &height)
|
||||
}
|
||||
} else {
|
||||
ghostty_surface_ime_point(surface, &x, &y)
|
||||
ghostty_surface_ime_point(surface, &x, &y, &width, &height)
|
||||
}
|
||||
|
||||
// Ghostty coordinates are in top-left (0, 0) so we have to convert to
|
||||
// bottom-left since that is what UIKit expects
|
||||
let viewRect = NSMakeRect(x, frame.size.height - y, 0, 0)
|
||||
let viewRect = NSMakeRect(
|
||||
x,
|
||||
frame.size.height - y,
|
||||
max(width, cellSize.width),
|
||||
max(height, cellSize.height))
|
||||
|
||||
// Convert the point to the window coordinates
|
||||
let winRect = self.convert(viewRect, to: nil)
|
||||
|
@@ -258,6 +258,7 @@ const DerivedConfig = struct {
|
||||
mouse_shift_capture: configpkg.MouseShiftCapture,
|
||||
macos_non_native_fullscreen: configpkg.NonNativeFullscreen,
|
||||
macos_option_as_alt: ?configpkg.OptionAsAlt,
|
||||
selection_clear_on_copy: bool,
|
||||
selection_clear_on_typing: bool,
|
||||
vt_kam_allowed: bool,
|
||||
wait_after_command: bool,
|
||||
@@ -327,6 +328,7 @@ const DerivedConfig = struct {
|
||||
.mouse_shift_capture = config.@"mouse-shift-capture",
|
||||
.macos_non_native_fullscreen = config.@"macos-non-native-fullscreen",
|
||||
.macos_option_as_alt = config.@"macos-option-as-alt",
|
||||
.selection_clear_on_copy = config.@"selection-clear-on-copy",
|
||||
.selection_clear_on_typing = config.@"selection-clear-on-typing",
|
||||
.vt_kam_allowed = config.@"vt-kam-allowed",
|
||||
.wait_after_command = config.@"wait-after-command",
|
||||
@@ -1730,6 +1732,7 @@ pub fn pwd(
|
||||
pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
||||
self.renderer_state.mutex.lock();
|
||||
const cursor = self.renderer_state.terminal.screen.cursor;
|
||||
const preedit_width: usize = if (self.renderer_state.preedit) |preedit| preedit.width() else 0;
|
||||
self.renderer_state.mutex.unlock();
|
||||
|
||||
// TODO: need to handle when scrolling and the cursor is not
|
||||
@@ -1764,7 +1767,38 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
||||
break :y y;
|
||||
};
|
||||
|
||||
return .{ .x = x, .y = y };
|
||||
// Our height for now is always just the cell height because our preedit
|
||||
// rendering only renders in a single line.
|
||||
const height: f64 = height: {
|
||||
var height: f64 = @floatFromInt(self.size.cell.height);
|
||||
height /= content_scale.y;
|
||||
break :height height;
|
||||
};
|
||||
const width: f64 = width: {
|
||||
var width: f64 = @floatFromInt(preedit_width * self.size.cell.width);
|
||||
|
||||
// Our max width is the remaining screen width after the cursor.
|
||||
// We don't have to deal with wrapping because the preedit doesn't
|
||||
// wrap right now.
|
||||
const screen_width: f64 = @floatFromInt(self.size.terminal().width);
|
||||
const x_offset: f64 = @floatFromInt((cursor.x + 1) * self.size.cell.width);
|
||||
const max = screen_width - x_offset;
|
||||
width = @min(width, max);
|
||||
|
||||
// Note: we don't apply content scale here because it looks like
|
||||
// for some reason in macOS its already scaled. I'm not sure why
|
||||
// that is so I'm going to just leave this comment here so its known
|
||||
// that I left this out on purpose pending more investigation.
|
||||
|
||||
break :width width;
|
||||
};
|
||||
|
||||
return .{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
}
|
||||
|
||||
fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard) !void {
|
||||
@@ -4512,6 +4546,17 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
return true;
|
||||
};
|
||||
|
||||
// Clear the selection if configured to do so.
|
||||
if (self.config.selection_clear_on_copy) {
|
||||
if (self.setSelection(null)) {
|
||||
self.queueRender() catch |err| {
|
||||
log.warn("failed to queue render after clear selection err={}", .{err});
|
||||
};
|
||||
} else |err| {
|
||||
log.warn("failed to clear selection after copy err={}", .{err});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -1822,10 +1822,18 @@ pub const CAPI = struct {
|
||||
surface.mousePressureCallback(stage, pressure);
|
||||
}
|
||||
|
||||
export fn ghostty_surface_ime_point(surface: *Surface, x: *f64, y: *f64) void {
|
||||
export fn ghostty_surface_ime_point(
|
||||
surface: *Surface,
|
||||
x: *f64,
|
||||
y: *f64,
|
||||
width: *f64,
|
||||
height: *f64,
|
||||
) void {
|
||||
const pos = surface.core_surface.imePoint();
|
||||
x.* = pos.x;
|
||||
y.* = pos.y;
|
||||
width.* = pos.width;
|
||||
height.* = pos.height;
|
||||
}
|
||||
|
||||
/// Request that the surface become closed. This will go through the
|
||||
|
@@ -24,6 +24,8 @@ pub const CursorPos = struct {
|
||||
pub const IMEPos = struct {
|
||||
x: f64,
|
||||
y: f64,
|
||||
width: f64,
|
||||
height: f64,
|
||||
};
|
||||
|
||||
/// The clipboard type.
|
||||
|
@@ -654,6 +654,18 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF },
|
||||
/// Available since: 1.2.0
|
||||
@"selection-clear-on-typing": bool = true,
|
||||
|
||||
/// Whether to clear selected text after copying. This defaults to `false`.
|
||||
///
|
||||
/// When set to `true`, the selection will be automatically cleared after
|
||||
/// any copy operation that invokes the `copy_to_clipboard` keyboard binding.
|
||||
/// Importantly, this will not clear the selection if the copy operation
|
||||
/// was invoked via `copy-on-select`.
|
||||
///
|
||||
/// When set to `false`, the selection remains visible after copying, allowing
|
||||
/// to see what was copied and potentially perform additional operations
|
||||
/// on the same selection.
|
||||
@"selection-clear-on-copy": bool = false,
|
||||
|
||||
/// The minimum contrast ratio between the foreground and background colors.
|
||||
/// The contrast ratio is a value between 1 and 21. A value of 1 allows for no
|
||||
/// contrast (e.g. black on black). This value is the contrast ratio as defined
|
||||
|
@@ -806,14 +806,41 @@ pub const Face = struct {
|
||||
const ic_width: ?f64 = ic_width: {
|
||||
const glyph = self.glyphIndex('水') orelse break :ic_width null;
|
||||
|
||||
var advances: [1]macos.graphics.Size = undefined;
|
||||
_ = ct_font.getAdvancesForGlyphs(
|
||||
const advance = ct_font.getAdvancesForGlyphs(
|
||||
.horizontal,
|
||||
&.{@intCast(glyph)},
|
||||
&advances,
|
||||
null,
|
||||
);
|
||||
|
||||
break :ic_width advances[0].width;
|
||||
const bounds = ct_font.getBoundingRectsForGlyphs(
|
||||
.horizontal,
|
||||
&.{@intCast(glyph)},
|
||||
null,
|
||||
);
|
||||
|
||||
// If the advance of the glyph is less than the width of the actual
|
||||
// glyph then we just treat it as invalid since it's probably wrong
|
||||
// and using it for size normalization will instead make the font
|
||||
// way too big.
|
||||
//
|
||||
// This can sometimes happen if there's a CJK font that has been
|
||||
// patched with the nerd fonts patcher and it butchers the advance
|
||||
// values so the advance ends up half the width of the actual glyph.
|
||||
if (bounds.size.width > advance) {
|
||||
var buf: [1024]u8 = undefined;
|
||||
const font_name = self.name(&buf) catch "<Error getting font name>";
|
||||
log.warn(
|
||||
"(getMetrics) Width of glyph '水' for font \"{s}\" is greater than its advance ({d} > {d}), discarding ic_width metric.",
|
||||
.{
|
||||
font_name,
|
||||
bounds.size.width,
|
||||
advance,
|
||||
},
|
||||
);
|
||||
break :ic_width null;
|
||||
}
|
||||
|
||||
break :ic_width advance;
|
||||
};
|
||||
|
||||
return .{
|
||||
|
@@ -1007,7 +1007,31 @@ pub const Face = struct {
|
||||
.no_svg = true,
|
||||
}) catch break :ic_width null;
|
||||
|
||||
break :ic_width f26dot6ToF64(face.handle.*.glyph.*.advance.x);
|
||||
const ft_glyph = face.handle.*.glyph;
|
||||
|
||||
// If the advance of the glyph is less than the width of the actual
|
||||
// glyph then we just treat it as invalid since it's probably wrong
|
||||
// and using it for size normalization will instead make the font
|
||||
// way too big.
|
||||
//
|
||||
// This can sometimes happen if there's a CJK font that has been
|
||||
// patched with the nerd fonts patcher and it butchers the advance
|
||||
// values so the advance ends up half the width of the actual glyph.
|
||||
if (ft_glyph.*.metrics.width > ft_glyph.*.advance.x) {
|
||||
var buf: [1024]u8 = undefined;
|
||||
const font_name = self.name(&buf) catch "<Error getting font name>";
|
||||
log.warn(
|
||||
"(getMetrics) Width of glyph '水' for font \"{s}\" is greater than its advance ({d} > {d}), discarding ic_width metric.",
|
||||
.{
|
||||
font_name,
|
||||
f26dot6ToF64(ft_glyph.*.metrics.width),
|
||||
f26dot6ToF64(ft_glyph.*.advance.x),
|
||||
},
|
||||
);
|
||||
break :ic_width null;
|
||||
}
|
||||
|
||||
break :ic_width f26dot6ToF64(ft_glyph.*.advance.x);
|
||||
};
|
||||
|
||||
return .{
|
||||
|
@@ -1551,15 +1551,17 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
// Look up the image
|
||||
const image = self.images.get(p.image_id) orelse {
|
||||
log.warn("image not found for placement image_id={}", .{p.image_id});
|
||||
return;
|
||||
continue;
|
||||
};
|
||||
|
||||
// Get the texture
|
||||
const texture = switch (image.image) {
|
||||
.ready => |t| t,
|
||||
.ready,
|
||||
.unload_ready,
|
||||
=> |t| t,
|
||||
else => {
|
||||
log.warn("image not ready for placement image_id={}", .{p.image_id});
|
||||
return;
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1909,7 +1911,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
if (img.isUnloading()) {
|
||||
img.deinit(self.alloc);
|
||||
self.images.removeByPtr(kv.key_ptr);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
if (img.isPending()) try img.upload(self.alloc, &self.api);
|
||||
}
|
||||
|
@@ -84,10 +84,23 @@ pub const Style = struct {
|
||||
}
|
||||
|
||||
/// True if the style is equal to another style.
|
||||
/// For performance do direct comparisons first.
|
||||
pub fn eql(self: Style, other: Style) bool {
|
||||
// We convert the styles to packed structs and compare as integers
|
||||
// because this is much faster than comparing each field separately.
|
||||
return PackedStyle.fromStyle(self) == PackedStyle.fromStyle(other);
|
||||
inline for (comptime std.meta.fields(Style)) |field| {
|
||||
if (comptime std.meta.hasUniqueRepresentation(field.type)) {
|
||||
if (@field(self, field.name) != @field(other, field.name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
inline for (comptime std.meta.fields(Style)) |field| {
|
||||
if (comptime !std.meta.hasUniqueRepresentation(field.type)) {
|
||||
if (!std.meta.eql(@field(self, field.name), @field(other, field.name))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns the bg color for a cell with this style given the cell
|
||||
|
Reference in New Issue
Block a user