mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
core: remove size limit on preedit length by heap allocating
Fixes #882 We previously had a hardcoded limit of 16 codepoints. In #882, a user pointed out that in Chinese, is reasonable for a preedit input to be longer than this. To avoid any future issues, this commit moves to a heap-allocated variant. Preedits aren't that common, they aren't high throughput, and they're generally pretty small, so using a heap allocation is fine. The memory is owned by the person who set it.
This commit is contained in:
@ -629,6 +629,8 @@ pub fn deinit(self: *Surface) void {
|
|||||||
self.alloc.destroy(v);
|
self.alloc.destroy(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up our render state
|
||||||
|
if (self.renderer_state.preedit) |p| self.alloc.free(p.codepoints);
|
||||||
self.alloc.destroy(self.renderer_state.mutex);
|
self.alloc.destroy(self.renderer_state.mutex);
|
||||||
self.config.deinit();
|
self.config.deinit();
|
||||||
|
|
||||||
@ -1087,7 +1089,10 @@ pub fn preeditCallback(self: *Surface, preedit_: ?[]const u8) !void {
|
|||||||
defer self.renderer_state.mutex.unlock();
|
defer self.renderer_state.mutex.unlock();
|
||||||
|
|
||||||
// We always clear our prior preedit
|
// We always clear our prior preedit
|
||||||
self.renderer_state.preedit = null;
|
if (self.renderer_state.preedit) |p| {
|
||||||
|
self.alloc.free(p.codepoints);
|
||||||
|
self.renderer_state.preedit = null;
|
||||||
|
}
|
||||||
|
|
||||||
// If we have no text, we're done. We queue a render in case we cleared
|
// If we have no text, we're done. We queue a render in case we cleared
|
||||||
// a prior preedit (likely).
|
// a prior preedit (likely).
|
||||||
@ -1101,7 +1106,9 @@ pub fn preeditCallback(self: *Surface, preedit_: ?[]const u8) !void {
|
|||||||
var it = view.iterator();
|
var it = view.iterator();
|
||||||
|
|
||||||
// Allocate the codepoints slice
|
// Allocate the codepoints slice
|
||||||
var preedit: renderer.State.Preedit = .{};
|
const Codepoint = renderer.State.Preedit.Codepoint;
|
||||||
|
var codepoints: std.ArrayListUnmanaged(Codepoint) = .{};
|
||||||
|
defer codepoints.deinit(self.alloc);
|
||||||
while (it.nextCodepoint()) |cp| {
|
while (it.nextCodepoint()) |cp| {
|
||||||
const width = ziglyph.display_width.codePointWidth(cp, .half);
|
const width = ziglyph.display_width.codePointWidth(cp, .half);
|
||||||
|
|
||||||
@ -1110,21 +1117,18 @@ pub fn preeditCallback(self: *Surface, preedit_: ?[]const u8) !void {
|
|||||||
// Let's just ignore it.
|
// Let's just ignore it.
|
||||||
if (width <= 0) continue;
|
if (width <= 0) continue;
|
||||||
|
|
||||||
preedit.codepoints[preedit.len] = .{ .codepoint = cp, .wide = width >= 2 };
|
try codepoints.append(
|
||||||
preedit.len += 1;
|
self.alloc,
|
||||||
|
.{ .codepoint = cp, .wide = width >= 2 },
|
||||||
// This is a strange edge case. We have a generous buffer for
|
);
|
||||||
// preedit text but if we exceed it, we just truncate.
|
|
||||||
if (preedit.len >= preedit.codepoints.len) {
|
|
||||||
log.warn("preedit text is longer than our buffer, truncating", .{});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have no codepoints, then we're done.
|
// If we have no codepoints, then we're done.
|
||||||
if (preedit.len == 0) return;
|
if (codepoints.items.len == 0) return;
|
||||||
|
|
||||||
self.renderer_state.preedit = preedit;
|
self.renderer_state.preedit = .{
|
||||||
|
.codepoints = try codepoints.toOwnedSlice(self.alloc),
|
||||||
|
};
|
||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -632,6 +632,14 @@ pub fn updateFrame(
|
|||||||
cursor_blink_visible,
|
cursor_blink_visible,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Get our preedit state
|
||||||
|
const preedit: ?renderer.State.Preedit = preedit: {
|
||||||
|
if (cursor_style == null) break :preedit null;
|
||||||
|
const p = state.preedit orelse break :preedit null;
|
||||||
|
break :preedit try p.clone(self.alloc);
|
||||||
|
};
|
||||||
|
errdefer if (preedit) |p| p.deinit(self.alloc);
|
||||||
|
|
||||||
// If we have Kitty graphics data, we enter a SLOW SLOW SLOW path.
|
// If we have Kitty graphics data, we enter a SLOW SLOW SLOW path.
|
||||||
// We only do this if the Kitty image state is dirty meaning only if
|
// We only do this if the Kitty image state is dirty meaning only if
|
||||||
// it changes.
|
// it changes.
|
||||||
@ -644,11 +652,14 @@ pub fn updateFrame(
|
|||||||
.selection = selection,
|
.selection = selection,
|
||||||
.screen = screen_copy,
|
.screen = screen_copy,
|
||||||
.mouse = state.mouse,
|
.mouse = state.mouse,
|
||||||
.preedit = if (cursor_style != null) state.preedit else null,
|
.preedit = preedit,
|
||||||
.cursor_style = cursor_style,
|
.cursor_style = cursor_style,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
defer critical.screen.deinit();
|
defer {
|
||||||
|
critical.screen.deinit();
|
||||||
|
if (critical.preedit) |p| p.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
// Build our GPU cells
|
// Build our GPU cells
|
||||||
try self.rebuildCells(
|
try self.rebuildCells(
|
||||||
@ -1572,7 +1583,7 @@ fn rebuildCells(
|
|||||||
if (preedit) |preedit_v| {
|
if (preedit) |preedit_v| {
|
||||||
const range = preedit_range.?;
|
const range = preedit_range.?;
|
||||||
var x = range.x[0];
|
var x = range.x[0];
|
||||||
for (preedit_v.codepoints[0..preedit_v.len]) |cp| {
|
for (preedit_v.codepoints) |cp| {
|
||||||
self.addPreeditCell(cp, x, range.y) catch |err| {
|
self.addPreeditCell(cp, x, range.y) catch |err| {
|
||||||
log.warn("error building preedit cell, will be invalid x={} y={}, err={}", .{
|
log.warn("error building preedit cell, will be invalid x={} y={}, err={}", .{
|
||||||
x,
|
x,
|
||||||
|
@ -697,6 +697,14 @@ pub fn updateFrame(
|
|||||||
cursor_blink_visible,
|
cursor_blink_visible,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Get our preedit state
|
||||||
|
const preedit: ?renderer.State.Preedit = preedit: {
|
||||||
|
if (cursor_style == null) break :preedit null;
|
||||||
|
const p = state.preedit orelse break :preedit null;
|
||||||
|
break :preedit try p.clone(self.alloc);
|
||||||
|
};
|
||||||
|
errdefer if (preedit) |p| p.deinit(self.alloc);
|
||||||
|
|
||||||
// If we have Kitty graphics data, we enter a SLOW SLOW SLOW path.
|
// If we have Kitty graphics data, we enter a SLOW SLOW SLOW path.
|
||||||
// We only do this if the Kitty image state is dirty meaning only if
|
// We only do this if the Kitty image state is dirty meaning only if
|
||||||
// it changes.
|
// it changes.
|
||||||
@ -709,11 +717,14 @@ pub fn updateFrame(
|
|||||||
.selection = selection,
|
.selection = selection,
|
||||||
.screen = screen_copy,
|
.screen = screen_copy,
|
||||||
.mouse = state.mouse,
|
.mouse = state.mouse,
|
||||||
.preedit = if (cursor_style != null) state.preedit else null,
|
.preedit = preedit,
|
||||||
.cursor_style = cursor_style,
|
.cursor_style = cursor_style,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
defer critical.screen.deinit();
|
defer {
|
||||||
|
critical.screen.deinit();
|
||||||
|
if (critical.preedit) |p| p.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
// Grab our draw mutex if we have it and update our data
|
// Grab our draw mutex if we have it and update our data
|
||||||
{
|
{
|
||||||
@ -1083,7 +1094,7 @@ pub fn rebuildCells(
|
|||||||
if (preedit) |preedit_v| {
|
if (preedit) |preedit_v| {
|
||||||
const range = preedit_range.?;
|
const range = preedit_range.?;
|
||||||
var x = range.x[0];
|
var x = range.x[0];
|
||||||
for (preedit_v.codepoints[0..preedit_v.len]) |cp| {
|
for (preedit_v.codepoints) |cp| {
|
||||||
self.addPreeditCell(cp, x, range.y) catch |err| {
|
self.addPreeditCell(cp, x, range.y) catch |err| {
|
||||||
log.warn("error building preedit cell, will be invalid x={} y={}, err={}", .{
|
log.warn("error building preedit cell, will be invalid x={} y={}, err={}", .{
|
||||||
x,
|
x,
|
||||||
|
@ -38,11 +38,8 @@ pub const Mouse = struct {
|
|||||||
|
|
||||||
/// The pre-edit state. See Surface.preeditCallback for more information.
|
/// The pre-edit state. See Surface.preeditCallback for more information.
|
||||||
pub const Preedit = struct {
|
pub const Preedit = struct {
|
||||||
/// The codepoints to render as preedit text. We allow up to 16 codepoints
|
/// The codepoints to render as preedit text.
|
||||||
/// as a sort of arbitrary limit. If we experience a realisitic use case
|
codepoints: []Codepoint,
|
||||||
/// where we need more please open an issue.
|
|
||||||
codepoints: [16]Codepoint = undefined,
|
|
||||||
len: u8 = 0,
|
|
||||||
|
|
||||||
/// A single codepoint to render as preedit text.
|
/// A single codepoint to render as preedit text.
|
||||||
pub const Codepoint = struct {
|
pub const Codepoint = struct {
|
||||||
@ -50,10 +47,22 @@ pub const Preedit = struct {
|
|||||||
wide: bool = false,
|
wide: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Deinit this preedit that was cre
|
||||||
|
pub fn deinit(self: *const Preedit, alloc: Allocator) void {
|
||||||
|
alloc.free(self.codepoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate a copy of this preedit in the given allocator..
|
||||||
|
pub fn clone(self: *const Preedit, alloc: Allocator) !Preedit {
|
||||||
|
return .{
|
||||||
|
.codepoints = try alloc.dupe(Codepoint, self.codepoints),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// The width in cells of all codepoints in the preedit.
|
/// The width in cells of all codepoints in the preedit.
|
||||||
pub fn width(self: *const Preedit) usize {
|
pub fn width(self: *const Preedit) usize {
|
||||||
var result: usize = 0;
|
var result: usize = 0;
|
||||||
for (self.codepoints[0..self.len]) |cp| {
|
for (self.codepoints) |cp| {
|
||||||
result += if (cp.wide) 2 else 1;
|
result += if (cp.wide) 2 else 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user