basic charset mapping, support configuration, print tests

This commit is contained in:
Mitchell Hashimoto
2022-08-27 09:42:05 -07:00
parent 75b63f3df3
commit 7626f194e9
5 changed files with 133 additions and 6 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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");

View File

@ -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) {