mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +03:00
config: grapheme-width-method, default to "unicode"
This adds a new configuration `grapheme-width-method` to change the default behavior that Ghostty uses to calculate grapheme width. The default value is `unicode` which is identical to setting mode 2027. **IMPORTANT:** This changes the default Ghostty behavior to be fully grapheme-aware including ZWJs, VS15, VS16. This may cause issues with some legacy programs and shells. I've changed my mind that this should become the default because enough people use emojis now that I've found in the beta program there are more issues reported about "incorrect emoji width" than any possibly desync issues. We'll see. For legacy programs, this can still be set to `grapheme-width-method = wcswidth`.
This commit is contained in:
@ -162,6 +162,32 @@ const c = @cImport({
|
||||
@"adjust-strikethrough-position": ?MetricModifier = null,
|
||||
@"adjust-strikethrough-thickness": ?MetricModifier = null,
|
||||
|
||||
/// The method to use for calculating the cell width of a grapheme cluster.
|
||||
/// The default value is "unicode" which uses the Unicode standard to
|
||||
/// determine grapheme width. This results in correct grapheme width but
|
||||
/// may result in cursor-desync issues with some programs (such as shells)
|
||||
/// that may use a legacy method such as "wcswidth".
|
||||
///
|
||||
/// Valid values are:
|
||||
///
|
||||
/// - "wcswidth" - Use the wcswidth function to determine grapheme width.
|
||||
/// This maximizes compatibility with legacy programs but may result
|
||||
/// in incorrect grapheme width for certain graphemes such as skin-tone
|
||||
/// emoji, non-English characters, etc.
|
||||
///
|
||||
/// Note that this "wcswidth" functionality is based on the libc wcswidth,
|
||||
/// not any other libraries with that name.
|
||||
///
|
||||
/// - "unicode" - Use the Unicode standard to determine grapheme width.
|
||||
///
|
||||
/// If a running program explicitly enables terminal mode 2027, then
|
||||
/// "unicode" width will be forced regardless of this configuration. When
|
||||
/// mode 2027 is reset, this configuration will be used again.
|
||||
///
|
||||
/// This configuration can be changed at runtime but will not affect existing
|
||||
/// printed cells. Only new cells will use the new configuration.
|
||||
@"grapheme-width-method": GraphemeWidthMethod = .unicode,
|
||||
|
||||
/// A named theme to use. The available themes are currently hardcoded to
|
||||
/// the themes that ship with Ghostty. On macOS, this list is in the
|
||||
/// `Ghostty.app/Contents/Resources/themes` directory. On Linux, this
|
||||
@ -2781,3 +2807,9 @@ pub const WindowSaveState = enum {
|
||||
never,
|
||||
always,
|
||||
};
|
||||
|
||||
/// See grapheme-width-method
|
||||
pub const GraphemeWidthMethod = enum {
|
||||
wcswidth,
|
||||
unicode,
|
||||
};
|
||||
|
@ -120,6 +120,9 @@ flags: packed struct {
|
||||
/// then we want to capture the shift key for the mouse protocol
|
||||
/// if the configuration allows it.
|
||||
mouse_shift_capture: enum { null, false, true } = .null,
|
||||
|
||||
/// If true, we perform grapheme clustering even if mode 2027 is disabled.
|
||||
default_grapheme_cluster: bool = false,
|
||||
} = .{},
|
||||
|
||||
/// The event types that can be reported for mouse-related activities.
|
||||
@ -724,6 +727,8 @@ pub fn print(self: *Terminal, c: u21) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
// log.debug("print={x} y={} x={}", .{ c, self.screen.cursor.y, self.screen.cursor.x });
|
||||
|
||||
// If we're not on the main display, do nothing for now
|
||||
if (self.status_display != .main) return;
|
||||
|
||||
@ -738,7 +743,7 @@ pub fn print(self: *Terminal, c: u21) !void {
|
||||
// purposely ordered in least-likely to most-likely so we can drop out
|
||||
// as quickly as possible.
|
||||
if (c > 255 and
|
||||
self.modes.get(.grapheme_cluster) and
|
||||
(self.modes.get(.grapheme_cluster) or self.flags.default_grapheme_cluster) and
|
||||
self.screen.cursor.x > 0)
|
||||
grapheme: {
|
||||
const row = self.screen.getRow(.{ .active = self.screen.cursor.y });
|
||||
@ -775,6 +780,7 @@ pub fn print(self: *Terminal, c: u21) !void {
|
||||
if (prev.cell.attrs.grapheme) {
|
||||
var it = row.codepointIterator(prev.x);
|
||||
while (it.next()) |cp2| {
|
||||
// log.debug("cp1={x} cp2={x}", .{ cp1, cp2 });
|
||||
assert(!ziglyph.graphemeBreak(
|
||||
cp1,
|
||||
cp2,
|
||||
@ -785,6 +791,7 @@ pub fn print(self: *Terminal, c: u21) !void {
|
||||
}
|
||||
}
|
||||
|
||||
// log.debug("cp1={x} cp2={x} end", .{ cp1, c });
|
||||
break :brk ziglyph.graphemeBreak(cp1, c, &state);
|
||||
};
|
||||
|
||||
@ -868,7 +875,9 @@ pub fn print(self: *Terminal, c: u21) !void {
|
||||
// If we have grapheme clustering enabled, we don't blindly attach
|
||||
// any zero width character to our cells and we instead just ignore
|
||||
// it.
|
||||
if (self.modes.get(.grapheme_cluster)) return;
|
||||
if (self.modes.get(.grapheme_cluster) or
|
||||
self.flags.default_grapheme_cluster)
|
||||
return;
|
||||
|
||||
// If we're at cell zero, then this is malformed data and we don't
|
||||
// print anything or even store this. Zero-width characters are ALWAYS
|
||||
|
@ -105,6 +105,7 @@ pub const DerivedConfig = struct {
|
||||
background: configpkg.Config.Color,
|
||||
osc_color_report_format: configpkg.Config.OSCColorReportFormat,
|
||||
term: []const u8,
|
||||
grapheme_width_method: configpkg.Config.GraphemeWidthMethod,
|
||||
|
||||
pub fn init(
|
||||
alloc_gpa: Allocator,
|
||||
@ -122,6 +123,7 @@ pub const DerivedConfig = struct {
|
||||
.background = config.background,
|
||||
.osc_color_report_format = config.@"osc-color-report-format",
|
||||
.term = config.term,
|
||||
.grapheme_width_method = config.@"grapheme-width-method",
|
||||
};
|
||||
}
|
||||
|
||||
@ -146,6 +148,7 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
|
||||
errdefer term.deinit(alloc);
|
||||
term.default_palette = opts.config.palette;
|
||||
term.color_palette.colors = opts.config.palette;
|
||||
term.flags.default_grapheme_cluster = opts.config.grapheme_width_method == .unicode;
|
||||
|
||||
// Set the image size limits
|
||||
try term.screen.kitty_images.setLimit(alloc, opts.config.image_storage_limit);
|
||||
@ -350,6 +353,8 @@ pub fn changeConfig(self: *Exec, config: *DerivedConfig) !void {
|
||||
// - command, working-directory: we never restart the underlying
|
||||
// process so we don't care or need to know about these.
|
||||
|
||||
self.terminal.flags.default_grapheme_cluster = config.grapheme_width_method == .unicode;
|
||||
|
||||
// Update the default palette. Note this will only apply to new colors drawn
|
||||
// since we decode all palette colors to RGB on usage.
|
||||
self.terminal.default_palette = config.palette;
|
||||
|
Reference in New Issue
Block a user