mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 00:36:07 +03:00
basic charset mapping, support configuration, print tests
This commit is contained in:
@ -1632,3 +1632,11 @@ pub fn setActiveStatusDisplay(
|
||||
) !void {
|
||||
self.terminal.status_display = req;
|
||||
}
|
||||
|
||||
pub fn configureCharset(
|
||||
self: *Window,
|
||||
slot: terminal.CharsetSlot,
|
||||
set: terminal.Charset,
|
||||
) !void {
|
||||
self.terminal.configureCharset(slot, set);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const ansi = @import("ansi.zig");
|
||||
const charsets = @import("charsets.zig");
|
||||
const csi = @import("csi.zig");
|
||||
const sgr = @import("sgr.zig");
|
||||
const Selection = @import("Selection.zig");
|
||||
@ -56,6 +57,9 @@ cols: usize,
|
||||
/// The current scrolling region.
|
||||
scrolling_region: ScrollingRegion,
|
||||
|
||||
/// The charset state
|
||||
charset: CharsetState = .{},
|
||||
|
||||
/// Modes - This isn't exhaustive, since some modes (i.e. cursor origin)
|
||||
/// are applied to the cursor and others aren't boolean yes/no.
|
||||
modes: packed struct {
|
||||
@ -79,6 +83,20 @@ modes: packed struct {
|
||||
}
|
||||
} = .{},
|
||||
|
||||
/// State required for all charset operations.
|
||||
const CharsetState = struct {
|
||||
/// The list of graphical charsets by slot
|
||||
charsets: CharsetArray = CharsetArray.initFill(charsets.Charset.utf8),
|
||||
|
||||
/// GL is the slot to use when using a 7-bit printable char (up to 127)
|
||||
/// GR used for 8-bit printable chars.
|
||||
gl: charsets.Slots = .G0,
|
||||
gr: charsets.Slots = .G2,
|
||||
|
||||
/// An array to map a charset slot to a lookup table.
|
||||
const CharsetArray = std.EnumArray(charsets.Slots, charsets.Charset);
|
||||
};
|
||||
|
||||
/// The event types that can be reported for mouse-related activities.
|
||||
/// These are all mutually exclusive (hence in a single enum).
|
||||
pub const MouseEvents = enum(u3) {
|
||||
@ -376,6 +394,11 @@ pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the charset into the given slot.
|
||||
pub fn configureCharset(self: *Terminal, slot: charsets.Slots, set: charsets.Charset) void {
|
||||
self.charset.charsets.set(slot, set);
|
||||
}
|
||||
|
||||
pub fn print(self: *Terminal, c: u21) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
@ -438,7 +461,21 @@ pub fn print(self: *Terminal, c: u21) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn printCell(self: *Terminal, c: u21) *Screen.Cell {
|
||||
fn printCell(self: *Terminal, unmapped_c: u21) *Screen.Cell {
|
||||
const c = c: {
|
||||
// TODO: non-utf8 handling, gr
|
||||
|
||||
const key = self.charset.gl;
|
||||
const set = self.charset.charsets.get(key);
|
||||
|
||||
// UTF-8 or ASCII is used as-is
|
||||
if (set == .utf8 or set == .ascii) break :c unmapped_c;
|
||||
|
||||
// Get our lookup table and map it
|
||||
const table = set.table();
|
||||
break :c @intCast(u21, table[@intCast(u8, unmapped_c)]);
|
||||
};
|
||||
|
||||
const cell = self.screen.getCell(
|
||||
self.screen.cursor.y,
|
||||
self.screen.cursor.x,
|
||||
@ -1245,6 +1282,30 @@ test "Terminal: print writes to bottom if scrolled" {
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: print charset" {
|
||||
var t = try init(testing.allocator, 80, 80);
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
// G1 should have no effect
|
||||
t.configureCharset(.G1, .dec_special);
|
||||
t.configureCharset(.G2, .dec_special);
|
||||
t.configureCharset(.G3, .dec_special);
|
||||
|
||||
// Basic grid writing
|
||||
try t.print('`');
|
||||
t.configureCharset(.G0, .utf8);
|
||||
try t.print('`');
|
||||
t.configureCharset(.G0, .ascii);
|
||||
try t.print('`');
|
||||
t.configureCharset(.G0, .dec_special);
|
||||
try t.print('`');
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("```◆", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: linefeed and carriage return" {
|
||||
var t = try init(testing.allocator, 80, 80);
|
||||
defer t.deinit(testing.allocator);
|
||||
|
@ -1,8 +1,18 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
/// The available charset slots for a terminal.
|
||||
pub const Slots = enum(u3) {
|
||||
G0 = 0,
|
||||
G1 = 1,
|
||||
G2 = 2,
|
||||
G3 = 3,
|
||||
};
|
||||
|
||||
/// The list of supported character sets and their associated tables.
|
||||
pub const Charset = enum {
|
||||
utf8,
|
||||
ascii,
|
||||
british,
|
||||
dec_special,
|
||||
|
||||
@ -13,10 +23,21 @@ pub const Charset = enum {
|
||||
return switch (set) {
|
||||
.british => &british,
|
||||
.dec_special => &dec_special,
|
||||
|
||||
// utf8 is not a table, callers should double-check if the
|
||||
// charset is utf8 and NOT use tables.
|
||||
.utf8 => unreachable,
|
||||
|
||||
// recommended that callers just map ascii directly but we can
|
||||
// support a table
|
||||
.ascii => &ascii,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Just a basic c => c ascii table
|
||||
const ascii = initTable();
|
||||
|
||||
/// https://vt100.net/docs/vt220-rm/chapter2.html
|
||||
const british = british: {
|
||||
var table = initTable();
|
||||
@ -76,6 +97,9 @@ test {
|
||||
const testing = std.testing;
|
||||
const info = @typeInfo(Charset).Enum;
|
||||
inline for (info.fields) |field| {
|
||||
// utf8 has no table
|
||||
if (@field(Charset, field.name) == .utf8) continue;
|
||||
|
||||
const table = @field(Charset, field.name).table();
|
||||
|
||||
// Yes, I could use `max_u8` here, but I want to explicitly use a
|
||||
|
@ -9,6 +9,7 @@ pub const point = @import("point.zig");
|
||||
pub const color = @import("color.zig");
|
||||
|
||||
pub const Charset = charsets.Charset;
|
||||
pub const CharsetSlot = charsets.Slots;
|
||||
pub const Terminal = @import("Terminal.zig");
|
||||
pub const Parser = @import("Parser.zig");
|
||||
pub const Selection = @import("Selection.zig");
|
||||
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const Parser = @import("Parser.zig");
|
||||
const ansi = @import("ansi.zig");
|
||||
const charsets = @import("charsets.zig");
|
||||
const csi = @import("csi.zig");
|
||||
const sgr = @import("sgr.zig");
|
||||
const trace = @import("tracy").trace;
|
||||
@ -408,17 +409,49 @@ pub fn Stream(comptime Handler: type) type {
|
||||
}
|
||||
}
|
||||
|
||||
fn configureCharset(
|
||||
self: Self,
|
||||
intermediates: []const u8,
|
||||
set: charsets.Charset,
|
||||
) !void {
|
||||
if (intermediates.len != 1) {
|
||||
log.warn("invalid charset intermediate: {any}", .{intermediates});
|
||||
return;
|
||||
}
|
||||
|
||||
const slot: charsets.Slots = switch (intermediates[0]) {
|
||||
// TODO: support slots '-', '.', '/'
|
||||
|
||||
'(' => .G0,
|
||||
')' => .G1,
|
||||
'*' => .G2,
|
||||
'+' => .G3,
|
||||
else => {
|
||||
log.warn("invalid charset intermediate: {any}", .{intermediates});
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
if (@hasDecl(T, "configureCharset")) {
|
||||
try self.handler.configureCharset(slot, set);
|
||||
return;
|
||||
}
|
||||
|
||||
log.warn("unimplemented configureCharset callback slot={} set={}", .{
|
||||
slot,
|
||||
set,
|
||||
});
|
||||
}
|
||||
|
||||
fn escDispatch(
|
||||
self: *Self,
|
||||
action: Parser.Action.ESC,
|
||||
) !void {
|
||||
switch (action.final) {
|
||||
// Charsets
|
||||
'B' => {
|
||||
// TODO: Charset support. Just ignore this for now because
|
||||
// every application sets this and it makes our logs SO
|
||||
// noisy.
|
||||
},
|
||||
'B' => try self.configureCharset(action.intermediates, .ascii),
|
||||
'A' => try self.configureCharset(action.intermediates, .british),
|
||||
'0' => try self.configureCharset(action.intermediates, .dec_special),
|
||||
|
||||
// DECSC - Save Cursor
|
||||
'7' => if (@hasDecl(T, "saveCursor")) switch (action.intermediates.len) {
|
||||
|
Reference in New Issue
Block a user