mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
terminal/new => terminal2 so we can figure out what depends on what
This commit is contained in:
@ -15,7 +15,7 @@ const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const ziglyph = @import("ziglyph");
|
||||
const cli = @import("../cli.zig");
|
||||
const terminal = @import("../terminal/main.zig");
|
||||
const terminalnew = @import("../terminal/new/main.zig");
|
||||
const terminalnew = @import("../terminal2/main.zig");
|
||||
|
||||
const Args = struct {
|
||||
mode: Mode = .noop,
|
||||
|
@ -309,6 +309,7 @@ test {
|
||||
_ = @import("segmented_pool.zig");
|
||||
_ = @import("inspector/main.zig");
|
||||
_ = @import("terminal/main.zig");
|
||||
_ = @import("terminal2/main.zig");
|
||||
_ = @import("terminfo/main.zig");
|
||||
_ = @import("simd/main.zig");
|
||||
_ = @import("unicode/main.zig");
|
||||
|
@ -25,7 +25,6 @@ pub const CSI = Parser.Action.CSI;
|
||||
pub const DCS = Parser.Action.DCS;
|
||||
pub const MouseShape = @import("mouse_shape.zig").MouseShape;
|
||||
pub const Terminal = @import("Terminal.zig");
|
||||
//pub const Terminal = new.Terminal;
|
||||
pub const Parser = @import("Parser.zig");
|
||||
pub const Selection = @import("Selection.zig");
|
||||
pub const Screen = @import("Screen.zig");
|
||||
@ -49,8 +48,8 @@ pub usingnamespace if (builtin.target.isWasm()) struct {
|
||||
pub usingnamespace @import("wasm.zig");
|
||||
} else struct {};
|
||||
|
||||
/// The new stuff. TODO: remove this before merge.
|
||||
pub const new = @import("new/main.zig");
|
||||
// TODO(paged-terminal) remove before merge
|
||||
pub const new = @import("../terminal2/main.zig");
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
|
@ -3,12 +3,12 @@ const Screen = @This();
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const ansi = @import("../ansi.zig");
|
||||
const charsets = @import("../charsets.zig");
|
||||
const kitty = @import("../kitty.zig");
|
||||
const sgr = @import("../sgr.zig");
|
||||
const unicode = @import("../../unicode/main.zig");
|
||||
const Selection = @import("../Selection.zig");
|
||||
const ansi = @import("ansi.zig");
|
||||
const charsets = @import("charsets.zig");
|
||||
const kitty = @import("kitty.zig");
|
||||
const sgr = @import("sgr.zig");
|
||||
const unicode = @import("../unicode/main.zig");
|
||||
//const Selection = @import("../Selection.zig");
|
||||
const PageList = @import("PageList.zig");
|
||||
const pagepkg = @import("page.zig");
|
||||
const point = @import("point.zig");
|
||||
@ -37,7 +37,8 @@ cursor: Cursor,
|
||||
saved_cursor: ?SavedCursor = null,
|
||||
|
||||
/// The selection for this screen (if any).
|
||||
selection: ?Selection = null,
|
||||
//selection: ?Selection = null,
|
||||
selection: ?void = null,
|
||||
|
||||
/// The charset state
|
||||
charset: CharsetState = .{},
|
||||
@ -826,18 +827,18 @@ pub fn manualStyleUpdate(self: *Screen) !void {
|
||||
/// Returns the raw text associated with a selection. This will unwrap
|
||||
/// soft-wrapped edges. The returned slice is owned by the caller and allocated
|
||||
/// using alloc, not the allocator associated with the screen (unless they match).
|
||||
pub fn selectionString(
|
||||
self: *Screen,
|
||||
alloc: Allocator,
|
||||
sel: Selection,
|
||||
trim: bool,
|
||||
) ![:0]const u8 {
|
||||
_ = self;
|
||||
_ = alloc;
|
||||
_ = sel;
|
||||
_ = trim;
|
||||
@panic("TODO");
|
||||
}
|
||||
// pub fn selectionString(
|
||||
// self: *Screen,
|
||||
// alloc: Allocator,
|
||||
// sel: Selection,
|
||||
// trim: bool,
|
||||
// ) ![:0]const u8 {
|
||||
// _ = self;
|
||||
// _ = alloc;
|
||||
// _ = sel;
|
||||
// _ = trim;
|
||||
// @panic("TODO");
|
||||
// }
|
||||
|
||||
/// Dump the screen to a string. The writer given should be buffered;
|
||||
/// this function does not attempt to efficiently write and generally writes
|
231
src/terminal2/Tabstops.zig
Normal file
231
src/terminal2/Tabstops.zig
Normal file
@ -0,0 +1,231 @@
|
||||
//! Keep track of the location of tabstops.
|
||||
//!
|
||||
//! This is implemented as a bit set. There is a preallocation segment that
|
||||
//! is used for almost all screen sizes. Then there is a dynamically allocated
|
||||
//! segment if the screen is larger than the preallocation amount.
|
||||
//!
|
||||
//! In reality, tabstops don't need to be the most performant in any metric.
|
||||
//! This implementation tries to balance denser memory usage (by using a bitset)
|
||||
//! and minimizing unnecessary allocations.
|
||||
const Tabstops = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const testing = std.testing;
|
||||
const assert = std.debug.assert;
|
||||
const fastmem = @import("../fastmem.zig");
|
||||
|
||||
/// Unit is the type we use per tabstop unit (see file docs).
|
||||
const Unit = u8;
|
||||
const unit_bits = @bitSizeOf(Unit);
|
||||
|
||||
/// The number of columns we preallocate for. This is kind of high which
|
||||
/// costs us some memory, but this is more columns than my 6k monitor at
|
||||
/// 12-point font size, so this should prevent allocation in almost all
|
||||
/// real world scenarios for the price of wasting at most
|
||||
/// (columns / sizeOf(Unit)) bytes.
|
||||
const prealloc_columns = 512;
|
||||
|
||||
/// The number of entries we need for our preallocation.
|
||||
const prealloc_count = prealloc_columns / unit_bits;
|
||||
|
||||
/// We precompute all the possible masks since we never use a huge bit size.
|
||||
const masks = blk: {
|
||||
var res: [unit_bits]Unit = undefined;
|
||||
for (res, 0..) |_, i| {
|
||||
res[i] = @shlExact(@as(Unit, 1), @as(u3, @intCast(i)));
|
||||
}
|
||||
|
||||
break :blk res;
|
||||
};
|
||||
|
||||
/// The number of columns this tabstop is set to manage. Use resize()
|
||||
/// to change this number.
|
||||
cols: usize = 0,
|
||||
|
||||
/// Preallocated tab stops.
|
||||
prealloc_stops: [prealloc_count]Unit = [1]Unit{0} ** prealloc_count,
|
||||
|
||||
/// Dynamically expanded stops above prealloc stops.
|
||||
dynamic_stops: []Unit = &[0]Unit{},
|
||||
|
||||
/// Returns the entry in the stops array that would contain this column.
|
||||
inline fn entry(col: usize) usize {
|
||||
return col / unit_bits;
|
||||
}
|
||||
|
||||
inline fn index(col: usize) usize {
|
||||
return @mod(col, unit_bits);
|
||||
}
|
||||
|
||||
pub fn init(alloc: Allocator, cols: usize, interval: usize) !Tabstops {
|
||||
var res: Tabstops = .{};
|
||||
try res.resize(alloc, cols);
|
||||
res.reset(interval);
|
||||
return res;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Tabstops, alloc: Allocator) void {
|
||||
if (self.dynamic_stops.len > 0) alloc.free(self.dynamic_stops);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Set the tabstop at a certain column. The columns are 0-indexed.
|
||||
pub fn set(self: *Tabstops, col: usize) void {
|
||||
const i = entry(col);
|
||||
const idx = index(col);
|
||||
if (i < prealloc_count) {
|
||||
self.prealloc_stops[i] |= masks[idx];
|
||||
return;
|
||||
}
|
||||
|
||||
const dynamic_i = i - prealloc_count;
|
||||
assert(dynamic_i < self.dynamic_stops.len);
|
||||
self.dynamic_stops[dynamic_i] |= masks[idx];
|
||||
}
|
||||
|
||||
/// Unset the tabstop at a certain column. The columns are 0-indexed.
|
||||
pub fn unset(self: *Tabstops, col: usize) void {
|
||||
const i = entry(col);
|
||||
const idx = index(col);
|
||||
if (i < prealloc_count) {
|
||||
self.prealloc_stops[i] ^= masks[idx];
|
||||
return;
|
||||
}
|
||||
|
||||
const dynamic_i = i - prealloc_count;
|
||||
assert(dynamic_i < self.dynamic_stops.len);
|
||||
self.dynamic_stops[dynamic_i] ^= masks[idx];
|
||||
}
|
||||
|
||||
/// Get the value of a tabstop at a specific column. The columns are 0-indexed.
|
||||
pub fn get(self: Tabstops, col: usize) bool {
|
||||
const i = entry(col);
|
||||
const idx = index(col);
|
||||
const mask = masks[idx];
|
||||
const unit = if (i < prealloc_count)
|
||||
self.prealloc_stops[i]
|
||||
else unit: {
|
||||
const dynamic_i = i - prealloc_count;
|
||||
assert(dynamic_i < self.dynamic_stops.len);
|
||||
break :unit self.dynamic_stops[dynamic_i];
|
||||
};
|
||||
|
||||
return unit & mask == mask;
|
||||
}
|
||||
|
||||
/// Resize this to support up to cols columns.
|
||||
// TODO: needs interval to set new tabstops
|
||||
pub fn resize(self: *Tabstops, alloc: Allocator, cols: usize) !void {
|
||||
// Set our new value
|
||||
self.cols = cols;
|
||||
|
||||
// Do nothing if it fits.
|
||||
if (cols <= prealloc_columns) return;
|
||||
|
||||
// What we need in the dynamic size
|
||||
const size = cols - prealloc_columns;
|
||||
if (size < self.dynamic_stops.len) return;
|
||||
|
||||
// Note: we can probably try to realloc here but I'm not sure it matters.
|
||||
const new = try alloc.alloc(Unit, size);
|
||||
if (self.dynamic_stops.len > 0) {
|
||||
fastmem.copy(Unit, new, self.dynamic_stops);
|
||||
alloc.free(self.dynamic_stops);
|
||||
}
|
||||
|
||||
self.dynamic_stops = new;
|
||||
}
|
||||
|
||||
/// Return the maximum number of columns this can support currently.
|
||||
pub fn capacity(self: Tabstops) usize {
|
||||
return (prealloc_count + self.dynamic_stops.len) * unit_bits;
|
||||
}
|
||||
|
||||
/// Unset all tabstops and then reset the initial tabstops to the given
|
||||
/// interval. An interval of 0 sets no tabstops.
|
||||
pub fn reset(self: *Tabstops, interval: usize) void {
|
||||
@memset(&self.prealloc_stops, 0);
|
||||
@memset(self.dynamic_stops, 0);
|
||||
|
||||
if (interval > 0) {
|
||||
var i: usize = interval;
|
||||
while (i < self.cols - 1) : (i += interval) {
|
||||
self.set(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "Tabstops: basic" {
|
||||
var t: Tabstops = .{};
|
||||
defer t.deinit(testing.allocator);
|
||||
try testing.expectEqual(@as(usize, 0), entry(4));
|
||||
try testing.expectEqual(@as(usize, 1), entry(8));
|
||||
try testing.expectEqual(@as(usize, 0), index(0));
|
||||
try testing.expectEqual(@as(usize, 1), index(1));
|
||||
try testing.expectEqual(@as(usize, 1), index(9));
|
||||
|
||||
try testing.expectEqual(@as(Unit, 0b00001000), masks[3]);
|
||||
try testing.expectEqual(@as(Unit, 0b00010000), masks[4]);
|
||||
|
||||
try testing.expect(!t.get(4));
|
||||
t.set(4);
|
||||
try testing.expect(t.get(4));
|
||||
try testing.expect(!t.get(3));
|
||||
|
||||
t.reset(0);
|
||||
try testing.expect(!t.get(4));
|
||||
|
||||
t.set(4);
|
||||
try testing.expect(t.get(4));
|
||||
t.unset(4);
|
||||
try testing.expect(!t.get(4));
|
||||
}
|
||||
|
||||
test "Tabstops: dynamic allocations" {
|
||||
var t: Tabstops = .{};
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
// Grow the capacity by 2.
|
||||
const cap = t.capacity();
|
||||
try t.resize(testing.allocator, cap * 2);
|
||||
|
||||
// Set something that was out of range of the first
|
||||
t.set(cap + 5);
|
||||
try testing.expect(t.get(cap + 5));
|
||||
try testing.expect(!t.get(cap + 4));
|
||||
|
||||
// Prealloc still works
|
||||
try testing.expect(!t.get(5));
|
||||
}
|
||||
|
||||
test "Tabstops: interval" {
|
||||
var t: Tabstops = try init(testing.allocator, 80, 4);
|
||||
defer t.deinit(testing.allocator);
|
||||
try testing.expect(!t.get(0));
|
||||
try testing.expect(t.get(4));
|
||||
try testing.expect(!t.get(5));
|
||||
try testing.expect(t.get(8));
|
||||
}
|
||||
|
||||
test "Tabstops: count on 80" {
|
||||
// https://superuser.com/questions/710019/why-there-are-11-tabstops-on-a-80-column-console
|
||||
|
||||
var t: Tabstops = try init(testing.allocator, 80, 8);
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
// Count the tabstops
|
||||
const count: usize = count: {
|
||||
var v: usize = 0;
|
||||
var i: usize = 0;
|
||||
while (i < 80) : (i += 1) {
|
||||
if (t.get(i)) {
|
||||
v += 1;
|
||||
}
|
||||
}
|
||||
|
||||
break :count v;
|
||||
};
|
||||
|
||||
try testing.expectEqual(@as(usize, 9), count);
|
||||
}
|
@ -12,17 +12,17 @@ const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const unicode = @import("../../unicode/main.zig");
|
||||
const unicode = @import("../unicode/main.zig");
|
||||
|
||||
const ansi = @import("../ansi.zig");
|
||||
const modes = @import("../modes.zig");
|
||||
const charsets = @import("../charsets.zig");
|
||||
const csi = @import("../csi.zig");
|
||||
const kitty = @import("../kitty.zig");
|
||||
const sgr = @import("../sgr.zig");
|
||||
const Tabstops = @import("../Tabstops.zig");
|
||||
const color = @import("../color.zig");
|
||||
const mouse_shape = @import("../mouse_shape.zig");
|
||||
const ansi = @import("ansi.zig");
|
||||
const modes = @import("modes.zig");
|
||||
const charsets = @import("charsets.zig");
|
||||
const csi = @import("csi.zig");
|
||||
const kitty = @import("kitty.zig");
|
||||
const sgr = @import("sgr.zig");
|
||||
const Tabstops = @import("Tabstops.zig");
|
||||
const color = @import("color.zig");
|
||||
const mouse_shape = @import("mouse_shape.zig");
|
||||
|
||||
const size = @import("size.zig");
|
||||
const pagepkg = @import("page.zig");
|
114
src/terminal2/ansi.zig
Normal file
114
src/terminal2/ansi.zig
Normal file
@ -0,0 +1,114 @@
|
||||
/// C0 (7-bit) control characters from ANSI.
|
||||
///
|
||||
/// This is not complete, control characters are only added to this
|
||||
/// as the terminal emulator handles them.
|
||||
pub const C0 = enum(u7) {
|
||||
/// Null
|
||||
NUL = 0x00,
|
||||
/// Start of heading
|
||||
SOH = 0x01,
|
||||
/// Start of text
|
||||
STX = 0x02,
|
||||
/// Enquiry
|
||||
ENQ = 0x05,
|
||||
/// Bell
|
||||
BEL = 0x07,
|
||||
/// Backspace
|
||||
BS = 0x08,
|
||||
// Horizontal tab
|
||||
HT = 0x09,
|
||||
/// Line feed
|
||||
LF = 0x0A,
|
||||
/// Vertical Tab
|
||||
VT = 0x0B,
|
||||
/// Form feed
|
||||
FF = 0x0C,
|
||||
/// Carriage return
|
||||
CR = 0x0D,
|
||||
/// Shift out
|
||||
SO = 0x0E,
|
||||
/// Shift in
|
||||
SI = 0x0F,
|
||||
|
||||
// Non-exhaustive so that @intToEnum never fails since the inputs are
|
||||
// user-generated.
|
||||
_,
|
||||
};
|
||||
|
||||
/// The SGR rendition aspects that can be set, sometimes known as attributes.
|
||||
/// The value corresponds to the parameter value for the SGR command (ESC [ m).
|
||||
pub const RenditionAspect = enum(u16) {
|
||||
default = 0,
|
||||
bold = 1,
|
||||
default_fg = 39,
|
||||
default_bg = 49,
|
||||
|
||||
// Non-exhaustive so that @intToEnum never fails since the inputs are
|
||||
// user-generated.
|
||||
_,
|
||||
};
|
||||
|
||||
/// The device attribute request type (ESC [ c).
|
||||
pub const DeviceAttributeReq = enum {
|
||||
primary, // Blank
|
||||
secondary, // >
|
||||
tertiary, // =
|
||||
};
|
||||
|
||||
/// Possible cursor styles (ESC [ q)
|
||||
pub const CursorStyle = enum(u16) {
|
||||
default = 0,
|
||||
blinking_block = 1,
|
||||
steady_block = 2,
|
||||
blinking_underline = 3,
|
||||
steady_underline = 4,
|
||||
blinking_bar = 5,
|
||||
steady_bar = 6,
|
||||
|
||||
// Non-exhaustive so that @intToEnum never fails for unsupported modes.
|
||||
_,
|
||||
|
||||
/// True if the cursor should blink.
|
||||
pub fn blinking(self: CursorStyle) bool {
|
||||
return switch (self) {
|
||||
.blinking_block, .blinking_underline, .blinking_bar => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// The status line type for DECSSDT.
|
||||
pub const StatusLineType = enum(u16) {
|
||||
none = 0,
|
||||
indicator = 1,
|
||||
host_writable = 2,
|
||||
|
||||
// Non-exhaustive so that @intToEnum never fails for unsupported values.
|
||||
_,
|
||||
};
|
||||
|
||||
/// The display to target for status updates (DECSASD).
|
||||
pub const StatusDisplay = enum(u16) {
|
||||
main = 0,
|
||||
status_line = 1,
|
||||
|
||||
// Non-exhaustive so that @intToEnum never fails for unsupported values.
|
||||
_,
|
||||
};
|
||||
|
||||
/// The possible modify key formats to ESC[>{a};{b}m
|
||||
/// Note: this is not complete, we should add more as we support more
|
||||
pub const ModifyKeyFormat = union(enum) {
|
||||
legacy: void,
|
||||
cursor_keys: void,
|
||||
function_keys: void,
|
||||
other_keys: enum { none, numeric_except, numeric },
|
||||
};
|
||||
|
||||
/// The protection modes that can be set for the terminal. See DECSCA and
|
||||
/// ESC V, W.
|
||||
pub const ProtectedMode = enum {
|
||||
off,
|
||||
iso, // ESC V, W
|
||||
dec, // CSI Ps " q
|
||||
};
|
114
src/terminal2/charsets.zig
Normal file
114
src/terminal2/charsets.zig
Normal file
@ -0,0 +1,114 @@
|
||||
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 name of the active slots.
|
||||
pub const ActiveSlot = enum { GL, GR };
|
||||
|
||||
/// The list of supported character sets and their associated tables.
|
||||
pub const Charset = enum {
|
||||
utf8,
|
||||
ascii,
|
||||
british,
|
||||
dec_special,
|
||||
|
||||
/// The table for the given charset. This returns a pointer to a
|
||||
/// slice that is guaranteed to be 255 chars that can be used to map
|
||||
/// ASCII to the given charset.
|
||||
pub fn table(set: Charset) []const u16 {
|
||||
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();
|
||||
table[0x23] = 0x00a3;
|
||||
break :british table;
|
||||
};
|
||||
|
||||
/// https://en.wikipedia.org/wiki/DEC_Special_Graphics
|
||||
const dec_special = tech: {
|
||||
var table = initTable();
|
||||
table[0x60] = 0x25C6;
|
||||
table[0x61] = 0x2592;
|
||||
table[0x62] = 0x2409;
|
||||
table[0x63] = 0x240C;
|
||||
table[0x64] = 0x240D;
|
||||
table[0x65] = 0x240A;
|
||||
table[0x66] = 0x00B0;
|
||||
table[0x67] = 0x00B1;
|
||||
table[0x68] = 0x2424;
|
||||
table[0x69] = 0x240B;
|
||||
table[0x6a] = 0x2518;
|
||||
table[0x6b] = 0x2510;
|
||||
table[0x6c] = 0x250C;
|
||||
table[0x6d] = 0x2514;
|
||||
table[0x6e] = 0x253C;
|
||||
table[0x6f] = 0x23BA;
|
||||
table[0x70] = 0x23BB;
|
||||
table[0x71] = 0x2500;
|
||||
table[0x72] = 0x23BC;
|
||||
table[0x73] = 0x23BD;
|
||||
table[0x74] = 0x251C;
|
||||
table[0x75] = 0x2524;
|
||||
table[0x76] = 0x2534;
|
||||
table[0x77] = 0x252C;
|
||||
table[0x78] = 0x2502;
|
||||
table[0x79] = 0x2264;
|
||||
table[0x7a] = 0x2265;
|
||||
table[0x7b] = 0x03C0;
|
||||
table[0x7c] = 0x2260;
|
||||
table[0x7d] = 0x00A3;
|
||||
table[0x7e] = 0x00B7;
|
||||
break :tech table;
|
||||
};
|
||||
|
||||
/// Our table length is 256 so we can contain all ASCII chars.
|
||||
const table_len = std.math.maxInt(u8) + 1;
|
||||
|
||||
/// Creates a table that maps ASCII to ASCII as a getting started point.
|
||||
fn initTable() [table_len]u16 {
|
||||
var result: [table_len]u16 = undefined;
|
||||
var i: usize = 0;
|
||||
while (i < table_len) : (i += 1) result[i] = @intCast(i);
|
||||
assert(i == table_len);
|
||||
return result;
|
||||
}
|
||||
|
||||
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 `table_len` here, but I want to explicitly use a
|
||||
// hardcoded constant so that if there are miscompilations or a comptime
|
||||
// issue, we catch it.
|
||||
try testing.expectEqual(@as(usize, 256), table.len);
|
||||
}
|
||||
}
|
339
src/terminal2/color.zig
Normal file
339
src/terminal2/color.zig
Normal file
@ -0,0 +1,339 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const x11_color = @import("x11_color.zig");
|
||||
|
||||
/// The default palette.
|
||||
pub const default: Palette = default: {
|
||||
var result: Palette = undefined;
|
||||
|
||||
// Named values
|
||||
var i: u8 = 0;
|
||||
while (i < 16) : (i += 1) {
|
||||
result[i] = Name.default(@enumFromInt(i)) catch unreachable;
|
||||
}
|
||||
|
||||
// Cube
|
||||
assert(i == 16);
|
||||
var r: u8 = 0;
|
||||
while (r < 6) : (r += 1) {
|
||||
var g: u8 = 0;
|
||||
while (g < 6) : (g += 1) {
|
||||
var b: u8 = 0;
|
||||
while (b < 6) : (b += 1) {
|
||||
result[i] = .{
|
||||
.r = if (r == 0) 0 else (r * 40 + 55),
|
||||
.g = if (g == 0) 0 else (g * 40 + 55),
|
||||
.b = if (b == 0) 0 else (b * 40 + 55),
|
||||
};
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grey ramp
|
||||
assert(i == 232);
|
||||
assert(@TypeOf(i) == u8);
|
||||
while (i > 0) : (i +%= 1) {
|
||||
const value = ((i - 232) * 10) + 8;
|
||||
result[i] = .{ .r = value, .g = value, .b = value };
|
||||
}
|
||||
|
||||
break :default result;
|
||||
};
|
||||
|
||||
/// Palette is the 256 color palette.
|
||||
pub const Palette = [256]RGB;
|
||||
|
||||
/// Color names in the standard 8 or 16 color palette.
|
||||
pub const Name = enum(u8) {
|
||||
black = 0,
|
||||
red = 1,
|
||||
green = 2,
|
||||
yellow = 3,
|
||||
blue = 4,
|
||||
magenta = 5,
|
||||
cyan = 6,
|
||||
white = 7,
|
||||
|
||||
bright_black = 8,
|
||||
bright_red = 9,
|
||||
bright_green = 10,
|
||||
bright_yellow = 11,
|
||||
bright_blue = 12,
|
||||
bright_magenta = 13,
|
||||
bright_cyan = 14,
|
||||
bright_white = 15,
|
||||
|
||||
// Remainders are valid unnamed values in the 256 color palette.
|
||||
_,
|
||||
|
||||
/// Default colors for tagged values.
|
||||
pub fn default(self: Name) !RGB {
|
||||
return switch (self) {
|
||||
.black => RGB{ .r = 0x1D, .g = 0x1F, .b = 0x21 },
|
||||
.red => RGB{ .r = 0xCC, .g = 0x66, .b = 0x66 },
|
||||
.green => RGB{ .r = 0xB5, .g = 0xBD, .b = 0x68 },
|
||||
.yellow => RGB{ .r = 0xF0, .g = 0xC6, .b = 0x74 },
|
||||
.blue => RGB{ .r = 0x81, .g = 0xA2, .b = 0xBE },
|
||||
.magenta => RGB{ .r = 0xB2, .g = 0x94, .b = 0xBB },
|
||||
.cyan => RGB{ .r = 0x8A, .g = 0xBE, .b = 0xB7 },
|
||||
.white => RGB{ .r = 0xC5, .g = 0xC8, .b = 0xC6 },
|
||||
|
||||
.bright_black => RGB{ .r = 0x66, .g = 0x66, .b = 0x66 },
|
||||
.bright_red => RGB{ .r = 0xD5, .g = 0x4E, .b = 0x53 },
|
||||
.bright_green => RGB{ .r = 0xB9, .g = 0xCA, .b = 0x4A },
|
||||
.bright_yellow => RGB{ .r = 0xE7, .g = 0xC5, .b = 0x47 },
|
||||
.bright_blue => RGB{ .r = 0x7A, .g = 0xA6, .b = 0xDA },
|
||||
.bright_magenta => RGB{ .r = 0xC3, .g = 0x97, .b = 0xD8 },
|
||||
.bright_cyan => RGB{ .r = 0x70, .g = 0xC0, .b = 0xB1 },
|
||||
.bright_white => RGB{ .r = 0xEA, .g = 0xEA, .b = 0xEA },
|
||||
|
||||
else => error.NoDefaultValue,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// RGB
|
||||
pub const RGB = struct {
|
||||
r: u8 = 0,
|
||||
g: u8 = 0,
|
||||
b: u8 = 0,
|
||||
|
||||
pub fn eql(self: RGB, other: RGB) bool {
|
||||
return self.r == other.r and self.g == other.g and self.b == other.b;
|
||||
}
|
||||
|
||||
/// Calculates the contrast ratio between two colors. The contrast
|
||||
/// ration is a value between 1 and 21 where 1 is the lowest contrast
|
||||
/// and 21 is the highest contrast.
|
||||
///
|
||||
/// https://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
||||
pub fn contrast(self: RGB, other: RGB) f64 {
|
||||
// pair[0] = lighter, pair[1] = darker
|
||||
const pair: [2]f64 = pair: {
|
||||
const self_lum = self.luminance();
|
||||
const other_lum = other.luminance();
|
||||
if (self_lum > other_lum) break :pair .{ self_lum, other_lum };
|
||||
break :pair .{ other_lum, self_lum };
|
||||
};
|
||||
|
||||
return (pair[0] + 0.05) / (pair[1] + 0.05);
|
||||
}
|
||||
|
||||
/// Calculates luminance based on the W3C formula. This returns a
|
||||
/// normalized value between 0 and 1 where 0 is black and 1 is white.
|
||||
///
|
||||
/// https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
pub fn luminance(self: RGB) f64 {
|
||||
const r_lum = componentLuminance(self.r);
|
||||
const g_lum = componentLuminance(self.g);
|
||||
const b_lum = componentLuminance(self.b);
|
||||
return 0.2126 * r_lum + 0.7152 * g_lum + 0.0722 * b_lum;
|
||||
}
|
||||
|
||||
/// Calculates single-component luminance based on the W3C formula.
|
||||
///
|
||||
/// Expects sRGB color space which at the time of writing we don't
|
||||
/// generally use but it's a good enough approximation until we fix that.
|
||||
/// https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
fn componentLuminance(c: u8) f64 {
|
||||
const c_f64: f64 = @floatFromInt(c);
|
||||
const normalized: f64 = c_f64 / 255;
|
||||
if (normalized <= 0.03928) return normalized / 12.92;
|
||||
return std.math.pow(f64, (normalized + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
/// Calculates "perceived luminance" which is better for determining
|
||||
/// light vs dark.
|
||||
///
|
||||
/// Source: https://www.w3.org/TR/AERT/#color-contrast
|
||||
pub fn perceivedLuminance(self: RGB) f64 {
|
||||
const r_f64: f64 = @floatFromInt(self.r);
|
||||
const g_f64: f64 = @floatFromInt(self.g);
|
||||
const b_f64: f64 = @floatFromInt(self.b);
|
||||
return 0.299 * (r_f64 / 255) + 0.587 * (g_f64 / 255) + 0.114 * (b_f64 / 255);
|
||||
}
|
||||
|
||||
test "size" {
|
||||
try std.testing.expectEqual(@as(usize, 24), @bitSizeOf(RGB));
|
||||
try std.testing.expectEqual(@as(usize, 3), @sizeOf(RGB));
|
||||
}
|
||||
|
||||
/// Parse a color from a floating point intensity value.
|
||||
///
|
||||
/// The value should be between 0.0 and 1.0, inclusive.
|
||||
fn fromIntensity(value: []const u8) !u8 {
|
||||
const i = std.fmt.parseFloat(f64, value) catch return error.InvalidFormat;
|
||||
if (i < 0.0 or i > 1.0) {
|
||||
return error.InvalidFormat;
|
||||
}
|
||||
|
||||
return @intFromFloat(i * std.math.maxInt(u8));
|
||||
}
|
||||
|
||||
/// Parse a color from a string of hexadecimal digits
|
||||
///
|
||||
/// The string can contain 1, 2, 3, or 4 characters and represents the color
|
||||
/// value scaled in 4, 8, 12, or 16 bits, respectively.
|
||||
fn fromHex(value: []const u8) !u8 {
|
||||
if (value.len == 0 or value.len > 4) {
|
||||
return error.InvalidFormat;
|
||||
}
|
||||
|
||||
const color = std.fmt.parseUnsigned(u16, value, 16) catch return error.InvalidFormat;
|
||||
const divisor: usize = switch (value.len) {
|
||||
1 => std.math.maxInt(u4),
|
||||
2 => std.math.maxInt(u8),
|
||||
3 => std.math.maxInt(u12),
|
||||
4 => std.math.maxInt(u16),
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
return @intCast(@as(usize, color) * std.math.maxInt(u8) / divisor);
|
||||
}
|
||||
|
||||
/// Parse a color specification.
|
||||
///
|
||||
/// Any of the following forms are accepted:
|
||||
///
|
||||
/// 1. rgb:<red>/<green>/<blue>
|
||||
///
|
||||
/// <red>, <green>, <blue> := h | hh | hhh | hhhh
|
||||
///
|
||||
/// where `h` is a single hexadecimal digit.
|
||||
///
|
||||
/// 2. rgbi:<red>/<green>/<blue>
|
||||
///
|
||||
/// where <red>, <green>, and <blue> are floating point values between
|
||||
/// 0.0 and 1.0 (inclusive).
|
||||
///
|
||||
/// 3. #hhhhhh
|
||||
///
|
||||
/// where `h` is a single hexadecimal digit.
|
||||
pub fn parse(value: []const u8) !RGB {
|
||||
if (value.len == 0) {
|
||||
return error.InvalidFormat;
|
||||
}
|
||||
|
||||
if (value[0] == '#') {
|
||||
if (value.len != 7) {
|
||||
return error.InvalidFormat;
|
||||
}
|
||||
|
||||
return RGB{
|
||||
.r = try RGB.fromHex(value[1..3]),
|
||||
.g = try RGB.fromHex(value[3..5]),
|
||||
.b = try RGB.fromHex(value[5..7]),
|
||||
};
|
||||
}
|
||||
|
||||
// Check for X11 named colors. We allow whitespace around the edges
|
||||
// of the color because Kitty allows whitespace. This is not part of
|
||||
// any spec I could find.
|
||||
if (x11_color.map.get(std.mem.trim(u8, value, " "))) |rgb| return rgb;
|
||||
|
||||
if (value.len < "rgb:a/a/a".len or !std.mem.eql(u8, value[0..3], "rgb")) {
|
||||
return error.InvalidFormat;
|
||||
}
|
||||
|
||||
var i: usize = 3;
|
||||
|
||||
const use_intensity = if (value[i] == 'i') blk: {
|
||||
i += 1;
|
||||
break :blk true;
|
||||
} else false;
|
||||
|
||||
if (value[i] != ':') {
|
||||
return error.InvalidFormat;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
|
||||
const r = r: {
|
||||
const slice = if (std.mem.indexOfScalarPos(u8, value, i, '/')) |end|
|
||||
value[i..end]
|
||||
else
|
||||
return error.InvalidFormat;
|
||||
|
||||
i += slice.len + 1;
|
||||
|
||||
break :r if (use_intensity)
|
||||
try RGB.fromIntensity(slice)
|
||||
else
|
||||
try RGB.fromHex(slice);
|
||||
};
|
||||
|
||||
const g = g: {
|
||||
const slice = if (std.mem.indexOfScalarPos(u8, value, i, '/')) |end|
|
||||
value[i..end]
|
||||
else
|
||||
return error.InvalidFormat;
|
||||
|
||||
i += slice.len + 1;
|
||||
|
||||
break :g if (use_intensity)
|
||||
try RGB.fromIntensity(slice)
|
||||
else
|
||||
try RGB.fromHex(slice);
|
||||
};
|
||||
|
||||
const b = if (use_intensity)
|
||||
try RGB.fromIntensity(value[i..])
|
||||
else
|
||||
try RGB.fromHex(value[i..]);
|
||||
|
||||
return RGB{
|
||||
.r = r,
|
||||
.g = g,
|
||||
.b = b,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test "palette: default" {
|
||||
const testing = std.testing;
|
||||
|
||||
// Safety check
|
||||
var i: u8 = 0;
|
||||
while (i < 16) : (i += 1) {
|
||||
try testing.expectEqual(Name.default(@as(Name, @enumFromInt(i))), default[i]);
|
||||
}
|
||||
}
|
||||
|
||||
test "RGB.parse" {
|
||||
const testing = std.testing;
|
||||
|
||||
try testing.expectEqual(RGB{ .r = 255, .g = 0, .b = 0 }, try RGB.parse("rgbi:1.0/0/0"));
|
||||
try testing.expectEqual(RGB{ .r = 127, .g = 160, .b = 0 }, try RGB.parse("rgb:7f/a0a0/0"));
|
||||
try testing.expectEqual(RGB{ .r = 255, .g = 255, .b = 255 }, try RGB.parse("rgb:f/ff/fff"));
|
||||
try testing.expectEqual(RGB{ .r = 255, .g = 255, .b = 255 }, try RGB.parse("#ffffff"));
|
||||
try testing.expectEqual(RGB{ .r = 255, .g = 0, .b = 16 }, try RGB.parse("#ff0010"));
|
||||
|
||||
try testing.expectEqual(RGB{ .r = 0, .g = 0, .b = 0 }, try RGB.parse("black"));
|
||||
try testing.expectEqual(RGB{ .r = 255, .g = 0, .b = 0 }, try RGB.parse("red"));
|
||||
try testing.expectEqual(RGB{ .r = 0, .g = 255, .b = 0 }, try RGB.parse("green"));
|
||||
try testing.expectEqual(RGB{ .r = 0, .g = 0, .b = 255 }, try RGB.parse("blue"));
|
||||
try testing.expectEqual(RGB{ .r = 255, .g = 255, .b = 255 }, try RGB.parse("white"));
|
||||
|
||||
try testing.expectEqual(RGB{ .r = 124, .g = 252, .b = 0 }, try RGB.parse("LawnGreen"));
|
||||
try testing.expectEqual(RGB{ .r = 0, .g = 250, .b = 154 }, try RGB.parse("medium spring green"));
|
||||
try testing.expectEqual(RGB{ .r = 34, .g = 139, .b = 34 }, try RGB.parse(" Forest Green "));
|
||||
|
||||
// Invalid format
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("rgb;"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("rgb:"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse(":a/a/a"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("a/a/a"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("rgb:a/a/a/"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("rgb:00000///"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("rgb:000/"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("rgbi:a/a/a"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("rgb:0.5/0.0/1.0"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("rgb:not/hex/zz"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("#"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("#ff"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("#ffff"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("#fffff"));
|
||||
try testing.expectError(error.InvalidFormat, RGB.parse("#gggggg"));
|
||||
}
|
33
src/terminal2/csi.zig
Normal file
33
src/terminal2/csi.zig
Normal file
@ -0,0 +1,33 @@
|
||||
// Modes for the ED CSI command.
|
||||
pub const EraseDisplay = enum(u8) {
|
||||
below = 0,
|
||||
above = 1,
|
||||
complete = 2,
|
||||
scrollback = 3,
|
||||
|
||||
/// This is an extension added by Kitty to move the viewport into the
|
||||
/// scrollback and then erase the display.
|
||||
scroll_complete = 22,
|
||||
};
|
||||
|
||||
// Modes for the EL CSI command.
|
||||
pub const EraseLine = enum(u8) {
|
||||
right = 0,
|
||||
left = 1,
|
||||
complete = 2,
|
||||
right_unless_pending_wrap = 4,
|
||||
|
||||
// Non-exhaustive so that @intToEnum never fails since the inputs are
|
||||
// user-generated.
|
||||
_,
|
||||
};
|
||||
|
||||
// Modes for the TBC (tab clear) command.
|
||||
pub const TabClear = enum(u8) {
|
||||
current = 0,
|
||||
all = 3,
|
||||
|
||||
// Non-exhaustive so that @intToEnum never fails since the inputs are
|
||||
// user-generated.
|
||||
_,
|
||||
};
|
9
src/terminal2/kitty.zig
Normal file
9
src/terminal2/kitty.zig
Normal file
@ -0,0 +1,9 @@
|
||||
//! Types and functions related to Kitty protocols.
|
||||
|
||||
// TODO: migrate to terminal2
|
||||
pub const graphics = @import("../terminal/kitty/graphics.zig");
|
||||
pub usingnamespace @import("../terminal/kitty/key.zig");
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
}
|
247
src/terminal2/modes.zig
Normal file
247
src/terminal2/modes.zig
Normal file
@ -0,0 +1,247 @@
|
||||
//! This file contains all the terminal modes that we support
|
||||
//! and various support types for them: an enum of supported modes,
|
||||
//! a packed struct to store mode values, a more generalized state
|
||||
//! struct to store values plus handle save/restore, and much more.
|
||||
//!
|
||||
//! There is pretty heavy comptime usage and type generation here.
|
||||
//! I don't love to have this sort of complexity but its a good way
|
||||
//! to ensure all our various types and logic remain in sync.
|
||||
|
||||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
|
||||
/// A struct that maintains the state of all the settable modes.
|
||||
pub const ModeState = struct {
|
||||
/// The values of the current modes.
|
||||
values: ModePacked = .{},
|
||||
|
||||
/// The saved values. We only allow saving each mode once.
|
||||
/// This is in line with other terminals that implement XTSAVE
|
||||
/// and XTRESTORE. We can improve this in the future if it becomes
|
||||
/// a real-world issue but we need to be aware of a DoS vector.
|
||||
saved: ModePacked = .{},
|
||||
|
||||
/// Set a mode to a value.
|
||||
pub fn set(self: *ModeState, mode: Mode, value: bool) void {
|
||||
switch (mode) {
|
||||
inline else => |mode_comptime| {
|
||||
const entry = comptime entryForMode(mode_comptime);
|
||||
@field(self.values, entry.name) = value;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value of a mode.
|
||||
pub fn get(self: *ModeState, mode: Mode) bool {
|
||||
switch (mode) {
|
||||
inline else => |mode_comptime| {
|
||||
const entry = comptime entryForMode(mode_comptime);
|
||||
return @field(self.values, entry.name);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Save the state of the given mode. This can then be restored
|
||||
/// with restore. This will only be accurate if the previous
|
||||
/// mode was saved exactly once and not restored. Otherwise this
|
||||
/// will just keep restoring the last stored value in memory.
|
||||
pub fn save(self: *ModeState, mode: Mode) void {
|
||||
switch (mode) {
|
||||
inline else => |mode_comptime| {
|
||||
const entry = comptime entryForMode(mode_comptime);
|
||||
@field(self.saved, entry.name) = @field(self.values, entry.name);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// See save. This will return the restored value.
|
||||
pub fn restore(self: *ModeState, mode: Mode) bool {
|
||||
switch (mode) {
|
||||
inline else => |mode_comptime| {
|
||||
const entry = comptime entryForMode(mode_comptime);
|
||||
@field(self.values, entry.name) = @field(self.saved, entry.name);
|
||||
return @field(self.values, entry.name);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
// We have this here so that we explicitly fail when we change the
|
||||
// size of modes. The size of modes is NOT particularly important,
|
||||
// we just want to be mentally aware when it happens.
|
||||
try std.testing.expectEqual(8, @sizeOf(ModePacked));
|
||||
}
|
||||
};
|
||||
|
||||
/// A packed struct of all the settable modes. This shouldn't
|
||||
/// be used directly but rather through the ModeState struct.
|
||||
pub const ModePacked = packed_struct: {
|
||||
const StructField = std.builtin.Type.StructField;
|
||||
var fields: [entries.len]StructField = undefined;
|
||||
for (entries, 0..) |entry, i| {
|
||||
fields[i] = .{
|
||||
.name = entry.name,
|
||||
.type = bool,
|
||||
.default_value = &entry.default,
|
||||
.is_comptime = false,
|
||||
.alignment = 0,
|
||||
};
|
||||
}
|
||||
|
||||
break :packed_struct @Type(.{ .Struct = .{
|
||||
.layout = .Packed,
|
||||
.fields = &fields,
|
||||
.decls = &.{},
|
||||
.is_tuple = false,
|
||||
} });
|
||||
};
|
||||
|
||||
/// An enum(u16) of the available modes. See entries for available values.
|
||||
pub const Mode = mode_enum: {
|
||||
const EnumField = std.builtin.Type.EnumField;
|
||||
var fields: [entries.len]EnumField = undefined;
|
||||
for (entries, 0..) |entry, i| {
|
||||
fields[i] = .{
|
||||
.name = entry.name,
|
||||
.value = @as(ModeTag.Backing, @bitCast(ModeTag{
|
||||
.value = entry.value,
|
||||
.ansi = entry.ansi,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
break :mode_enum @Type(.{ .Enum = .{
|
||||
.tag_type = ModeTag.Backing,
|
||||
.fields = &fields,
|
||||
.decls = &.{},
|
||||
.is_exhaustive = true,
|
||||
} });
|
||||
};
|
||||
|
||||
/// The tag type for our enum is a u16 but we use a packed struct
|
||||
/// in order to pack the ansi bit into the tag.
|
||||
pub const ModeTag = packed struct(u16) {
|
||||
pub const Backing = @typeInfo(@This()).Struct.backing_integer.?;
|
||||
value: u15,
|
||||
ansi: bool = false,
|
||||
|
||||
test "order" {
|
||||
const t: ModeTag = .{ .value = 1 };
|
||||
const int: Backing = @bitCast(t);
|
||||
try std.testing.expectEqual(@as(Backing, 1), int);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn modeFromInt(v: u16, ansi: bool) ?Mode {
|
||||
inline for (entries) |entry| {
|
||||
if (comptime !entry.disabled) {
|
||||
if (entry.value == v and entry.ansi == ansi) {
|
||||
const tag: ModeTag = .{ .ansi = ansi, .value = entry.value };
|
||||
const int: ModeTag.Backing = @bitCast(tag);
|
||||
return @enumFromInt(int);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn entryForMode(comptime mode: Mode) ModeEntry {
|
||||
@setEvalBranchQuota(10_000);
|
||||
const name = @tagName(mode);
|
||||
for (entries) |entry| {
|
||||
if (std.mem.eql(u8, entry.name, name)) return entry;
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
/// A single entry of a possible mode we support. This is used to
|
||||
/// dynamically define the enum and other tables.
|
||||
const ModeEntry = struct {
|
||||
name: [:0]const u8,
|
||||
value: comptime_int,
|
||||
default: bool = false,
|
||||
|
||||
/// True if this is an ANSI mode, false if its a DEC mode (?-prefixed).
|
||||
ansi: bool = false,
|
||||
|
||||
/// If true, this mode is disabled and Ghostty will not allow it to be
|
||||
/// set or queried. The mode enum still has it, allowing Ghostty developers
|
||||
/// to develop a mode without exposing it to real users.
|
||||
disabled: bool = false,
|
||||
};
|
||||
|
||||
/// The full list of available entries. For documentation see how
|
||||
/// they're used within Ghostty or google their values. It is not
|
||||
/// valuable to redocument them all here.
|
||||
const entries: []const ModeEntry = &.{
|
||||
// ANSI
|
||||
.{ .name = "disable_keyboard", .value = 2, .ansi = true }, // KAM
|
||||
.{ .name = "insert", .value = 4, .ansi = true },
|
||||
.{ .name = "send_receive_mode", .value = 12, .ansi = true, .default = true }, // SRM
|
||||
.{ .name = "linefeed", .value = 20, .ansi = true },
|
||||
|
||||
// DEC
|
||||
.{ .name = "cursor_keys", .value = 1 }, // DECCKM
|
||||
.{ .name = "132_column", .value = 3 },
|
||||
.{ .name = "slow_scroll", .value = 4 },
|
||||
.{ .name = "reverse_colors", .value = 5 },
|
||||
.{ .name = "origin", .value = 6 },
|
||||
.{ .name = "wraparound", .value = 7, .default = true },
|
||||
.{ .name = "autorepeat", .value = 8 },
|
||||
.{ .name = "mouse_event_x10", .value = 9 },
|
||||
.{ .name = "cursor_blinking", .value = 12 },
|
||||
.{ .name = "cursor_visible", .value = 25, .default = true },
|
||||
.{ .name = "enable_mode_3", .value = 40 },
|
||||
.{ .name = "reverse_wrap", .value = 45 },
|
||||
.{ .name = "keypad_keys", .value = 66 },
|
||||
.{ .name = "enable_left_and_right_margin", .value = 69 },
|
||||
.{ .name = "mouse_event_normal", .value = 1000 },
|
||||
.{ .name = "mouse_event_button", .value = 1002 },
|
||||
.{ .name = "mouse_event_any", .value = 1003 },
|
||||
.{ .name = "focus_event", .value = 1004 },
|
||||
.{ .name = "mouse_format_utf8", .value = 1005 },
|
||||
.{ .name = "mouse_format_sgr", .value = 1006 },
|
||||
.{ .name = "mouse_alternate_scroll", .value = 1007, .default = true },
|
||||
.{ .name = "mouse_format_urxvt", .value = 1015 },
|
||||
.{ .name = "mouse_format_sgr_pixels", .value = 1016 },
|
||||
.{ .name = "ignore_keypad_with_numlock", .value = 1035, .default = true },
|
||||
.{ .name = "alt_esc_prefix", .value = 1036, .default = true },
|
||||
.{ .name = "alt_sends_escape", .value = 1039 },
|
||||
.{ .name = "reverse_wrap_extended", .value = 1045 },
|
||||
.{ .name = "alt_screen", .value = 1047 },
|
||||
.{ .name = "alt_screen_save_cursor_clear_enter", .value = 1049 },
|
||||
.{ .name = "bracketed_paste", .value = 2004 },
|
||||
.{ .name = "synchronized_output", .value = 2026 },
|
||||
.{ .name = "grapheme_cluster", .value = 2027 },
|
||||
.{ .name = "report_color_scheme", .value = 2031 },
|
||||
};
|
||||
|
||||
test {
|
||||
_ = Mode;
|
||||
_ = ModePacked;
|
||||
}
|
||||
|
||||
test modeFromInt {
|
||||
try testing.expect(modeFromInt(4, true).? == .insert);
|
||||
try testing.expect(modeFromInt(9, true) == null);
|
||||
try testing.expect(modeFromInt(9, false).? == .mouse_event_x10);
|
||||
try testing.expect(modeFromInt(14, true) == null);
|
||||
}
|
||||
|
||||
test ModeState {
|
||||
var state: ModeState = .{};
|
||||
|
||||
// Normal set/get
|
||||
try testing.expect(!state.get(.cursor_keys));
|
||||
state.set(.cursor_keys, true);
|
||||
try testing.expect(state.get(.cursor_keys));
|
||||
|
||||
// Save/restore
|
||||
state.save(.cursor_keys);
|
||||
state.set(.cursor_keys, false);
|
||||
try testing.expect(!state.get(.cursor_keys));
|
||||
try testing.expect(state.restore(.cursor_keys));
|
||||
try testing.expect(state.get(.cursor_keys));
|
||||
}
|
115
src/terminal2/mouse_shape.zig
Normal file
115
src/terminal2/mouse_shape.zig
Normal file
@ -0,0 +1,115 @@
|
||||
const std = @import("std");
|
||||
|
||||
/// The possible cursor shapes. Not all app runtimes support these shapes.
|
||||
/// The shapes are always based on the W3C supported cursor styles so we
|
||||
/// can have a cross platform list.
|
||||
//
|
||||
// Must be kept in sync with ghostty_cursor_shape_e
|
||||
pub const MouseShape = enum(c_int) {
|
||||
default,
|
||||
context_menu,
|
||||
help,
|
||||
pointer,
|
||||
progress,
|
||||
wait,
|
||||
cell,
|
||||
crosshair,
|
||||
text,
|
||||
vertical_text,
|
||||
alias,
|
||||
copy,
|
||||
move,
|
||||
no_drop,
|
||||
not_allowed,
|
||||
grab,
|
||||
grabbing,
|
||||
all_scroll,
|
||||
col_resize,
|
||||
row_resize,
|
||||
n_resize,
|
||||
e_resize,
|
||||
s_resize,
|
||||
w_resize,
|
||||
ne_resize,
|
||||
nw_resize,
|
||||
se_resize,
|
||||
sw_resize,
|
||||
ew_resize,
|
||||
ns_resize,
|
||||
nesw_resize,
|
||||
nwse_resize,
|
||||
zoom_in,
|
||||
zoom_out,
|
||||
|
||||
/// Build cursor shape from string or null if its unknown.
|
||||
pub fn fromString(v: []const u8) ?MouseShape {
|
||||
return string_map.get(v);
|
||||
}
|
||||
};
|
||||
|
||||
const string_map = std.ComptimeStringMap(MouseShape, .{
|
||||
// W3C
|
||||
.{ "default", .default },
|
||||
.{ "context-menu", .context_menu },
|
||||
.{ "help", .help },
|
||||
.{ "pointer", .pointer },
|
||||
.{ "progress", .progress },
|
||||
.{ "wait", .wait },
|
||||
.{ "cell", .cell },
|
||||
.{ "crosshair", .crosshair },
|
||||
.{ "text", .text },
|
||||
.{ "vertical-text", .vertical_text },
|
||||
.{ "alias", .alias },
|
||||
.{ "copy", .copy },
|
||||
.{ "move", .move },
|
||||
.{ "no-drop", .no_drop },
|
||||
.{ "not-allowed", .not_allowed },
|
||||
.{ "grab", .grab },
|
||||
.{ "grabbing", .grabbing },
|
||||
.{ "all-scroll", .all_scroll },
|
||||
.{ "col-resize", .col_resize },
|
||||
.{ "row-resize", .row_resize },
|
||||
.{ "n-resize", .n_resize },
|
||||
.{ "e-resize", .e_resize },
|
||||
.{ "s-resize", .s_resize },
|
||||
.{ "w-resize", .w_resize },
|
||||
.{ "ne-resize", .ne_resize },
|
||||
.{ "nw-resize", .nw_resize },
|
||||
.{ "se-resize", .se_resize },
|
||||
.{ "sw-resize", .sw_resize },
|
||||
.{ "ew-resize", .ew_resize },
|
||||
.{ "ns-resize", .ns_resize },
|
||||
.{ "nesw-resize", .nesw_resize },
|
||||
.{ "nwse-resize", .nwse_resize },
|
||||
.{ "zoom-in", .zoom_in },
|
||||
.{ "zoom-out", .zoom_out },
|
||||
|
||||
// xterm/foot
|
||||
.{ "left_ptr", .default },
|
||||
.{ "question_arrow", .help },
|
||||
.{ "hand", .pointer },
|
||||
.{ "left_ptr_watch", .progress },
|
||||
.{ "watch", .wait },
|
||||
.{ "cross", .crosshair },
|
||||
.{ "xterm", .text },
|
||||
.{ "dnd-link", .alias },
|
||||
.{ "dnd-copy", .copy },
|
||||
.{ "dnd-move", .move },
|
||||
.{ "dnd-no-drop", .no_drop },
|
||||
.{ "crossed_circle", .not_allowed },
|
||||
.{ "hand1", .grab },
|
||||
.{ "right_side", .e_resize },
|
||||
.{ "top_side", .n_resize },
|
||||
.{ "top_right_corner", .ne_resize },
|
||||
.{ "top_left_corner", .nw_resize },
|
||||
.{ "bottom_side", .s_resize },
|
||||
.{ "bottom_right_corner", .se_resize },
|
||||
.{ "bottom_left_corner", .sw_resize },
|
||||
.{ "left_side", .w_resize },
|
||||
.{ "fleur", .all_scroll },
|
||||
});
|
||||
|
||||
test "cursor shape from string" {
|
||||
const testing = std.testing;
|
||||
try testing.expectEqual(MouseShape.default, MouseShape.fromString("default").?);
|
||||
}
|
@ -2,9 +2,9 @@ const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const fastmem = @import("../../fastmem.zig");
|
||||
const color = @import("../color.zig");
|
||||
const sgr = @import("../sgr.zig");
|
||||
const fastmem = @import("../fastmem.zig");
|
||||
const color = @import("color.zig");
|
||||
const sgr = @import("sgr.zig");
|
||||
const style = @import("style.zig");
|
||||
const size = @import("size.zig");
|
||||
const getOffset = size.getOffset;
|
@ -77,11 +77,6 @@ pub const Viewport = struct {
|
||||
pub fn eql(self: Viewport, other: Viewport) bool {
|
||||
return self.x == other.x and self.y == other.y;
|
||||
}
|
||||
|
||||
const TerminalScreen = @import("Screen.zig");
|
||||
pub fn toScreen(_: Viewport, _: *const TerminalScreen) Screen {
|
||||
@panic("TODO");
|
||||
}
|
||||
};
|
||||
|
||||
/// A point in the terminal that is in relation to the entire screen.
|
782
src/terminal2/res/rgb.txt
Normal file
782
src/terminal2/res/rgb.txt
Normal file
@ -0,0 +1,782 @@
|
||||
255 250 250 snow
|
||||
248 248 255 ghost white
|
||||
248 248 255 GhostWhite
|
||||
245 245 245 white smoke
|
||||
245 245 245 WhiteSmoke
|
||||
220 220 220 gainsboro
|
||||
255 250 240 floral white
|
||||
255 250 240 FloralWhite
|
||||
253 245 230 old lace
|
||||
253 245 230 OldLace
|
||||
250 240 230 linen
|
||||
250 235 215 antique white
|
||||
250 235 215 AntiqueWhite
|
||||
255 239 213 papaya whip
|
||||
255 239 213 PapayaWhip
|
||||
255 235 205 blanched almond
|
||||
255 235 205 BlanchedAlmond
|
||||
255 228 196 bisque
|
||||
255 218 185 peach puff
|
||||
255 218 185 PeachPuff
|
||||
255 222 173 navajo white
|
||||
255 222 173 NavajoWhite
|
||||
255 228 181 moccasin
|
||||
255 248 220 cornsilk
|
||||
255 255 240 ivory
|
||||
255 250 205 lemon chiffon
|
||||
255 250 205 LemonChiffon
|
||||
255 245 238 seashell
|
||||
240 255 240 honeydew
|
||||
245 255 250 mint cream
|
||||
245 255 250 MintCream
|
||||
240 255 255 azure
|
||||
240 248 255 alice blue
|
||||
240 248 255 AliceBlue
|
||||
230 230 250 lavender
|
||||
255 240 245 lavender blush
|
||||
255 240 245 LavenderBlush
|
||||
255 228 225 misty rose
|
||||
255 228 225 MistyRose
|
||||
255 255 255 white
|
||||
0 0 0 black
|
||||
47 79 79 dark slate gray
|
||||
47 79 79 DarkSlateGray
|
||||
47 79 79 dark slate grey
|
||||
47 79 79 DarkSlateGrey
|
||||
105 105 105 dim gray
|
||||
105 105 105 DimGray
|
||||
105 105 105 dim grey
|
||||
105 105 105 DimGrey
|
||||
112 128 144 slate gray
|
||||
112 128 144 SlateGray
|
||||
112 128 144 slate grey
|
||||
112 128 144 SlateGrey
|
||||
119 136 153 light slate gray
|
||||
119 136 153 LightSlateGray
|
||||
119 136 153 light slate grey
|
||||
119 136 153 LightSlateGrey
|
||||
190 190 190 gray
|
||||
190 190 190 grey
|
||||
190 190 190 x11 gray
|
||||
190 190 190 X11Gray
|
||||
190 190 190 x11 grey
|
||||
190 190 190 X11Grey
|
||||
128 128 128 web gray
|
||||
128 128 128 WebGray
|
||||
128 128 128 web grey
|
||||
128 128 128 WebGrey
|
||||
211 211 211 light grey
|
||||
211 211 211 LightGrey
|
||||
211 211 211 light gray
|
||||
211 211 211 LightGray
|
||||
25 25 112 midnight blue
|
||||
25 25 112 MidnightBlue
|
||||
0 0 128 navy
|
||||
0 0 128 navy blue
|
||||
0 0 128 NavyBlue
|
||||
100 149 237 cornflower blue
|
||||
100 149 237 CornflowerBlue
|
||||
72 61 139 dark slate blue
|
||||
72 61 139 DarkSlateBlue
|
||||
106 90 205 slate blue
|
||||
106 90 205 SlateBlue
|
||||
123 104 238 medium slate blue
|
||||
123 104 238 MediumSlateBlue
|
||||
132 112 255 light slate blue
|
||||
132 112 255 LightSlateBlue
|
||||
0 0 205 medium blue
|
||||
0 0 205 MediumBlue
|
||||
65 105 225 royal blue
|
||||
65 105 225 RoyalBlue
|
||||
0 0 255 blue
|
||||
30 144 255 dodger blue
|
||||
30 144 255 DodgerBlue
|
||||
0 191 255 deep sky blue
|
||||
0 191 255 DeepSkyBlue
|
||||
135 206 235 sky blue
|
||||
135 206 235 SkyBlue
|
||||
135 206 250 light sky blue
|
||||
135 206 250 LightSkyBlue
|
||||
70 130 180 steel blue
|
||||
70 130 180 SteelBlue
|
||||
176 196 222 light steel blue
|
||||
176 196 222 LightSteelBlue
|
||||
173 216 230 light blue
|
||||
173 216 230 LightBlue
|
||||
176 224 230 powder blue
|
||||
176 224 230 PowderBlue
|
||||
175 238 238 pale turquoise
|
||||
175 238 238 PaleTurquoise
|
||||
0 206 209 dark turquoise
|
||||
0 206 209 DarkTurquoise
|
||||
72 209 204 medium turquoise
|
||||
72 209 204 MediumTurquoise
|
||||
64 224 208 turquoise
|
||||
0 255 255 cyan
|
||||
0 255 255 aqua
|
||||
224 255 255 light cyan
|
||||
224 255 255 LightCyan
|
||||
95 158 160 cadet blue
|
||||
95 158 160 CadetBlue
|
||||
102 205 170 medium aquamarine
|
||||
102 205 170 MediumAquamarine
|
||||
127 255 212 aquamarine
|
||||
0 100 0 dark green
|
||||
0 100 0 DarkGreen
|
||||
85 107 47 dark olive green
|
||||
85 107 47 DarkOliveGreen
|
||||
143 188 143 dark sea green
|
||||
143 188 143 DarkSeaGreen
|
||||
46 139 87 sea green
|
||||
46 139 87 SeaGreen
|
||||
60 179 113 medium sea green
|
||||
60 179 113 MediumSeaGreen
|
||||
32 178 170 light sea green
|
||||
32 178 170 LightSeaGreen
|
||||
152 251 152 pale green
|
||||
152 251 152 PaleGreen
|
||||
0 255 127 spring green
|
||||
0 255 127 SpringGreen
|
||||
124 252 0 lawn green
|
||||
124 252 0 LawnGreen
|
||||
0 255 0 green
|
||||
0 255 0 lime
|
||||
0 255 0 x11 green
|
||||
0 255 0 X11Green
|
||||
0 128 0 web green
|
||||
0 128 0 WebGreen
|
||||
127 255 0 chartreuse
|
||||
0 250 154 medium spring green
|
||||
0 250 154 MediumSpringGreen
|
||||
173 255 47 green yellow
|
||||
173 255 47 GreenYellow
|
||||
50 205 50 lime green
|
||||
50 205 50 LimeGreen
|
||||
154 205 50 yellow green
|
||||
154 205 50 YellowGreen
|
||||
34 139 34 forest green
|
||||
34 139 34 ForestGreen
|
||||
107 142 35 olive drab
|
||||
107 142 35 OliveDrab
|
||||
189 183 107 dark khaki
|
||||
189 183 107 DarkKhaki
|
||||
240 230 140 khaki
|
||||
238 232 170 pale goldenrod
|
||||
238 232 170 PaleGoldenrod
|
||||
250 250 210 light goldenrod yellow
|
||||
250 250 210 LightGoldenrodYellow
|
||||
255 255 224 light yellow
|
||||
255 255 224 LightYellow
|
||||
255 255 0 yellow
|
||||
255 215 0 gold
|
||||
238 221 130 light goldenrod
|
||||
238 221 130 LightGoldenrod
|
||||
218 165 32 goldenrod
|
||||
184 134 11 dark goldenrod
|
||||
184 134 11 DarkGoldenrod
|
||||
188 143 143 rosy brown
|
||||
188 143 143 RosyBrown
|
||||
205 92 92 indian red
|
||||
205 92 92 IndianRed
|
||||
139 69 19 saddle brown
|
||||
139 69 19 SaddleBrown
|
||||
160 82 45 sienna
|
||||
205 133 63 peru
|
||||
222 184 135 burlywood
|
||||
245 245 220 beige
|
||||
245 222 179 wheat
|
||||
244 164 96 sandy brown
|
||||
244 164 96 SandyBrown
|
||||
210 180 140 tan
|
||||
210 105 30 chocolate
|
||||
178 34 34 firebrick
|
||||
165 42 42 brown
|
||||
233 150 122 dark salmon
|
||||
233 150 122 DarkSalmon
|
||||
250 128 114 salmon
|
||||
255 160 122 light salmon
|
||||
255 160 122 LightSalmon
|
||||
255 165 0 orange
|
||||
255 140 0 dark orange
|
||||
255 140 0 DarkOrange
|
||||
255 127 80 coral
|
||||
240 128 128 light coral
|
||||
240 128 128 LightCoral
|
||||
255 99 71 tomato
|
||||
255 69 0 orange red
|
||||
255 69 0 OrangeRed
|
||||
255 0 0 red
|
||||
255 105 180 hot pink
|
||||
255 105 180 HotPink
|
||||
255 20 147 deep pink
|
||||
255 20 147 DeepPink
|
||||
255 192 203 pink
|
||||
255 182 193 light pink
|
||||
255 182 193 LightPink
|
||||
219 112 147 pale violet red
|
||||
219 112 147 PaleVioletRed
|
||||
176 48 96 maroon
|
||||
176 48 96 x11 maroon
|
||||
176 48 96 X11Maroon
|
||||
128 0 0 web maroon
|
||||
128 0 0 WebMaroon
|
||||
199 21 133 medium violet red
|
||||
199 21 133 MediumVioletRed
|
||||
208 32 144 violet red
|
||||
208 32 144 VioletRed
|
||||
255 0 255 magenta
|
||||
255 0 255 fuchsia
|
||||
238 130 238 violet
|
||||
221 160 221 plum
|
||||
218 112 214 orchid
|
||||
186 85 211 medium orchid
|
||||
186 85 211 MediumOrchid
|
||||
153 50 204 dark orchid
|
||||
153 50 204 DarkOrchid
|
||||
148 0 211 dark violet
|
||||
148 0 211 DarkViolet
|
||||
138 43 226 blue violet
|
||||
138 43 226 BlueViolet
|
||||
160 32 240 purple
|
||||
160 32 240 x11 purple
|
||||
160 32 240 X11Purple
|
||||
128 0 128 web purple
|
||||
128 0 128 WebPurple
|
||||
147 112 219 medium purple
|
||||
147 112 219 MediumPurple
|
||||
216 191 216 thistle
|
||||
255 250 250 snow1
|
||||
238 233 233 snow2
|
||||
205 201 201 snow3
|
||||
139 137 137 snow4
|
||||
255 245 238 seashell1
|
||||
238 229 222 seashell2
|
||||
205 197 191 seashell3
|
||||
139 134 130 seashell4
|
||||
255 239 219 AntiqueWhite1
|
||||
238 223 204 AntiqueWhite2
|
||||
205 192 176 AntiqueWhite3
|
||||
139 131 120 AntiqueWhite4
|
||||
255 228 196 bisque1
|
||||
238 213 183 bisque2
|
||||
205 183 158 bisque3
|
||||
139 125 107 bisque4
|
||||
255 218 185 PeachPuff1
|
||||
238 203 173 PeachPuff2
|
||||
205 175 149 PeachPuff3
|
||||
139 119 101 PeachPuff4
|
||||
255 222 173 NavajoWhite1
|
||||
238 207 161 NavajoWhite2
|
||||
205 179 139 NavajoWhite3
|
||||
139 121 94 NavajoWhite4
|
||||
255 250 205 LemonChiffon1
|
||||
238 233 191 LemonChiffon2
|
||||
205 201 165 LemonChiffon3
|
||||
139 137 112 LemonChiffon4
|
||||
255 248 220 cornsilk1
|
||||
238 232 205 cornsilk2
|
||||
205 200 177 cornsilk3
|
||||
139 136 120 cornsilk4
|
||||
255 255 240 ivory1
|
||||
238 238 224 ivory2
|
||||
205 205 193 ivory3
|
||||
139 139 131 ivory4
|
||||
240 255 240 honeydew1
|
||||
224 238 224 honeydew2
|
||||
193 205 193 honeydew3
|
||||
131 139 131 honeydew4
|
||||
255 240 245 LavenderBlush1
|
||||
238 224 229 LavenderBlush2
|
||||
205 193 197 LavenderBlush3
|
||||
139 131 134 LavenderBlush4
|
||||
255 228 225 MistyRose1
|
||||
238 213 210 MistyRose2
|
||||
205 183 181 MistyRose3
|
||||
139 125 123 MistyRose4
|
||||
240 255 255 azure1
|
||||
224 238 238 azure2
|
||||
193 205 205 azure3
|
||||
131 139 139 azure4
|
||||
131 111 255 SlateBlue1
|
||||
122 103 238 SlateBlue2
|
||||
105 89 205 SlateBlue3
|
||||
71 60 139 SlateBlue4
|
||||
72 118 255 RoyalBlue1
|
||||
67 110 238 RoyalBlue2
|
||||
58 95 205 RoyalBlue3
|
||||
39 64 139 RoyalBlue4
|
||||
0 0 255 blue1
|
||||
0 0 238 blue2
|
||||
0 0 205 blue3
|
||||
0 0 139 blue4
|
||||
30 144 255 DodgerBlue1
|
||||
28 134 238 DodgerBlue2
|
||||
24 116 205 DodgerBlue3
|
||||
16 78 139 DodgerBlue4
|
||||
99 184 255 SteelBlue1
|
||||
92 172 238 SteelBlue2
|
||||
79 148 205 SteelBlue3
|
||||
54 100 139 SteelBlue4
|
||||
0 191 255 DeepSkyBlue1
|
||||
0 178 238 DeepSkyBlue2
|
||||
0 154 205 DeepSkyBlue3
|
||||
0 104 139 DeepSkyBlue4
|
||||
135 206 255 SkyBlue1
|
||||
126 192 238 SkyBlue2
|
||||
108 166 205 SkyBlue3
|
||||
74 112 139 SkyBlue4
|
||||
176 226 255 LightSkyBlue1
|
||||
164 211 238 LightSkyBlue2
|
||||
141 182 205 LightSkyBlue3
|
||||
96 123 139 LightSkyBlue4
|
||||
198 226 255 SlateGray1
|
||||
185 211 238 SlateGray2
|
||||
159 182 205 SlateGray3
|
||||
108 123 139 SlateGray4
|
||||
202 225 255 LightSteelBlue1
|
||||
188 210 238 LightSteelBlue2
|
||||
162 181 205 LightSteelBlue3
|
||||
110 123 139 LightSteelBlue4
|
||||
191 239 255 LightBlue1
|
||||
178 223 238 LightBlue2
|
||||
154 192 205 LightBlue3
|
||||
104 131 139 LightBlue4
|
||||
224 255 255 LightCyan1
|
||||
209 238 238 LightCyan2
|
||||
180 205 205 LightCyan3
|
||||
122 139 139 LightCyan4
|
||||
187 255 255 PaleTurquoise1
|
||||
174 238 238 PaleTurquoise2
|
||||
150 205 205 PaleTurquoise3
|
||||
102 139 139 PaleTurquoise4
|
||||
152 245 255 CadetBlue1
|
||||
142 229 238 CadetBlue2
|
||||
122 197 205 CadetBlue3
|
||||
83 134 139 CadetBlue4
|
||||
0 245 255 turquoise1
|
||||
0 229 238 turquoise2
|
||||
0 197 205 turquoise3
|
||||
0 134 139 turquoise4
|
||||
0 255 255 cyan1
|
||||
0 238 238 cyan2
|
||||
0 205 205 cyan3
|
||||
0 139 139 cyan4
|
||||
151 255 255 DarkSlateGray1
|
||||
141 238 238 DarkSlateGray2
|
||||
121 205 205 DarkSlateGray3
|
||||
82 139 139 DarkSlateGray4
|
||||
127 255 212 aquamarine1
|
||||
118 238 198 aquamarine2
|
||||
102 205 170 aquamarine3
|
||||
69 139 116 aquamarine4
|
||||
193 255 193 DarkSeaGreen1
|
||||
180 238 180 DarkSeaGreen2
|
||||
155 205 155 DarkSeaGreen3
|
||||
105 139 105 DarkSeaGreen4
|
||||
84 255 159 SeaGreen1
|
||||
78 238 148 SeaGreen2
|
||||
67 205 128 SeaGreen3
|
||||
46 139 87 SeaGreen4
|
||||
154 255 154 PaleGreen1
|
||||
144 238 144 PaleGreen2
|
||||
124 205 124 PaleGreen3
|
||||
84 139 84 PaleGreen4
|
||||
0 255 127 SpringGreen1
|
||||
0 238 118 SpringGreen2
|
||||
0 205 102 SpringGreen3
|
||||
0 139 69 SpringGreen4
|
||||
0 255 0 green1
|
||||
0 238 0 green2
|
||||
0 205 0 green3
|
||||
0 139 0 green4
|
||||
127 255 0 chartreuse1
|
||||
118 238 0 chartreuse2
|
||||
102 205 0 chartreuse3
|
||||
69 139 0 chartreuse4
|
||||
192 255 62 OliveDrab1
|
||||
179 238 58 OliveDrab2
|
||||
154 205 50 OliveDrab3
|
||||
105 139 34 OliveDrab4
|
||||
202 255 112 DarkOliveGreen1
|
||||
188 238 104 DarkOliveGreen2
|
||||
162 205 90 DarkOliveGreen3
|
||||
110 139 61 DarkOliveGreen4
|
||||
255 246 143 khaki1
|
||||
238 230 133 khaki2
|
||||
205 198 115 khaki3
|
||||
139 134 78 khaki4
|
||||
255 236 139 LightGoldenrod1
|
||||
238 220 130 LightGoldenrod2
|
||||
205 190 112 LightGoldenrod3
|
||||
139 129 76 LightGoldenrod4
|
||||
255 255 224 LightYellow1
|
||||
238 238 209 LightYellow2
|
||||
205 205 180 LightYellow3
|
||||
139 139 122 LightYellow4
|
||||
255 255 0 yellow1
|
||||
238 238 0 yellow2
|
||||
205 205 0 yellow3
|
||||
139 139 0 yellow4
|
||||
255 215 0 gold1
|
||||
238 201 0 gold2
|
||||
205 173 0 gold3
|
||||
139 117 0 gold4
|
||||
255 193 37 goldenrod1
|
||||
238 180 34 goldenrod2
|
||||
205 155 29 goldenrod3
|
||||
139 105 20 goldenrod4
|
||||
255 185 15 DarkGoldenrod1
|
||||
238 173 14 DarkGoldenrod2
|
||||
205 149 12 DarkGoldenrod3
|
||||
139 101 8 DarkGoldenrod4
|
||||
255 193 193 RosyBrown1
|
||||
238 180 180 RosyBrown2
|
||||
205 155 155 RosyBrown3
|
||||
139 105 105 RosyBrown4
|
||||
255 106 106 IndianRed1
|
||||
238 99 99 IndianRed2
|
||||
205 85 85 IndianRed3
|
||||
139 58 58 IndianRed4
|
||||
255 130 71 sienna1
|
||||
238 121 66 sienna2
|
||||
205 104 57 sienna3
|
||||
139 71 38 sienna4
|
||||
255 211 155 burlywood1
|
||||
238 197 145 burlywood2
|
||||
205 170 125 burlywood3
|
||||
139 115 85 burlywood4
|
||||
255 231 186 wheat1
|
||||
238 216 174 wheat2
|
||||
205 186 150 wheat3
|
||||
139 126 102 wheat4
|
||||
255 165 79 tan1
|
||||
238 154 73 tan2
|
||||
205 133 63 tan3
|
||||
139 90 43 tan4
|
||||
255 127 36 chocolate1
|
||||
238 118 33 chocolate2
|
||||
205 102 29 chocolate3
|
||||
139 69 19 chocolate4
|
||||
255 48 48 firebrick1
|
||||
238 44 44 firebrick2
|
||||
205 38 38 firebrick3
|
||||
139 26 26 firebrick4
|
||||
255 64 64 brown1
|
||||
238 59 59 brown2
|
||||
205 51 51 brown3
|
||||
139 35 35 brown4
|
||||
255 140 105 salmon1
|
||||
238 130 98 salmon2
|
||||
205 112 84 salmon3
|
||||
139 76 57 salmon4
|
||||
255 160 122 LightSalmon1
|
||||
238 149 114 LightSalmon2
|
||||
205 129 98 LightSalmon3
|
||||
139 87 66 LightSalmon4
|
||||
255 165 0 orange1
|
||||
238 154 0 orange2
|
||||
205 133 0 orange3
|
||||
139 90 0 orange4
|
||||
255 127 0 DarkOrange1
|
||||
238 118 0 DarkOrange2
|
||||
205 102 0 DarkOrange3
|
||||
139 69 0 DarkOrange4
|
||||
255 114 86 coral1
|
||||
238 106 80 coral2
|
||||
205 91 69 coral3
|
||||
139 62 47 coral4
|
||||
255 99 71 tomato1
|
||||
238 92 66 tomato2
|
||||
205 79 57 tomato3
|
||||
139 54 38 tomato4
|
||||
255 69 0 OrangeRed1
|
||||
238 64 0 OrangeRed2
|
||||
205 55 0 OrangeRed3
|
||||
139 37 0 OrangeRed4
|
||||
255 0 0 red1
|
||||
238 0 0 red2
|
||||
205 0 0 red3
|
||||
139 0 0 red4
|
||||
255 20 147 DeepPink1
|
||||
238 18 137 DeepPink2
|
||||
205 16 118 DeepPink3
|
||||
139 10 80 DeepPink4
|
||||
255 110 180 HotPink1
|
||||
238 106 167 HotPink2
|
||||
205 96 144 HotPink3
|
||||
139 58 98 HotPink4
|
||||
255 181 197 pink1
|
||||
238 169 184 pink2
|
||||
205 145 158 pink3
|
||||
139 99 108 pink4
|
||||
255 174 185 LightPink1
|
||||
238 162 173 LightPink2
|
||||
205 140 149 LightPink3
|
||||
139 95 101 LightPink4
|
||||
255 130 171 PaleVioletRed1
|
||||
238 121 159 PaleVioletRed2
|
||||
205 104 137 PaleVioletRed3
|
||||
139 71 93 PaleVioletRed4
|
||||
255 52 179 maroon1
|
||||
238 48 167 maroon2
|
||||
205 41 144 maroon3
|
||||
139 28 98 maroon4
|
||||
255 62 150 VioletRed1
|
||||
238 58 140 VioletRed2
|
||||
205 50 120 VioletRed3
|
||||
139 34 82 VioletRed4
|
||||
255 0 255 magenta1
|
||||
238 0 238 magenta2
|
||||
205 0 205 magenta3
|
||||
139 0 139 magenta4
|
||||
255 131 250 orchid1
|
||||
238 122 233 orchid2
|
||||
205 105 201 orchid3
|
||||
139 71 137 orchid4
|
||||
255 187 255 plum1
|
||||
238 174 238 plum2
|
||||
205 150 205 plum3
|
||||
139 102 139 plum4
|
||||
224 102 255 MediumOrchid1
|
||||
209 95 238 MediumOrchid2
|
||||
180 82 205 MediumOrchid3
|
||||
122 55 139 MediumOrchid4
|
||||
191 62 255 DarkOrchid1
|
||||
178 58 238 DarkOrchid2
|
||||
154 50 205 DarkOrchid3
|
||||
104 34 139 DarkOrchid4
|
||||
155 48 255 purple1
|
||||
145 44 238 purple2
|
||||
125 38 205 purple3
|
||||
85 26 139 purple4
|
||||
171 130 255 MediumPurple1
|
||||
159 121 238 MediumPurple2
|
||||
137 104 205 MediumPurple3
|
||||
93 71 139 MediumPurple4
|
||||
255 225 255 thistle1
|
||||
238 210 238 thistle2
|
||||
205 181 205 thistle3
|
||||
139 123 139 thistle4
|
||||
0 0 0 gray0
|
||||
0 0 0 grey0
|
||||
3 3 3 gray1
|
||||
3 3 3 grey1
|
||||
5 5 5 gray2
|
||||
5 5 5 grey2
|
||||
8 8 8 gray3
|
||||
8 8 8 grey3
|
||||
10 10 10 gray4
|
||||
10 10 10 grey4
|
||||
13 13 13 gray5
|
||||
13 13 13 grey5
|
||||
15 15 15 gray6
|
||||
15 15 15 grey6
|
||||
18 18 18 gray7
|
||||
18 18 18 grey7
|
||||
20 20 20 gray8
|
||||
20 20 20 grey8
|
||||
23 23 23 gray9
|
||||
23 23 23 grey9
|
||||
26 26 26 gray10
|
||||
26 26 26 grey10
|
||||
28 28 28 gray11
|
||||
28 28 28 grey11
|
||||
31 31 31 gray12
|
||||
31 31 31 grey12
|
||||
33 33 33 gray13
|
||||
33 33 33 grey13
|
||||
36 36 36 gray14
|
||||
36 36 36 grey14
|
||||
38 38 38 gray15
|
||||
38 38 38 grey15
|
||||
41 41 41 gray16
|
||||
41 41 41 grey16
|
||||
43 43 43 gray17
|
||||
43 43 43 grey17
|
||||
46 46 46 gray18
|
||||
46 46 46 grey18
|
||||
48 48 48 gray19
|
||||
48 48 48 grey19
|
||||
51 51 51 gray20
|
||||
51 51 51 grey20
|
||||
54 54 54 gray21
|
||||
54 54 54 grey21
|
||||
56 56 56 gray22
|
||||
56 56 56 grey22
|
||||
59 59 59 gray23
|
||||
59 59 59 grey23
|
||||
61 61 61 gray24
|
||||
61 61 61 grey24
|
||||
64 64 64 gray25
|
||||
64 64 64 grey25
|
||||
66 66 66 gray26
|
||||
66 66 66 grey26
|
||||
69 69 69 gray27
|
||||
69 69 69 grey27
|
||||
71 71 71 gray28
|
||||
71 71 71 grey28
|
||||
74 74 74 gray29
|
||||
74 74 74 grey29
|
||||
77 77 77 gray30
|
||||
77 77 77 grey30
|
||||
79 79 79 gray31
|
||||
79 79 79 grey31
|
||||
82 82 82 gray32
|
||||
82 82 82 grey32
|
||||
84 84 84 gray33
|
||||
84 84 84 grey33
|
||||
87 87 87 gray34
|
||||
87 87 87 grey34
|
||||
89 89 89 gray35
|
||||
89 89 89 grey35
|
||||
92 92 92 gray36
|
||||
92 92 92 grey36
|
||||
94 94 94 gray37
|
||||
94 94 94 grey37
|
||||
97 97 97 gray38
|
||||
97 97 97 grey38
|
||||
99 99 99 gray39
|
||||
99 99 99 grey39
|
||||
102 102 102 gray40
|
||||
102 102 102 grey40
|
||||
105 105 105 gray41
|
||||
105 105 105 grey41
|
||||
107 107 107 gray42
|
||||
107 107 107 grey42
|
||||
110 110 110 gray43
|
||||
110 110 110 grey43
|
||||
112 112 112 gray44
|
||||
112 112 112 grey44
|
||||
115 115 115 gray45
|
||||
115 115 115 grey45
|
||||
117 117 117 gray46
|
||||
117 117 117 grey46
|
||||
120 120 120 gray47
|
||||
120 120 120 grey47
|
||||
122 122 122 gray48
|
||||
122 122 122 grey48
|
||||
125 125 125 gray49
|
||||
125 125 125 grey49
|
||||
127 127 127 gray50
|
||||
127 127 127 grey50
|
||||
130 130 130 gray51
|
||||
130 130 130 grey51
|
||||
133 133 133 gray52
|
||||
133 133 133 grey52
|
||||
135 135 135 gray53
|
||||
135 135 135 grey53
|
||||
138 138 138 gray54
|
||||
138 138 138 grey54
|
||||
140 140 140 gray55
|
||||
140 140 140 grey55
|
||||
143 143 143 gray56
|
||||
143 143 143 grey56
|
||||
145 145 145 gray57
|
||||
145 145 145 grey57
|
||||
148 148 148 gray58
|
||||
148 148 148 grey58
|
||||
150 150 150 gray59
|
||||
150 150 150 grey59
|
||||
153 153 153 gray60
|
||||
153 153 153 grey60
|
||||
156 156 156 gray61
|
||||
156 156 156 grey61
|
||||
158 158 158 gray62
|
||||
158 158 158 grey62
|
||||
161 161 161 gray63
|
||||
161 161 161 grey63
|
||||
163 163 163 gray64
|
||||
163 163 163 grey64
|
||||
166 166 166 gray65
|
||||
166 166 166 grey65
|
||||
168 168 168 gray66
|
||||
168 168 168 grey66
|
||||
171 171 171 gray67
|
||||
171 171 171 grey67
|
||||
173 173 173 gray68
|
||||
173 173 173 grey68
|
||||
176 176 176 gray69
|
||||
176 176 176 grey69
|
||||
179 179 179 gray70
|
||||
179 179 179 grey70
|
||||
181 181 181 gray71
|
||||
181 181 181 grey71
|
||||
184 184 184 gray72
|
||||
184 184 184 grey72
|
||||
186 186 186 gray73
|
||||
186 186 186 grey73
|
||||
189 189 189 gray74
|
||||
189 189 189 grey74
|
||||
191 191 191 gray75
|
||||
191 191 191 grey75
|
||||
194 194 194 gray76
|
||||
194 194 194 grey76
|
||||
196 196 196 gray77
|
||||
196 196 196 grey77
|
||||
199 199 199 gray78
|
||||
199 199 199 grey78
|
||||
201 201 201 gray79
|
||||
201 201 201 grey79
|
||||
204 204 204 gray80
|
||||
204 204 204 grey80
|
||||
207 207 207 gray81
|
||||
207 207 207 grey81
|
||||
209 209 209 gray82
|
||||
209 209 209 grey82
|
||||
212 212 212 gray83
|
||||
212 212 212 grey83
|
||||
214 214 214 gray84
|
||||
214 214 214 grey84
|
||||
217 217 217 gray85
|
||||
217 217 217 grey85
|
||||
219 219 219 gray86
|
||||
219 219 219 grey86
|
||||
222 222 222 gray87
|
||||
222 222 222 grey87
|
||||
224 224 224 gray88
|
||||
224 224 224 grey88
|
||||
227 227 227 gray89
|
||||
227 227 227 grey89
|
||||
229 229 229 gray90
|
||||
229 229 229 grey90
|
||||
232 232 232 gray91
|
||||
232 232 232 grey91
|
||||
235 235 235 gray92
|
||||
235 235 235 grey92
|
||||
237 237 237 gray93
|
||||
237 237 237 grey93
|
||||
240 240 240 gray94
|
||||
240 240 240 grey94
|
||||
242 242 242 gray95
|
||||
242 242 242 grey95
|
||||
245 245 245 gray96
|
||||
245 245 245 grey96
|
||||
247 247 247 gray97
|
||||
247 247 247 grey97
|
||||
250 250 250 gray98
|
||||
250 250 250 grey98
|
||||
252 252 252 gray99
|
||||
252 252 252 grey99
|
||||
255 255 255 gray100
|
||||
255 255 255 grey100
|
||||
169 169 169 dark grey
|
||||
169 169 169 DarkGrey
|
||||
169 169 169 dark gray
|
||||
169 169 169 DarkGray
|
||||
0 0 139 dark blue
|
||||
0 0 139 DarkBlue
|
||||
0 139 139 dark cyan
|
||||
0 139 139 DarkCyan
|
||||
139 0 139 dark magenta
|
||||
139 0 139 DarkMagenta
|
||||
139 0 0 dark red
|
||||
139 0 0 DarkRed
|
||||
144 238 144 light green
|
||||
144 238 144 LightGreen
|
||||
220 20 60 crimson
|
||||
75 0 130 indigo
|
||||
128 128 0 olive
|
||||
102 51 153 rebecca purple
|
||||
102 51 153 RebeccaPurple
|
||||
192 192 192 silver
|
||||
0 128 128 teal
|
559
src/terminal2/sgr.zig
Normal file
559
src/terminal2/sgr.zig
Normal file
@ -0,0 +1,559 @@
|
||||
//! SGR (Select Graphic Rendition) attrinvbute parsing and types.
|
||||
|
||||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const color = @import("color.zig");
|
||||
|
||||
/// Attribute type for SGR
|
||||
pub const Attribute = union(enum) {
|
||||
/// Unset all attributes
|
||||
unset: void,
|
||||
|
||||
/// Unknown attribute, the raw CSI command parameters are here.
|
||||
unknown: struct {
|
||||
/// Full is the full SGR input.
|
||||
full: []const u16,
|
||||
|
||||
/// Partial is the remaining, where we got hung up.
|
||||
partial: []const u16,
|
||||
},
|
||||
|
||||
/// Bold the text.
|
||||
bold: void,
|
||||
reset_bold: void,
|
||||
|
||||
/// Italic text.
|
||||
italic: void,
|
||||
reset_italic: void,
|
||||
|
||||
/// Faint/dim text.
|
||||
/// Note: reset faint is the same SGR code as reset bold
|
||||
faint: void,
|
||||
|
||||
/// Underline the text
|
||||
underline: Underline,
|
||||
reset_underline: void,
|
||||
underline_color: color.RGB,
|
||||
@"256_underline_color": u8,
|
||||
reset_underline_color: void,
|
||||
|
||||
/// Blink the text
|
||||
blink: void,
|
||||
reset_blink: void,
|
||||
|
||||
/// Invert fg/bg colors.
|
||||
inverse: void,
|
||||
reset_inverse: void,
|
||||
|
||||
/// Invisible
|
||||
invisible: void,
|
||||
reset_invisible: void,
|
||||
|
||||
/// Strikethrough the text.
|
||||
strikethrough: void,
|
||||
reset_strikethrough: void,
|
||||
|
||||
/// Set foreground color as RGB values.
|
||||
direct_color_fg: color.RGB,
|
||||
|
||||
/// Set background color as RGB values.
|
||||
direct_color_bg: color.RGB,
|
||||
|
||||
/// Set the background/foreground as a named color attribute.
|
||||
@"8_bg": color.Name,
|
||||
@"8_fg": color.Name,
|
||||
|
||||
/// Reset the fg/bg to their default values.
|
||||
reset_fg: void,
|
||||
reset_bg: void,
|
||||
|
||||
/// Set the background/foreground as a named bright color attribute.
|
||||
@"8_bright_bg": color.Name,
|
||||
@"8_bright_fg": color.Name,
|
||||
|
||||
/// Set background color as 256-color palette.
|
||||
@"256_bg": u8,
|
||||
|
||||
/// Set foreground color as 256-color palette.
|
||||
@"256_fg": u8,
|
||||
|
||||
pub const Underline = enum(u3) {
|
||||
none = 0,
|
||||
single = 1,
|
||||
double = 2,
|
||||
curly = 3,
|
||||
dotted = 4,
|
||||
dashed = 5,
|
||||
};
|
||||
};
|
||||
|
||||
/// Parser parses the attributes from a list of SGR parameters.
|
||||
pub const Parser = struct {
|
||||
params: []const u16,
|
||||
idx: usize = 0,
|
||||
|
||||
/// True if the separator is a colon
|
||||
colon: bool = false,
|
||||
|
||||
/// Next returns the next attribute or null if there are no more attributes.
|
||||
pub fn next(self: *Parser) ?Attribute {
|
||||
if (self.idx > self.params.len) return null;
|
||||
|
||||
// Implicitly means unset
|
||||
if (self.params.len == 0) {
|
||||
self.idx += 1;
|
||||
return Attribute{ .unset = {} };
|
||||
}
|
||||
|
||||
const slice = self.params[self.idx..self.params.len];
|
||||
self.idx += 1;
|
||||
|
||||
// Our last one will have an idx be the last value.
|
||||
if (slice.len == 0) return null;
|
||||
|
||||
switch (slice[0]) {
|
||||
0 => return Attribute{ .unset = {} },
|
||||
|
||||
1 => return Attribute{ .bold = {} },
|
||||
|
||||
2 => return Attribute{ .faint = {} },
|
||||
|
||||
3 => return Attribute{ .italic = {} },
|
||||
|
||||
4 => blk: {
|
||||
if (self.colon) {
|
||||
switch (slice.len) {
|
||||
// 0 is unreachable because we're here and we read
|
||||
// an element to get here.
|
||||
0 => unreachable,
|
||||
|
||||
// 1 is possible if underline is the last element.
|
||||
1 => return Attribute{ .underline = .single },
|
||||
|
||||
// 2 means we have a specific underline style.
|
||||
2 => {
|
||||
self.idx += 1;
|
||||
switch (slice[1]) {
|
||||
0 => return Attribute{ .reset_underline = {} },
|
||||
1 => return Attribute{ .underline = .single },
|
||||
2 => return Attribute{ .underline = .double },
|
||||
3 => return Attribute{ .underline = .curly },
|
||||
4 => return Attribute{ .underline = .dotted },
|
||||
5 => return Attribute{ .underline = .dashed },
|
||||
|
||||
// For unknown underline styles, just render
|
||||
// a single underline.
|
||||
else => return Attribute{ .underline = .single },
|
||||
}
|
||||
},
|
||||
|
||||
// Colon-separated must only be 2.
|
||||
else => break :blk,
|
||||
}
|
||||
}
|
||||
|
||||
return Attribute{ .underline = .single };
|
||||
},
|
||||
|
||||
5 => return Attribute{ .blink = {} },
|
||||
|
||||
6 => return Attribute{ .blink = {} },
|
||||
|
||||
7 => return Attribute{ .inverse = {} },
|
||||
|
||||
8 => return Attribute{ .invisible = {} },
|
||||
|
||||
9 => return Attribute{ .strikethrough = {} },
|
||||
|
||||
22 => return Attribute{ .reset_bold = {} },
|
||||
|
||||
23 => return Attribute{ .reset_italic = {} },
|
||||
|
||||
24 => return Attribute{ .reset_underline = {} },
|
||||
|
||||
25 => return Attribute{ .reset_blink = {} },
|
||||
|
||||
27 => return Attribute{ .reset_inverse = {} },
|
||||
|
||||
28 => return Attribute{ .reset_invisible = {} },
|
||||
|
||||
29 => return Attribute{ .reset_strikethrough = {} },
|
||||
|
||||
30...37 => return Attribute{
|
||||
.@"8_fg" = @enumFromInt(slice[0] - 30),
|
||||
},
|
||||
|
||||
38 => if (slice.len >= 5 and slice[1] == 2) {
|
||||
self.idx += 4;
|
||||
|
||||
// In the 6-len form, ignore the 3rd param.
|
||||
const rgb = slice[2..5];
|
||||
|
||||
// We use @truncate because the value should be 0 to 255. If
|
||||
// it isn't, the behavior is undefined so we just... truncate it.
|
||||
return Attribute{
|
||||
.direct_color_fg = .{
|
||||
.r = @truncate(rgb[0]),
|
||||
.g = @truncate(rgb[1]),
|
||||
.b = @truncate(rgb[2]),
|
||||
},
|
||||
};
|
||||
} else if (slice.len >= 3 and slice[1] == 5) {
|
||||
self.idx += 2;
|
||||
return Attribute{
|
||||
.@"256_fg" = @truncate(slice[2]),
|
||||
};
|
||||
},
|
||||
|
||||
39 => return Attribute{ .reset_fg = {} },
|
||||
|
||||
40...47 => return Attribute{
|
||||
.@"8_bg" = @enumFromInt(slice[0] - 40),
|
||||
},
|
||||
|
||||
48 => if (slice.len >= 5 and slice[1] == 2) {
|
||||
self.idx += 4;
|
||||
|
||||
// We only support the 5-len form.
|
||||
const rgb = slice[2..5];
|
||||
|
||||
// We use @truncate because the value should be 0 to 255. If
|
||||
// it isn't, the behavior is undefined so we just... truncate it.
|
||||
return Attribute{
|
||||
.direct_color_bg = .{
|
||||
.r = @truncate(rgb[0]),
|
||||
.g = @truncate(rgb[1]),
|
||||
.b = @truncate(rgb[2]),
|
||||
},
|
||||
};
|
||||
} else if (slice.len >= 3 and slice[1] == 5) {
|
||||
self.idx += 2;
|
||||
return Attribute{
|
||||
.@"256_bg" = @truncate(slice[2]),
|
||||
};
|
||||
},
|
||||
|
||||
49 => return Attribute{ .reset_bg = {} },
|
||||
|
||||
58 => if (slice.len >= 5 and slice[1] == 2) {
|
||||
self.idx += 4;
|
||||
|
||||
// In the 6-len form, ignore the 3rd param. Otherwise, use it.
|
||||
const rgb = if (slice.len == 5) slice[2..5] else rgb: {
|
||||
// Consume one more element
|
||||
self.idx += 1;
|
||||
break :rgb slice[3..6];
|
||||
};
|
||||
|
||||
// We use @truncate because the value should be 0 to 255. If
|
||||
// it isn't, the behavior is undefined so we just... truncate it.
|
||||
return Attribute{
|
||||
.underline_color = .{
|
||||
.r = @truncate(rgb[0]),
|
||||
.g = @truncate(rgb[1]),
|
||||
.b = @truncate(rgb[2]),
|
||||
},
|
||||
};
|
||||
} else if (slice.len >= 3 and slice[1] == 5) {
|
||||
self.idx += 2;
|
||||
return Attribute{
|
||||
.@"256_underline_color" = @truncate(slice[2]),
|
||||
};
|
||||
},
|
||||
|
||||
59 => return Attribute{ .reset_underline_color = {} },
|
||||
|
||||
90...97 => return Attribute{
|
||||
// 82 instead of 90 to offset to "bright" colors
|
||||
.@"8_bright_fg" = @enumFromInt(slice[0] - 82),
|
||||
},
|
||||
|
||||
100...107 => return Attribute{
|
||||
.@"8_bright_bg" = @enumFromInt(slice[0] - 92),
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
|
||||
return Attribute{ .unknown = .{ .full = self.params, .partial = slice } };
|
||||
}
|
||||
};
|
||||
|
||||
fn testParse(params: []const u16) Attribute {
|
||||
var p: Parser = .{ .params = params };
|
||||
return p.next().?;
|
||||
}
|
||||
|
||||
fn testParseColon(params: []const u16) Attribute {
|
||||
var p: Parser = .{ .params = params, .colon = true };
|
||||
return p.next().?;
|
||||
}
|
||||
|
||||
test "sgr: Parser" {
|
||||
try testing.expect(testParse(&[_]u16{}) == .unset);
|
||||
try testing.expect(testParse(&[_]u16{0}) == .unset);
|
||||
|
||||
{
|
||||
const v = testParse(&[_]u16{ 38, 2, 40, 44, 52 });
|
||||
try testing.expect(v == .direct_color_fg);
|
||||
try testing.expectEqual(@as(u8, 40), v.direct_color_fg.r);
|
||||
try testing.expectEqual(@as(u8, 44), v.direct_color_fg.g);
|
||||
try testing.expectEqual(@as(u8, 52), v.direct_color_fg.b);
|
||||
}
|
||||
|
||||
try testing.expect(testParse(&[_]u16{ 38, 2, 44, 52 }) == .unknown);
|
||||
|
||||
{
|
||||
const v = testParse(&[_]u16{ 48, 2, 40, 44, 52 });
|
||||
try testing.expect(v == .direct_color_bg);
|
||||
try testing.expectEqual(@as(u8, 40), v.direct_color_bg.r);
|
||||
try testing.expectEqual(@as(u8, 44), v.direct_color_bg.g);
|
||||
try testing.expectEqual(@as(u8, 52), v.direct_color_bg.b);
|
||||
}
|
||||
|
||||
try testing.expect(testParse(&[_]u16{ 48, 2, 44, 52 }) == .unknown);
|
||||
}
|
||||
|
||||
test "sgr: Parser multiple" {
|
||||
var p: Parser = .{ .params = &[_]u16{ 0, 38, 2, 40, 44, 52 } };
|
||||
try testing.expect(p.next().? == .unset);
|
||||
try testing.expect(p.next().? == .direct_color_fg);
|
||||
try testing.expect(p.next() == null);
|
||||
try testing.expect(p.next() == null);
|
||||
}
|
||||
|
||||
test "sgr: bold" {
|
||||
{
|
||||
const v = testParse(&[_]u16{1});
|
||||
try testing.expect(v == .bold);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParse(&[_]u16{22});
|
||||
try testing.expect(v == .reset_bold);
|
||||
}
|
||||
}
|
||||
|
||||
test "sgr: italic" {
|
||||
{
|
||||
const v = testParse(&[_]u16{3});
|
||||
try testing.expect(v == .italic);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParse(&[_]u16{23});
|
||||
try testing.expect(v == .reset_italic);
|
||||
}
|
||||
}
|
||||
|
||||
test "sgr: underline" {
|
||||
{
|
||||
const v = testParse(&[_]u16{4});
|
||||
try testing.expect(v == .underline);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParse(&[_]u16{24});
|
||||
try testing.expect(v == .reset_underline);
|
||||
}
|
||||
}
|
||||
|
||||
test "sgr: underline styles" {
|
||||
{
|
||||
const v = testParseColon(&[_]u16{ 4, 2 });
|
||||
try testing.expect(v == .underline);
|
||||
try testing.expect(v.underline == .double);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParseColon(&[_]u16{ 4, 0 });
|
||||
try testing.expect(v == .reset_underline);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParseColon(&[_]u16{ 4, 1 });
|
||||
try testing.expect(v == .underline);
|
||||
try testing.expect(v.underline == .single);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParseColon(&[_]u16{ 4, 3 });
|
||||
try testing.expect(v == .underline);
|
||||
try testing.expect(v.underline == .curly);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParseColon(&[_]u16{ 4, 4 });
|
||||
try testing.expect(v == .underline);
|
||||
try testing.expect(v.underline == .dotted);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParseColon(&[_]u16{ 4, 5 });
|
||||
try testing.expect(v == .underline);
|
||||
try testing.expect(v.underline == .dashed);
|
||||
}
|
||||
}
|
||||
|
||||
test "sgr: blink" {
|
||||
{
|
||||
const v = testParse(&[_]u16{5});
|
||||
try testing.expect(v == .blink);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParse(&[_]u16{6});
|
||||
try testing.expect(v == .blink);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParse(&[_]u16{25});
|
||||
try testing.expect(v == .reset_blink);
|
||||
}
|
||||
}
|
||||
|
||||
test "sgr: inverse" {
|
||||
{
|
||||
const v = testParse(&[_]u16{7});
|
||||
try testing.expect(v == .inverse);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParse(&[_]u16{27});
|
||||
try testing.expect(v == .reset_inverse);
|
||||
}
|
||||
}
|
||||
|
||||
test "sgr: strikethrough" {
|
||||
{
|
||||
const v = testParse(&[_]u16{9});
|
||||
try testing.expect(v == .strikethrough);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParse(&[_]u16{29});
|
||||
try testing.expect(v == .reset_strikethrough);
|
||||
}
|
||||
}
|
||||
|
||||
test "sgr: 8 color" {
|
||||
var p: Parser = .{ .params = &[_]u16{ 31, 43, 90, 103 } };
|
||||
|
||||
{
|
||||
const v = p.next().?;
|
||||
try testing.expect(v == .@"8_fg");
|
||||
try testing.expect(v.@"8_fg" == .red);
|
||||
}
|
||||
|
||||
{
|
||||
const v = p.next().?;
|
||||
try testing.expect(v == .@"8_bg");
|
||||
try testing.expect(v.@"8_bg" == .yellow);
|
||||
}
|
||||
|
||||
{
|
||||
const v = p.next().?;
|
||||
try testing.expect(v == .@"8_bright_fg");
|
||||
try testing.expect(v.@"8_bright_fg" == .bright_black);
|
||||
}
|
||||
|
||||
{
|
||||
const v = p.next().?;
|
||||
try testing.expect(v == .@"8_bright_bg");
|
||||
try testing.expect(v.@"8_bright_bg" == .bright_yellow);
|
||||
}
|
||||
}
|
||||
|
||||
test "sgr: 256 color" {
|
||||
var p: Parser = .{ .params = &[_]u16{ 38, 5, 161, 48, 5, 236 } };
|
||||
try testing.expect(p.next().? == .@"256_fg");
|
||||
try testing.expect(p.next().? == .@"256_bg");
|
||||
try testing.expect(p.next() == null);
|
||||
}
|
||||
|
||||
test "sgr: 256 color underline" {
|
||||
var p: Parser = .{ .params = &[_]u16{ 58, 5, 9 } };
|
||||
try testing.expect(p.next().? == .@"256_underline_color");
|
||||
try testing.expect(p.next() == null);
|
||||
}
|
||||
|
||||
test "sgr: 24-bit bg color" {
|
||||
{
|
||||
const v = testParseColon(&[_]u16{ 48, 2, 1, 2, 3 });
|
||||
try testing.expect(v == .direct_color_bg);
|
||||
try testing.expectEqual(@as(u8, 1), v.direct_color_bg.r);
|
||||
try testing.expectEqual(@as(u8, 2), v.direct_color_bg.g);
|
||||
try testing.expectEqual(@as(u8, 3), v.direct_color_bg.b);
|
||||
}
|
||||
}
|
||||
|
||||
test "sgr: underline color" {
|
||||
{
|
||||
const v = testParseColon(&[_]u16{ 58, 2, 1, 2, 3 });
|
||||
try testing.expect(v == .underline_color);
|
||||
try testing.expectEqual(@as(u8, 1), v.underline_color.r);
|
||||
try testing.expectEqual(@as(u8, 2), v.underline_color.g);
|
||||
try testing.expectEqual(@as(u8, 3), v.underline_color.b);
|
||||
}
|
||||
|
||||
{
|
||||
const v = testParseColon(&[_]u16{ 58, 2, 0, 1, 2, 3 });
|
||||
try testing.expect(v == .underline_color);
|
||||
try testing.expectEqual(@as(u8, 1), v.underline_color.r);
|
||||
try testing.expectEqual(@as(u8, 2), v.underline_color.g);
|
||||
try testing.expectEqual(@as(u8, 3), v.underline_color.b);
|
||||
}
|
||||
}
|
||||
|
||||
test "sgr: reset underline color" {
|
||||
var p: Parser = .{ .params = &[_]u16{59} };
|
||||
try testing.expect(p.next().? == .reset_underline_color);
|
||||
}
|
||||
|
||||
test "sgr: invisible" {
|
||||
var p: Parser = .{ .params = &[_]u16{ 8, 28 } };
|
||||
try testing.expect(p.next().? == .invisible);
|
||||
try testing.expect(p.next().? == .reset_invisible);
|
||||
}
|
||||
|
||||
test "sgr: underline, bg, and fg" {
|
||||
var p: Parser = .{
|
||||
.params = &[_]u16{ 4, 38, 2, 255, 247, 219, 48, 2, 242, 93, 147, 4 },
|
||||
};
|
||||
{
|
||||
const v = p.next().?;
|
||||
try testing.expect(v == .underline);
|
||||
try testing.expectEqual(Attribute.Underline.single, v.underline);
|
||||
}
|
||||
{
|
||||
const v = p.next().?;
|
||||
try testing.expect(v == .direct_color_fg);
|
||||
try testing.expectEqual(@as(u8, 255), v.direct_color_fg.r);
|
||||
try testing.expectEqual(@as(u8, 247), v.direct_color_fg.g);
|
||||
try testing.expectEqual(@as(u8, 219), v.direct_color_fg.b);
|
||||
}
|
||||
{
|
||||
const v = p.next().?;
|
||||
try testing.expect(v == .direct_color_bg);
|
||||
try testing.expectEqual(@as(u8, 242), v.direct_color_bg.r);
|
||||
try testing.expectEqual(@as(u8, 93), v.direct_color_bg.g);
|
||||
try testing.expectEqual(@as(u8, 147), v.direct_color_bg.b);
|
||||
}
|
||||
{
|
||||
const v = p.next().?;
|
||||
try testing.expect(v == .underline);
|
||||
try testing.expectEqual(Attribute.Underline.single, v.underline);
|
||||
}
|
||||
}
|
||||
|
||||
test "sgr: direct color fg missing color" {
|
||||
// This used to crash
|
||||
var p: Parser = .{ .params = &[_]u16{ 38, 5 }, .colon = false };
|
||||
while (p.next()) |_| {}
|
||||
}
|
||||
|
||||
test "sgr: direct color bg missing color" {
|
||||
// This used to crash
|
||||
var p: Parser = .{ .params = &[_]u16{ 48, 5 }, .colon = false };
|
||||
while (p.next()) |_| {}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const color = @import("../color.zig");
|
||||
const sgr = @import("../sgr.zig");
|
||||
const color = @import("color.zig");
|
||||
const sgr = @import("sgr.zig");
|
||||
const page = @import("page.zig");
|
||||
const size = @import("size.zig");
|
||||
const Offset = size.Offset;
|
62
src/terminal2/x11_color.zig
Normal file
62
src/terminal2/x11_color.zig
Normal file
@ -0,0 +1,62 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const RGB = @import("color.zig").RGB;
|
||||
|
||||
/// The map of all available X11 colors.
|
||||
pub const map = colorMap() catch @compileError("failed to parse rgb.txt");
|
||||
|
||||
fn colorMap() !type {
|
||||
@setEvalBranchQuota(100_000);
|
||||
|
||||
const KV = struct { []const u8, RGB };
|
||||
|
||||
// The length of our data is the number of lines in the rgb file.
|
||||
const len = std.mem.count(u8, data, "\n");
|
||||
var kvs: [len]KV = undefined;
|
||||
|
||||
// Parse the line. This is not very robust parsing, because we expect
|
||||
// a very exact format for rgb.txt. However, this is all done at comptime
|
||||
// so if our data is bad, we should hopefully get an error here or one
|
||||
// of our unit tests will catch it.
|
||||
var iter = std.mem.splitScalar(u8, data, '\n');
|
||||
var i: usize = 0;
|
||||
while (iter.next()) |line| {
|
||||
if (line.len == 0) continue;
|
||||
const r = try std.fmt.parseInt(u8, std.mem.trim(u8, line[0..3], " "), 10);
|
||||
const g = try std.fmt.parseInt(u8, std.mem.trim(u8, line[4..7], " "), 10);
|
||||
const b = try std.fmt.parseInt(u8, std.mem.trim(u8, line[8..11], " "), 10);
|
||||
const name = std.mem.trim(u8, line[12..], " \t\n");
|
||||
kvs[i] = .{ name, .{ .r = r, .g = g, .b = b } };
|
||||
i += 1;
|
||||
}
|
||||
assert(i == len);
|
||||
|
||||
return std.ComptimeStringMapWithEql(
|
||||
RGB,
|
||||
kvs,
|
||||
std.comptime_string_map.eqlAsciiIgnoreCase,
|
||||
);
|
||||
}
|
||||
|
||||
/// This is the rgb.txt file from the X11 project. This was last sourced
|
||||
/// from this location: https://gitlab.freedesktop.org/xorg/app/rgb
|
||||
/// This data is licensed under the MIT/X11 license while this Zig file is
|
||||
/// licensed under the same license as Ghostty.
|
||||
const data = @embedFile("res/rgb.txt");
|
||||
|
||||
test {
|
||||
const testing = std.testing;
|
||||
try testing.expectEqual(null, map.get("nosuchcolor"));
|
||||
try testing.expectEqual(RGB{ .r = 255, .g = 255, .b = 255 }, map.get("white").?);
|
||||
try testing.expectEqual(RGB{ .r = 0, .g = 250, .b = 154 }, map.get("medium spring green"));
|
||||
try testing.expectEqual(RGB{ .r = 34, .g = 139, .b = 34 }, map.get("ForestGreen"));
|
||||
try testing.expectEqual(RGB{ .r = 34, .g = 139, .b = 34 }, map.get("FoReStGReen"));
|
||||
try testing.expectEqual(RGB{ .r = 0, .g = 0, .b = 0 }, map.get("black"));
|
||||
try testing.expectEqual(RGB{ .r = 255, .g = 0, .b = 0 }, map.get("red"));
|
||||
try testing.expectEqual(RGB{ .r = 0, .g = 255, .b = 0 }, map.get("green"));
|
||||
try testing.expectEqual(RGB{ .r = 0, .g = 0, .b = 255 }, map.get("blue"));
|
||||
try testing.expectEqual(RGB{ .r = 255, .g = 255, .b = 255 }, map.get("white"));
|
||||
try testing.expectEqual(RGB{ .r = 124, .g = 252, .b = 0 }, map.get("lawngreen"));
|
||||
try testing.expectEqual(RGB{ .r = 0, .g = 250, .b = 154 }, map.get("mediumspringgreen"));
|
||||
try testing.expectEqual(RGB{ .r = 34, .g = 139, .b = 34 }, map.get("forestgreen"));
|
||||
}
|
Reference in New Issue
Block a user