Skip to content
Merged
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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: ghostel-module-${{ matrix.platform }}
path: ghostel-module${{ matrix.suffix }}
path: |
ghostel-module${{ matrix.suffix }}
ghostel-module.version
test-native:
needs: build-native
Expand Down
10 changes: 10 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const std = @import("std");
const module_version = @import("src/version.zig").version;

const vendored_emacs_module_dir = "vendor";

Expand Down Expand Up @@ -60,6 +61,15 @@ pub fn build(b: *std.Build) void {
);
b.getInstallStep().dependOn(&copy_step.step);

// Sidecar version file sitting next to the binary. The elisp loader
// reads this before `module-load` to detect a stale module without
// mapping it into the process. Mirrors the path of the .so/.dylib
// produced above.
const version_wf = b.addWriteFiles();
const version_file = version_wf.add("ghostel-module.version", module_version ++ "\n");
const copy_version_step = b.addInstallFile(version_file, "../ghostel-module.version");
b.getInstallStep().dependOn(&copy_version_step.step);

// ----------------------------------------------------------------
// `zig build test` — pure-Zig unit tests for the decoder helpers.
//
Expand Down
276 changes: 227 additions & 49 deletions lisp/ghostel.el

Large diffs are not rendered by default.

34 changes: 31 additions & 3 deletions src/Renderer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ row: RowContent = .{},

font_info: ?FontInfo = null,

/// Bold text coloring configuration.
bold_config: BoldConfig = .none,

pub const BoldConfig = union(enum) {
none,
bright,
fixed: gt.ColorRgb,
};

const FontInfo = struct {
width: i64,
height: i64,
Expand Down Expand Up @@ -285,7 +294,7 @@ fn formatColor(color: gt.ColorRgb, buf: *[7]u8) []const u8 {
}

/// Read the style for the current cell from the render state.
fn readCellProps(cells: gt.RenderStateRowCells, key: CellPropKey) !?CellProps {
fn readCellProps(self: *Self, term: *Terminal, cells: gt.RenderStateRowCells, key: CellPropKey) !?CellProps {
var props: CellProps = .{};

props.fg = gt.rs_row_cells.get(gt.ColorRgb, cells, gt.RS_CELLS_DATA_FG_COLOR) catch |err| switch (err) {
Expand All @@ -310,6 +319,22 @@ fn readCellProps(cells: gt.RenderStateRowCells, key: CellPropKey) !?CellProps {
if (gs.underline_color.tag == gt.c.GHOSTTY_STYLE_COLOR_RGB) {
props.underline_color = gs.underline_color.value.rgb;
}

// Bold color handling (matches Ghostty 1.2.0+)
if (props.bold and self.bold_config != .none) {
if (gs.fg_color.tag == gt.c.GHOSTTY_STYLE_COLOR_PALETTE) {
const index = gs.fg_color.value.palette;
if (index < 8) {
const palette = try term.getColorPalette();
props.fg = palette[index + 8];
}
} else if (gs.fg_color.tag == gt.c.GHOSTTY_STYLE_COLOR_NONE) {
switch (self.bold_config) {
.fixed => |fixed_color| props.fg = fixed_color,
else => {},
}
}
}
}

props.hyperlink = key.hyperlink;
Expand Down Expand Up @@ -500,6 +525,7 @@ pub const RowContent = struct {
/// styled blanks are preserved.
pub fn build(
self: *RowContent,
term: *Terminal,
row: gt.RenderStateRowIterator,
row_cells: *gt.RenderStateRowCells,
adjustment_threshold: u32,
Expand Down Expand Up @@ -568,7 +594,7 @@ pub const RowContent = struct {
try self.runs.append(RowContent.allocator, .{
.start_char = self.char_len,
.end_char = self.char_len,
.props = try readCellProps(row_cells.*, prop_key),
.props = try readCellProps(&term.renderer, term, row_cells.*, prop_key),
});
current_prop_key = prop_key;
}
Expand Down Expand Up @@ -716,9 +742,11 @@ fn adjustGlyphs(self: *Self, env: emacs.Env, row_start: i64) void {
fn insertRow(
self: *Self,
env: emacs.Env,
term: *Terminal,
default_colors: *const BgFg,
) !void {
try self.row.build(
term,
self.row_iterator,
&self.row_cells,
if (self.font_info) |f| f.coverage else std.math.maxInt(u32),
Expand Down Expand Up @@ -853,7 +881,7 @@ pub fn render(self: *Self, env: emacs.Env, term: *Terminal, skip: usize, force_f
const dirty_row = dirty_full or try gt.rs_row.get(bool, self.row_iterator, gt.RS_ROW_DATA_DIRTY);
if (dirty_row) {
env.deleteRegion(env.point(), env.lineBeginningPosition2());
try self.insertRow(env, &default_colors);
try self.insertRow(env, term, &default_colors);
} else {
_ = env.forwardLine(1);
}
Expand Down
1 change: 1 addition & 0 deletions src/emacs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ const interned_symbols = [_][:0]const u8{
":underline",
":weight",
"bold",
"bright",
"char-before",
"cons",
"dash",
Expand Down
42 changes: 40 additions & 2 deletions src/module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ const pty = @import("pty.zig");

const c = emacs.c;

/// Module version — keep in sync with ghostel.el and build.zig.zon.
const version = "0.25.0";
/// Module version — see src/version.zig. Keep in sync with ghostel.el
/// and build.zig.zon.
const version = @import("version.zig").version;

// ---------------------------------------------------------------------------
// Module entry point
Expand Down Expand Up @@ -91,6 +92,13 @@ export fn emacs_module_init(runtime: *c.struct_emacs_runtime) callconv(.c) c_int
\\
\\(ghostel--set-default-colors TERM FG-HEX BG-HEX)
);
env.bindFunction("ghostel--set-bold-config", 2, 2, &fnSetBoldConfig,
\\Configure bold text coloring.
\\
\\CONFIG can be nil (none), 'bright, or a hex color string.
\\
\\(ghostel--set-bold-config TERM CONFIG)
);
env.bindFunction("ghostel--mode-enabled", 2, 2, &fnModeEnabled,
\\Return t if terminal DEC private MODE is enabled.
\\
Expand Down Expand Up @@ -1088,6 +1096,36 @@ fn fnSetDefaultColors(raw_env: ?*c.emacs_env, _: isize, args: [*c]c.emacs_value,
return env.t();
}

/// (ghostel--set-bold-config TERM CONFIG)
///
/// CONFIG can be nil (none), 'bright, or a hex color string.
fn fnSetBoldConfig(raw_env: ?*c.emacs_env, _: isize, args: [*c]c.emacs_value, _: ?*anyopaque) callconv(.c) c.emacs_value {
const env = emacs.Env.init(raw_env.?);
const term = env.getUserPtr(Terminal, args[0]) orelse return env.nil();
const val = args[1];

if (env.isNil(val)) {
term.renderer.bold_config = .none;
} else if (env.eq(val, emacs.sym.bright)) {
term.renderer.bold_config = .bright;
} else {
var hex_buf: [16]u8 = undefined;
const hex = env.extractString(val, &hex_buf) orelse {
env.signalError("invalid bold config value", .{});
return env.nil();
};

if (parseHexColor(hex)) |color| {
term.renderer.bold_config = .{ .fixed = color };
} else {
env.signalError("invalid bold color: %s", .{hex});
return env.nil();
}
}

return env.t();
}

/// (ghostel--debug-state TERM)
/// Returns a string with render state debug info.
///
Expand Down
3 changes: 3 additions & 0 deletions src/version.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/// Module version — single source of truth for src/module.zig and build.zig.
/// Keep in sync with `version` in build.zig.zon and `Version:` in lisp/ghostel.el.
pub const version = "0.25.0";
Loading
Loading