mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
basic charset mapping, support configuration, print tests
This commit is contained in:
@ -1632,3 +1632,11 @@ pub fn setActiveStatusDisplay(
|
|||||||
) !void {
|
) !void {
|
||||||
self.terminal.status_display = req;
|
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 Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const ansi = @import("ansi.zig");
|
const ansi = @import("ansi.zig");
|
||||||
|
const charsets = @import("charsets.zig");
|
||||||
const csi = @import("csi.zig");
|
const csi = @import("csi.zig");
|
||||||
const sgr = @import("sgr.zig");
|
const sgr = @import("sgr.zig");
|
||||||
const Selection = @import("Selection.zig");
|
const Selection = @import("Selection.zig");
|
||||||
@ -56,6 +57,9 @@ cols: usize,
|
|||||||
/// The current scrolling region.
|
/// The current scrolling region.
|
||||||
scrolling_region: ScrollingRegion,
|
scrolling_region: ScrollingRegion,
|
||||||
|
|
||||||
|
/// The charset state
|
||||||
|
charset: CharsetState = .{},
|
||||||
|
|
||||||
/// Modes - This isn't exhaustive, since some modes (i.e. cursor origin)
|
/// Modes - This isn't exhaustive, since some modes (i.e. cursor origin)
|
||||||
/// are applied to the cursor and others aren't boolean yes/no.
|
/// are applied to the cursor and others aren't boolean yes/no.
|
||||||
modes: packed struct {
|
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.
|
/// The event types that can be reported for mouse-related activities.
|
||||||
/// These are all mutually exclusive (hence in a single enum).
|
/// These are all mutually exclusive (hence in a single enum).
|
||||||
pub const MouseEvents = enum(u3) {
|
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 {
|
pub fn print(self: *Terminal, c: u21) !void {
|
||||||
const tracy = trace(@src());
|
const tracy = trace(@src());
|
||||||
defer tracy.end();
|
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(
|
const cell = self.screen.getCell(
|
||||||
self.screen.cursor.y,
|
self.screen.cursor.y,
|
||||||
self.screen.cursor.x,
|
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" {
|
test "Terminal: linefeed and carriage return" {
|
||||||
var t = try init(testing.allocator, 80, 80);
|
var t = try init(testing.allocator, 80, 80);
|
||||||
defer t.deinit(testing.allocator);
|
defer t.deinit(testing.allocator);
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
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.
|
/// The list of supported character sets and their associated tables.
|
||||||
pub const Charset = enum {
|
pub const Charset = enum {
|
||||||
|
utf8,
|
||||||
|
ascii,
|
||||||
british,
|
british,
|
||||||
dec_special,
|
dec_special,
|
||||||
|
|
||||||
@ -13,10 +23,21 @@ pub const Charset = enum {
|
|||||||
return switch (set) {
|
return switch (set) {
|
||||||
.british => &british,
|
.british => &british,
|
||||||
.dec_special => &dec_special,
|
.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
|
/// https://vt100.net/docs/vt220-rm/chapter2.html
|
||||||
const british = british: {
|
const british = british: {
|
||||||
var table = initTable();
|
var table = initTable();
|
||||||
@ -76,6 +97,9 @@ test {
|
|||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const info = @typeInfo(Charset).Enum;
|
const info = @typeInfo(Charset).Enum;
|
||||||
inline for (info.fields) |field| {
|
inline for (info.fields) |field| {
|
||||||
|
// utf8 has no table
|
||||||
|
if (@field(Charset, field.name) == .utf8) continue;
|
||||||
|
|
||||||
const table = @field(Charset, field.name).table();
|
const table = @field(Charset, field.name).table();
|
||||||
|
|
||||||
// Yes, I could use `max_u8` here, but I want to explicitly use a
|
// 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 color = @import("color.zig");
|
||||||
|
|
||||||
pub const Charset = charsets.Charset;
|
pub const Charset = charsets.Charset;
|
||||||
|
pub const CharsetSlot = charsets.Slots;
|
||||||
pub const Terminal = @import("Terminal.zig");
|
pub const Terminal = @import("Terminal.zig");
|
||||||
pub const Parser = @import("Parser.zig");
|
pub const Parser = @import("Parser.zig");
|
||||||
pub const Selection = @import("Selection.zig");
|
pub const Selection = @import("Selection.zig");
|
||||||
|
@ -2,6 +2,7 @@ const std = @import("std");
|
|||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const Parser = @import("Parser.zig");
|
const Parser = @import("Parser.zig");
|
||||||
const ansi = @import("ansi.zig");
|
const ansi = @import("ansi.zig");
|
||||||
|
const charsets = @import("charsets.zig");
|
||||||
const csi = @import("csi.zig");
|
const csi = @import("csi.zig");
|
||||||
const sgr = @import("sgr.zig");
|
const sgr = @import("sgr.zig");
|
||||||
const trace = @import("tracy").trace;
|
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(
|
fn escDispatch(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
action: Parser.Action.ESC,
|
action: Parser.Action.ESC,
|
||||||
) !void {
|
) !void {
|
||||||
switch (action.final) {
|
switch (action.final) {
|
||||||
// Charsets
|
// Charsets
|
||||||
'B' => {
|
'B' => try self.configureCharset(action.intermediates, .ascii),
|
||||||
// TODO: Charset support. Just ignore this for now because
|
'A' => try self.configureCharset(action.intermediates, .british),
|
||||||
// every application sets this and it makes our logs SO
|
'0' => try self.configureCharset(action.intermediates, .dec_special),
|
||||||
// noisy.
|
|
||||||
},
|
|
||||||
|
|
||||||
// DECSC - Save Cursor
|
// DECSC - Save Cursor
|
||||||
'7' => if (@hasDecl(T, "saveCursor")) switch (action.intermediates.len) {
|
'7' => if (@hasDecl(T, "saveCursor")) switch (action.intermediates.len) {
|
||||||
|
Reference in New Issue
Block a user