summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/e2e_harness.zig28
-rw-r--r--test/fs.py47
-rw-r--r--test/gui-goldens.txt8
-rw-r--r--test/gui_monkey.py179
-rw-r--r--test/mode.zig2
-rw-r--r--test/monkey9p.py16
-rw-r--r--test/output.zig6
-rw-r--r--test/panes.zig68
-rw-r--r--test/perf-baseline-gui-Debug.json2
-rw-r--r--test/perf-baseline-tty-Debug.json2
-rw-r--r--test/perf-baseline-tty-ReleaseFast.json2
-rw-r--r--test/perf.zig26
-rw-r--r--test/snapshots/theme.golden12
-rw-r--r--test/snapshots/themesel.golden18
14 files changed, 336 insertions, 80 deletions
diff --git a/test/e2e_harness.zig b/test/e2e_harness.zig
index eaa19b43..9e67c49f 100644
--- a/test/e2e_harness.zig
+++ b/test/e2e_harness.zig
@@ -64,6 +64,8 @@ pub const Harness = struct {
/// when true, print the captured screen state after each pump/waitFor and on
/// every assertion, so live test runs can be inspected (zig build test -Dtrace).
trace: bool = false,
+ /// How much of a `CSI 5 n` the output has ended on (`feed`).
+ status_request: u8 = 0,
/// every raw byte the app has emitted (accumulated in pump). Lets tests assert
/// on control sequences the emulator consumes and never renders (e.g. OSC 52
/// clipboard writes). Capture is bounded explicitly so a runaway child cannot
@@ -147,6 +149,24 @@ pub const Harness = struct {
/// Read pty output and feed it to our ghostty terminal for `ms` ms. After
/// this, the grid reflects everything the app rendered so far.
+ /// The app's output into the emulator. The one query answered is the
+ /// device status request (`CSI 5 n` → `CSI 0 n`), as every terminal
+ /// does: the tty asks it after each frame and draws no other until the
+ /// answer (FrameAck in src/tty/tty.zig).
+ fn feed(self: *Harness, bytes: []const u8) void {
+ self.stream.nextSlice(bytes);
+ const request = "\x1b[5n";
+ for (bytes) |b| {
+ if (b == request[self.status_request]) {
+ self.status_request += 1;
+ } else self.status_request = if (b == 0x1b) 1 else 0;
+ if (self.status_request == request.len) {
+ self.status_request = 0;
+ _ = libc.write(self.master, "\x1b[0n", 4);
+ }
+ }
+ }
+
pub fn pump(self: *Harness, ms: i64) !void {
const deadline = nowMs() + ms;
var buf: [4096]u8 = undefined;
@@ -157,7 +177,7 @@ pub const Harness = struct {
const n = posix.read(self.master, &buf) catch break;
if (n == 0) break;
self.recordRaw(buf[0..n]);
- self.stream.nextSlice(buf[0..n]);
+ self.feed(buf[0..n]);
}
}
if (self.trace) self.traceScreen("pump");
@@ -176,7 +196,7 @@ pub const Harness = struct {
const n = posix.read(self.master, &buf) catch return false;
if (n == 0) return false;
self.recordRaw(buf[0..n]);
- self.stream.nextSlice(buf[0..n]);
+ self.feed(buf[0..n]);
return true;
}
@@ -243,7 +263,7 @@ pub const Harness = struct {
const n = posix.read(self.master, &buf) catch break;
if (n == 0) break;
self.recordRaw(buf[0..n]);
- self.stream.nextSlice(buf[0..n]);
+ self.feed(buf[0..n]);
}
const text = try self.screenText();
defer self.gpa.free(text);
@@ -270,7 +290,7 @@ pub const Harness = struct {
const n = posix.read(self.master, &buf) catch break;
if (n == 0) break;
self.recordRaw(buf[0..n]);
- self.stream.nextSlice(buf[0..n]);
+ self.feed(buf[0..n]);
if (std.mem.indexOf(u8, self.raw.items, needle) != null) return true;
}
return false;
diff --git a/test/fs.py b/test/fs.py
index db10f32d..06643385 100644
--- a/test/fs.py
+++ b/test/fs.py
@@ -953,6 +953,20 @@ def test(binary, quic=False):
with Client(address, msize=256) as small:
assert set(small.list('/os' + str(listing))) == {
f'entry-{number:02}' for number in range(24)}
+ # 64 KiB frames: a ctl line with no newline, whole in its one
+ # Twrite, runs with it however long -- the cutoff for a write
+ # that may go on is the frame the client asked for, less 24.
+ with Client(address, msize=65536) as big:
+ assert big.msize == 65536
+ ctl = big.open('/pane/1/ctl', 1)
+ line = b'bogus' + b'x' * 20000
+ try:
+ big.rpc(118, struct.pack('<IQI', ctl, 0, len(line)) + line)
+ except OSError:
+ pass # refused with its write (a line over 1024 bytes), not held
+ else:
+ raise AssertionError('a whole 20000-byte line waited for a newline')
+ big.close(ctl)
frozen = client.open('/screen')
before = bytearray(client.read_fid(frozen, count=31))
@@ -1261,10 +1275,43 @@ def test(binary, quic=False):
assert time.monotonic() - deleted < .05, time.monotonic() - deleted
assert second.wait(timeout=5) == 0
assert edited.read_bytes() == b'edited\n'
+ # A name not there yet opens a pane named it, as acme's B does,
+ # and Save creates it: git's and fish's temporary files are.
+ fresh = root / 'wait new.txt'
+ before = serials()
+ made = subprocess.Popen([binary, '--wait', fresh.name], cwd=root, env=env,
+ stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
+ # The launch makes the pane (pane/new), then names it: wait for
+ # the name, not the pane.
+ deadline = time.monotonic() + 3
+ while str(fresh).encode() not in client.read('/index'):
+ assert made.poll() is None, made.stderr.read()
+ assert time.monotonic() < deadline, 'a new name opened no pane named it'
+ time.sleep(.01)
+ named, = serials() - before
+ client.write(f'/pane/{named}/body', b'new\n')
+ execute(client, named, 'Save')
+ client.remove(f'/pane/{named}')
+ assert made.wait(timeout=5) == 0
+ assert fresh.read_bytes() == b'new\n'
orphan = launch('--wait')
assert orphan.poll() is None
assert orphan.wait(timeout=5) == 1
+ # A launch with no terminal to draw on (a pty that is no one's
+ # controlling terminal) says so and exits 1, no error trace.
+ master, slave = os.openpty()
+ try:
+ env = {k: v for k, v in os.environ.items() if not k.startswith('PARDES_')}
+ env.update(HOME=str(root), XDG_RUNTIME_DIR=str(root), TERM='xterm-256color')
+ result = subprocess.run([binary, '--tty', 'x.txt'], cwd=root, env=env, stdin=slave, stdout=slave,
+ stderr=subprocess.PIPE, start_new_session=True, timeout=10)
+ finally:
+ os.close(master)
+ os.close(slave)
+ assert result.returncode == 1, result
+ assert b'pardes: no terminal to draw on' in result.stderr and b'.zig:' not in result.stderr, result.stderr
+
for tty in [False, True]:
name = 'lsp-tty' if tty else 'lsp-detached'
with session(binary, root, name, tty=tty) as (client, _):
diff --git a/test/gui-goldens.txt b/test/gui-goldens.txt
index 2b364b37..2d3c1a77 100644
--- a/test/gui-goldens.txt
+++ b/test/gui-goldens.txt
@@ -11,7 +11,7 @@
12-multiline-tag ddd7d775a246e91b88f0eff98b6f9bea8aa5d63dbfbe931bd830ccb9086bd5c4
13-image f609043fc0fcdb55369b836b61fcbc631f786cc2295a5ddd72c0d9a59b177a60
14-theme-ink 881b7e99f38ad4aa3c46d2c61e20598010175d7858712a67fa6638fc29f12e14
-15-theme-acme-light ca5252f5117ac8d6c679cef917c9f316c9ca311fc7a8dcbac2796b4fc04d3b64
-16-debug 591bb7c45b4b9c46cd2458330b4fa3962afc6dac827fa6f5e20e927a72c586e6
-17-terminal 38299427891e31de0b5597159501c161474620eb26fd7708a6396f0fd685209e
-18-mid-transition 5d3e37d874988b460297e18df9508a77b602d5f675e28983c2c793871d4301f6
+15-theme-acme-light 9648633a897416b14849e0dbb857a98d4c4b7d46aa0e799ab5e302fbe240652f
+16-debug be4d09d67b4293dd038bca57960d77d65d1f9f0281433e9cb9cbba654418fc15
+17-terminal 440cc30bb1f5086e572840f6ccd211dda1e266d77fe02f997e51d92f52494d3c
+18-mid-transition e1f6459cb918050e4f4e8cd485d924f022e75bc19d80c79f710117b18168e8e8
diff --git a/test/gui_monkey.py b/test/gui_monkey.py
new file mode 100644
index 00000000..2b32f050
--- /dev/null
+++ b/test/gui_monkey.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python3
+"""A monkey over the GUI's effects: for each Motion x Lift x Bloom, a hidden
+test window gets a random burst of clicks, drags, wheels, keys, new and
+joined columns, opened files and theme changes, then must settle (its
+frames stop changing) with no crash record and its process alive.
+
+gui_monkey.py <pardes-gui> [outdir] [--seed N] [--steps N]; `zig build
+monkey-gui -Dplatform=gui -- [--seed N] [--steps N]` runs it on the built
+window. Exits 1 when any combination failed; what failed is in outdir.
+"""
+import argparse
+import hashlib
+import itertools
+import os
+import random
+import shutil
+import sys
+import tempfile
+import time
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).resolve().parent))
+from fs import session # noqa: E402
+
+MOTIONS = ['off', 'crisp', 'smooth', 'bouncy', 'playful']
+LIFTS = ['off', 'shadow', 'rim', 'auto']
+BLOOMS = ['0', '2']
+THEMES = ['lapis', 'forge', 'acme', 'dusk', 'daybreak']
+W, H = 1280, 840
+
+
+def settled(cap, timeout=20):
+ latest = cap / 'latest.ppm'
+ previous = None
+ deadline = time.monotonic() + timeout
+ while time.monotonic() < deadline:
+ time.sleep(.4)
+ try:
+ data = latest.read_bytes()
+ except FileNotFoundError:
+ continue
+ digest = hashlib.sha256(data).hexdigest()
+ if digest == previous:
+ return True
+ previous = digest
+ return False
+
+
+def run(binary, out, motion, lift, bloom, seed, steps):
+ rng = random.Random(seed)
+ root = Path(tempfile.mkdtemp(prefix='pardes-gui-monkey-'))
+ cap = root / 'cap'
+ cap.mkdir()
+ (root / 'config/pardes').mkdir(parents=True)
+ (root / 'config/pardes/init').write_text(
+ f'Shell /bin/sh\nMessageLinger 0\nMotion {motion}\nLift {lift}\nBloom {bloom}\nPanelSlide\nSelectionGlow on\nHoverGlow on\n')
+ src = root / 'a.zig'
+ src.write_text(''.join(f'pub fn f{n}(x: u32) u32 {{ return x * {n}; }} // a line\n' for n in range(200)))
+ for name in ('b.txt', 'c.txt'):
+ (root / name).write_text(''.join(f'{name} line {n} with some words\n' for n in range(80)))
+ display = Path(os.environ.get('XDG_RUNTIME_DIR', '/run/user/%d' % os.getuid())) / os.environ.get('WAYLAND_DISPLAY', 'wayland-0')
+ env = {'PARDES_TEST': '1', 'PARDES_TEST_CLOCK': '1', 'PARDES_TEST_COLS': '80', 'PARDES_TEST_ROWS': '28', 'PARDES_TEST_PAD': '0',
+ 'PARDES_TEST_CAPTURE_DIR': str(cap), 'SDL_VIDEODRIVER': 'wayland', 'WAYLAND_DISPLAY': str(display)}
+ name = 'monkey'
+ launch = ['-c', 'stty cols 80 rows 28; exec "$@"', name, str(binary.resolve()), '--9p=' + name, str(src)]
+ handles = {}
+ verdict = 'ok'
+ log = []
+ try:
+ with session('/bin/sh', root, name, socket_name=name, tty=True, launch=launch, inherited=env, terminal=handles) as (client, _):
+ fd = handles['input_fd']
+ time.sleep(1.0)
+
+ def mouse(kind, button, x, y):
+ os.write(fd, f'\x1b]777;mouse;{kind};{button};{x};{y}\x07'.encode())
+
+ def first():
+ rows = client.read('/index').decode().splitlines()
+ return int(rows[0].split()[0]) if rows else None
+
+ for _ in range(steps):
+ x, y = rng.randrange(W), rng.randrange(H)
+ action = rng.randrange(12)
+ try:
+ if action in (0, 1):
+ mouse('down', 1, x, y); mouse('up', 1, x, y); log.append(f'click {x},{y}')
+ elif action == 2:
+ x2, y2 = rng.randrange(W), rng.randrange(H)
+ mouse('down', 1, x, y)
+ for i in range(1, 6):
+ mouse('motion', 1, x + (x2 - x) * i // 5, y + (y2 - y) * i // 5)
+ mouse('up', 1, x2, y2); log.append(f'drag {x},{y}->{x2},{y2}')
+ elif action == 3:
+ amount = rng.choice([-6, -2, 2, 6])
+ os.write(fd, f'\x1b]777;mouse;wheel;{amount};{x};{y}\x07'.encode()); log.append(f'wheel {amount}')
+ elif action == 4:
+ mouse('motion', 0, x, y); log.append(f'hover {x},{y}')
+ elif action == 5:
+ client.write('/ctl', b'Newcol\n'); log.append('Newcol')
+ elif action == 6:
+ serial = first()
+ if serial is not None:
+ target = root / rng.choice(['b.txt', 'c.txt', 'a.zig'])
+ client.write(f'/pane/{serial}/look', f'{target}\n'.encode()); log.append(f'look {target.name}')
+ elif action == 7:
+ serial = first()
+ if serial is not None:
+ # Delcol too: the last column's going leaves an
+ # empty window now (acme's), not a quit.
+ word = rng.choice(['Joincol', 'Collapse', 'Delcol'])
+ try:
+ client.write(f'/pane/{serial}/exec', (word + '\n').encode())
+ except OSError:
+ pass
+ log.append(word)
+ else:
+ # An empty window: pane/new makes its column and a pane.
+ try:
+ client.read('/pane/new')
+ except OSError:
+ pass
+ log.append('pane/new (empty window)')
+ elif action == 8:
+ os.write(fd, rng.choice([b'j', b'k', b'10j', b'gg', b'w', b'%', b'v5j', b'\x1b']))
+ log.append('keys')
+ elif action == 9:
+ theme = rng.choice(THEMES)
+ client.write('/ctl', f'Theme {theme}\n'.encode()); log.append(f'Theme {theme}')
+ elif action == 10:
+ mouse('down', 3, x, y); mouse('up', 3, x, y); log.append(f'look-click {x},{y}')
+ else:
+ time.sleep(rng.choice([0, .05, .2])); log.append('pause')
+ except OSError as why:
+ log.append(f'refused: {why}')
+ time.sleep(.03)
+ if not settled(cap):
+ verdict = 'never settled'
+ try:
+ os.kill(handles['pid'], 0)
+ except OSError:
+ verdict = 'process gone'
+ client.read('/index')
+ except Exception as why: # noqa: BLE001
+ verdict = f'failed: {type(why).__name__}: {str(why)[:300]}'
+ crashes = list((root / 'config').rglob('*crash*'))
+ if crashes:
+ verdict = 'crash record: ' + ', '.join(str(c) for c in crashes)
+ label = f'{motion}-{lift}-bloom{bloom}'
+ if verdict != 'ok':
+ (out / f'{label}.log').write_text('\n'.join(log) + '\n' + verdict + '\n')
+ for c in crashes:
+ shutil.copy(c, out / f'{label}-{c.name}')
+ if (cap / 'latest.ppm').exists():
+ shutil.copy(cap / 'latest.ppm', out / f'{label}-last.ppm')
+ shutil.rmtree(root, ignore_errors=True)
+ return label, verdict
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument('binary', type=Path)
+ ap.add_argument('out', type=Path, nargs='?')
+ ap.add_argument('--seed', type=int, default=1)
+ ap.add_argument('--steps', type=int, default=60)
+ args = ap.parse_args()
+ if args.out is None:
+ args.out = Path(tempfile.mkdtemp(prefix='pardes-gui-monkey-out-'))
+ args.out.mkdir(parents=True, exist_ok=True)
+ bad = 0
+ for n, (motion, lift, bloom) in enumerate(itertools.product(MOTIONS, LIFTS, BLOOMS)):
+ label, verdict = run(args.binary, args.out, motion, lift, bloom, args.seed * 1000 + n, args.steps)
+ print(f'{label}: {verdict}', flush=True)
+ bad += verdict != 'ok'
+ print(f'{bad} of {len(MOTIONS) * len(LIFTS) * len(BLOOMS)} combinations failed; logs in {args.out}', flush=True)
+ sys.exit(1 if bad else 0)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/test/mode.zig b/test/mode.zig
index cba93c30..767cda93 100644
--- a/test/mode.zig
+++ b/test/mode.zig
@@ -26,7 +26,7 @@ fn customTag(pane: *pardes.Pane, text: []const u8) !void {
}
test "Mode cycles terminal modes and keeps legacy toggle and custom tags" {
- if (comptime !panes.Terminal.enabled) return error.SkipZigTest;
+ if (comptime !panes.terminal.enabled) return error.SkipZigTest;
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100 });
defer p.deinit();
p.presentation.enabled = false;
diff --git a/test/monkey9p.py b/test/monkey9p.py
index c0e99fb8..ee0b0ebc 100644
--- a/test/monkey9p.py
+++ b/test/monkey9p.py
@@ -641,11 +641,11 @@ def op_write(sess, op):
ok, body = w.call(TOPEN, struct.pack('<IB', fid, mode), timeout)
res.add(open_kind(path, mode), path, ok, None if ok else body)
sizes = chunking(op.get('chunks', 'whole'), len(data), w.msize - 23)
- allowances(res, path, data, sizes, w.msize - 24)
opened = ok
sent = data
if ok:
offset = 0
+ took = []
for size in sizes:
piece = data[offset:offset + size]
ok, body = w.call(TWRITE, struct.pack('<IQI', fid, op.get('offset', offset), len(piece)) + piece, timeout)
@@ -654,8 +654,13 @@ def op_write(sess, op):
res.add('write' if mode & 3 else 'write-badfid', path, ok, None if ok else body)
offset += size
sent = data[:offset]
+ if ok:
+ took.append(size)
if not ok and op.get('stop_on_error', True):
break
+ # Misses are allowed the Twrites that succeeded: a line written a
+ # byte a Twrite is a look a byte, each its own write.
+ allowances(res, path, data[:sum(took)], took, w.msize - 24)
ok, body = w.call(TCLUNK, struct.pack('<I', fid), timeout)
res.add('clunk-write' if mode & 3 else 'clunk', path, ok, None if ok else body)
# What the open holds when it closes: the last line written, if unended.
@@ -850,11 +855,12 @@ def op_hwrite(sess, op):
fid, path, mode, off, _ = h
data = dec(op['data'])[:sess.wire.msize - 23] # one Twrite
h[4] = data
- allowances(res, path, data, [len(data)], sess.wire.msize - 24)
ok, body = sess.wire.call(TWRITE, struct.pack('<IQI', fid, off[0], len(data)) + data,
SLOW_TIMEOUT if slow(data) else TIMEOUT)
res.add('write' if mode & 3 else 'write-badfid', path, ok, None if ok else body)
if ok:
+ allowances(res, path, data, [len(data)], sess.wire.msize - 24)
+ if ok:
off[0] += len(data)
return res
@@ -1498,9 +1504,9 @@ def one_failure_rule(ctx, res):
and w[1].rstrip('/') != '/pane/new']
errs = occurrences(res.window, 'err')
msgs = occurrences(res.window, 'msg')
- # Only for a write that succeeded: a failed one logs its one err.
- failed_paths = {w[1] for w in failed}
- allowed = sum(r[3] for r in res.requests if r[0] == 'allow' and r[1] not in failed_paths)
+ # Allowed only the Twrites that succeeded (allowances): a failed one
+ # logs its one err.
+ allowed = sum(r[3] for r in res.requests if r[0] == 'allow')
# fs.md 'A write of command lines ... what is left when it closes runs at
# the close ... and its failure is in the log alone'.
closes = [r for r in res.requests if r[0] == 'close-runs']
diff --git a/test/output.zig b/test/output.zig
index e5f87fb7..1b1b80c9 100644
--- a/test/output.zig
+++ b/test/output.zig
@@ -472,7 +472,7 @@ test "Mini syntax colors survive toggles themes and rendering without source acc
defer p.deinit();
try p.panes[0].?.setOwnedCwd(dir);
p.settings.colors = false;
- try panes.Mini.open(p, 0, "demo.zig");
+ try panes.mini.open(p, 0, "demo.zig");
const id = p.active;
const mini = p.panes[id].?;
const keyword = std.mem.indexOfScalar(u8, mini.file.?.mini.?.colors, @intFromEnum(pardes.syntax.Syn.keyword)) orelse return error.MissingKeywordColor;
@@ -522,12 +522,12 @@ test "Mini failures leave its existing snapshot and focus unchanged" {
const source = try p.setTestFile("abc\n");
var path_buf: [128]u8 = undefined;
const path = try std.fmt.bufPrint(&path_buf, "/virtual/pane/{d}/body", .{source.serial});
- try panes.Mini.open(p, 0, path);
+ try panes.mini.open(p, 0, path);
const id = p.active;
const mini = p.panes[id].?;
const body = mini.file.?.content.ptr;
const next = p.freeSlot();
- try std.testing.expectError(error.FileNotFound, panes.Mini.open(p, 0, "/virtual/does-not-exist"));
+ try std.testing.expectError(error.FileNotFound, panes.mini.open(p, 0, "/virtual/does-not-exist"));
try std.testing.expectEqual(id, p.active);
try std.testing.expectEqual(body, mini.file.?.content.ptr);
try std.testing.expectEqual(next, p.freeSlot());
diff --git a/test/panes.zig b/test/panes.zig
index c1ae1666..f7ca9fc5 100644
--- a/test/panes.zig
+++ b/test/panes.zig
@@ -864,7 +864,7 @@ const TtySelectionTests = struct {
}
test "tty selection yields to a new modal cursor selection" {
- if (comptime !panes.Terminal.enabled) return error.SkipZigTest;
+ if (comptime !panes.terminal.enabled) return error.SkipZigTest;
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 40, .rows = 12 });
defer p.deinit();
p.presentation.enabled = false;
@@ -889,7 +889,7 @@ const TtySelectionTests = struct {
}
test "tty selection preserves its exact stream through yank clipboard and mode changes" {
- if (comptime !panes.Terminal.enabled) return error.SkipZigTest;
+ if (comptime !panes.terminal.enabled) return error.SkipZigTest;
for ([_]bool{ false, true }) |prompt| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 40, .rows = 12 });
defer p.deinit();
@@ -949,7 +949,7 @@ const TtySelectionTests = struct {
}
test "tty selection pastes across panes once with existing bracket and mouse protocols" {
- if (comptime !panes.Terminal.enabled) return error.SkipZigTest;
+ if (comptime !panes.terminal.enabled) return error.SkipZigTest;
for ([_]bool{ false, true }) |bracketed| {
for ([_]bool{ false, true }) |reports_mouse| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 40, .rows = 24 });
@@ -1028,14 +1028,14 @@ const TtySelectionTests = struct {
// than the text goes and reads the desktop clipboard on its own account,
// and a coding agent reading it is looking for an image, not for words.
test "raw tty paste chords type at the program instead of reaching it as keys" {
- if (comptime !panes.Terminal.enabled) return error.SkipZigTest;
+ if (comptime !panes.terminal.enabled) return error.SkipZigTest;
for ([_]bool{ false, true }) |bracketed| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 40, .rows = 12 });
defer p.deinit();
const pane = p.panes[0].?;
try std.testing.expectEqual(panes.Text.Mode.tty, pane.body.mode);
if (bracketed) p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[?2004h" } });
- try std.testing.expectEqual(bracketed, panes.Terminal.bracketedPaste(pane));
+ try std.testing.expectEqual(bracketed, panes.terminal.bracketedPaste(pane));
p.registers.put(p.gpa, '"', "one\ntwo", 0, 1, true); // what a `y` anywhere left behind
var buf: [256]u8 = undefined;
_ = childInput(p, &buf);
@@ -1554,7 +1554,7 @@ const TagNameTintTests = struct {
try std.testing.expectEqual(pardes.Color{ .rgb = p.theme().sel_fg }, surface.at(x + 1, y).style.fg);
}
- test "tag filename tint legacy themes retain distinct active foreground" {
+ test "tag filename tint legacy themes keep their active foreground and tint the name with a hue of their own" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 70, .rows = 14 });
defer p.deinit();
const pane = try p.setTestFile("body\n");
@@ -1574,8 +1574,12 @@ const TagNameTintTests = struct {
const x = p.rects[0].x + pardes.TAG_TEXT_INSET;
const y = tagY(p, p.rects[0]);
const fg = if (active) active_fg else tag_fg;
+ // The directory in the ink; the name tinted, never in it.
+ const chrome = p.chromeTheme();
+ const tint = if (active) chrome.tag_active_name_fg else chrome.tag_name_fg;
+ try std.testing.expect(!std.meta.eql(tint, fg));
try std.testing.expectEqual(pardes.Color{ .rgb = fg }, surface.at(x + 1, y).style.fg);
- try std.testing.expectEqual(pardes.Color{ .rgb = fg }, surface.at(x + 5, y).style.fg);
+ try std.testing.expectEqual(pardes.Color{ .rgb = tint }, surface.at(x + 5, y).style.fg);
}
}
};
@@ -1829,7 +1833,7 @@ test "terminal overlay recoloring never materializes unrelated history" {
_ = try p.render(frame.allocator());
try testing.expect(p.shell_rows.pane == null);
- pane.ovl.?.row = panes.Terminal.gridOffset(pane);
+ pane.ovl.?.row = panes.terminal.gridOffset(pane);
p.gpa.free(pane.ovl.?.text);
pane.ovl.?.text = try p.gpa.dupe(u8, "caf\xc3\xa9 \xe7\x95\x8c");
_ = frame.reset(.retain_capacity);
@@ -1843,7 +1847,7 @@ test "terminal overlay recoloring never materializes unrelated history" {
}
test "terminal output keeps following new rows after scrollback eviction" {
- if (comptime !panes.Terminal.enabled) return error.SkipZigTest;
+ if (comptime !panes.terminal.enabled) return error.SkipZigTest;
const gpa = std.testing.allocator;
const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = 80, .rows = 24 });
defer p.deinit();
@@ -1859,9 +1863,9 @@ test "terminal output keeps following new rows after scrollback eviction" {
for (0..3) |phase| {
if (phase > 0) {
pane.body.mode = .normal;
- panes.Terminal.scrollGrid(pane, -30);
+ panes.terminal.scrollGrid(pane, -30);
try std.testing.expect(pages.scrollbar().offset < pages.scrollbar().total - pane.rows);
- panes.Terminal.enterTty(p, 0);
+ panes.terminal.enterTty(p, 0);
}
for (phase * 20_000..(phase + 1) * 20_000) |n| {
var buf: [64]u8 = undefined;
@@ -1874,7 +1878,7 @@ test "terminal output keeps following new rows after scrollback eviction" {
try std.testing.expect(pages.page_size <= pages.maxSize());
const sb = pages.scrollbar();
try std.testing.expectEqual(sb.total - pane.rows, sb.offset);
- const full = try panes.Terminal.screenTextAlloc(pane, gpa);
+ const full = try panes.terminal.screenTextAlloc(pane, gpa);
defer gpa.free(full);
var last_buf: [16]u8 = undefined;
const last = try std.fmt.bufPrint(&last_buf, "row-{d:0>5}", .{(phase + 1) * 20_000 - 1});
@@ -1904,7 +1908,7 @@ test "terminal output keeps following new rows after scrollback eviction" {
}
test "terminal edit stays on its surviving row when old scrollback is evicted" {
- if (comptime !panes.Terminal.enabled) return error.SkipZigTest;
+ if (comptime !panes.terminal.enabled) return error.SkipZigTest;
const gpa = std.testing.allocator;
const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = 80, .rows = 24 });
defer p.deinit();
@@ -1942,7 +1946,7 @@ test "terminal edit stays on its surviving row when old scrollback is evicted" {
}
}
try std.testing.expect(removed > 0 and removed < original_row);
- const full = try panes.Terminal.screenTextAlloc(pane, gpa);
+ const full = try panes.terminal.screenTextAlloc(pane, gpa);
defer gpa.free(full);
const row = original_row - @as(i32, @intCast(removed));
try std.testing.expectEqualStrings(last, modal.lineSlice(full, @intCast(row)));
@@ -1952,7 +1956,7 @@ test "terminal edit stays on its surviving row when old scrollback is evicted" {
}
test "terminal edits and undo survive partial history clearing and whole-screen loss" {
- if (comptime !panes.Terminal.enabled) return error.SkipZigTest;
+ if (comptime !panes.terminal.enabled) return error.SkipZigTest;
const gpa = std.testing.allocator;
const Action = enum { clear_history, partial_clear_history, reset_primary, reset_alternate, evict_all, hidden_undo };
for (std.enums.values(Action)) |action| {
@@ -1974,7 +1978,7 @@ test "terminal edits and undo survive partial history clearing and whole-screen
pane.body.cur_col = 4;
pane.body.cur_pinned = true;
pane.body.vsel = .{ .active = true, .row = row, .col = 1 };
- panes.Terminal.pushUndo(p, pane);
+ panes.terminal.pushUndo(p, pane);
const overlay = pane.ovl.?;
pane.body.ed_redo[0] = .{
.ovl = .{ .row = overlay.row, .rows = overlay.rows, .text = try gpa.dupe(u8, overlay.text) },
@@ -1986,7 +1990,7 @@ test "terminal edits and undo survive partial history clearing and whole-screen
if (action == .hidden_undo) {
gpa.free(pane.ovl.?.text);
pane.ovl = null;
- panes.Terminal.enterTty(p, 0);
+ panes.terminal.enterTty(p, 0);
}
const bytes = switch (action) {
.clear_history, .partial_clear_history => "\x1b[3J",
@@ -2002,20 +2006,20 @@ test "terminal edits and undo survive partial history clearing and whole-screen
try std.testing.expectEqual(expected + 1, pane.body.cur_row);
try std.testing.expectEqual(expected, pane.body.vsel.row);
}
- for ([_]panes.Terminal.Snapshot{ pane.body.ed_undo[0], pane.body.ed_redo[0] }) |snapshot| {
+ for ([_]panes.terminal.Snapshot{ pane.body.ed_undo[0], pane.body.ed_redo[0] }) |snapshot| {
try std.testing.expectEqual(expected, snapshot.ovl.?.row);
try std.testing.expectEqual(expected + 1, snapshot.cur_row);
try std.testing.expectEqualStrings("edit one\nedit two", snapshot.ovl.?.text);
}
try std.testing.expectEqual(@as(usize, 2), pane.terminal.?.vt.screens.active.pages.countTrackedPins());
- panes.Terminal.undo(p, pane);
+ panes.terminal.undo(p, pane);
try std.testing.expectEqualStrings("edit one\nedit two", pane.ovl.?.text);
try std.testing.expectEqual(expected, pane.ovl.?.row);
}
}
test "terminal lower scroll regions do not move edits above them" {
- if (comptime !panes.Terminal.enabled) return error.SkipZigTest;
+ if (comptime !panes.terminal.enabled) return error.SkipZigTest;
const gpa = std.testing.allocator;
const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = 80, .rows = 24 });
defer p.deinit();
@@ -2026,23 +2030,23 @@ test "terminal lower scroll regions do not move edits above them" {
pane.ovl = .{ .row = 1, .rows = 1, .text = try gpa.dupe(u8, "unsent") };
pane.body.cur_row = 1;
pane.body.cur_pinned = true;
- panes.Terminal.pushUndo(p, pane);
- const total = panes.Terminal.scrollbar(pane).total;
+ panes.terminal.pushUndo(p, pane);
+ const total = panes.terminal.scrollbar(pane).total;
var setup: [64]u8 = undefined;
const bytes = try std.fmt.bufPrint(&setup, "\x1b[4;{d}r\x1b[{d};1H", .{ pane.rows, pane.rows });
p.update(.{ .output = .{ .pane = 0, .bytes = bytes } });
p.update(.{ .output = .{ .pane = 0, .bytes = "tail\r\n" ** 50 } });
- try std.testing.expectEqual(total, panes.Terminal.scrollbar(pane).total);
+ try std.testing.expectEqual(total, panes.terminal.scrollbar(pane).total);
try std.testing.expectEqual(@as(i32, 1), pane.ovl.?.row);
try std.testing.expectEqual(@as(i32, 1), pane.body.cur_row);
try std.testing.expectEqual(@as(i32, 1), pane.body.ed_undo[0].ovl.?.row);
- const full = try panes.Terminal.screenTextAlloc(pane, gpa);
+ const full = try panes.terminal.screenTextAlloc(pane, gpa);
defer gpa.free(full);
try std.testing.expect(std.mem.startsWith(u8, full, "top\nsecond\nthird\n"));
}
test "terminal eviction and lower-region scrolling in one read preserve a surviving edit" {
- if (comptime !panes.Terminal.enabled) return error.SkipZigTest;
+ if (comptime !panes.terminal.enabled) return error.SkipZigTest;
const gpa = std.testing.allocator;
const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = 80, .rows = 24 });
defer p.deinit();
@@ -2067,7 +2071,7 @@ test "terminal eviction and lower-region scrolling in one read preserve a surviv
pane.ovl = .{ .row = old_row, .rows = 1, .text = try gpa.dupe(u8, "kept edit") };
pane.body.cur_row = old_row;
pane.body.cur_pinned = true;
- panes.Terminal.pushUndo(p, pane);
+ panes.terminal.pushUndo(p, pane);
const old_total = pages.total_rows;
var buf: [128]u8 = undefined;
const bytes = try std.fmt.bufPrint(&buf, "new\r\n\x1b[4;{d}r\x1b[{d};1Hregion\r\n", .{ pane.rows, pane.rows });
@@ -3338,11 +3342,11 @@ const TerminalTests = struct {
// Tests asserting raw ANSI indices explicitly select helix, whose palette
// is inherited. Native-palette coverage lives in the overlay/eviction and
// trailing-blank tests and checks their concrete RGB values instead.
- const gridOffset = panes.Terminal.gridOffset;
- const scrollGrid = panes.Terminal.scrollGrid;
- const promptInputReady = panes.Terminal.promptInputReady;
- const forwardKey = panes.Terminal.forwardKey;
- const enterTty = panes.Terminal.enterTty;
+ const gridOffset = panes.terminal.gridOffset;
+ const scrollGrid = panes.terminal.scrollGrid;
+ const promptInputReady = panes.terminal.promptInputReady;
+ const forwardKey = panes.terminal.forwardKey;
+ const enterTty = panes.terminal.enterTty;
// Align normal-mode glyphs with the VT grid and compare their styles.
fn modeStyleDiffs(p: *Pardes, pane: *Pane, gpa: std.mem.Allocator, note: []const u8) !usize {
@@ -4794,7 +4798,7 @@ test "Zig keywords keep every rendered byte colored across line-number widths" {
}
test "terminal overlays preserve trailing blank styles without coloring inserted rows" {
- if (comptime !panes.Terminal.enabled) return error.SkipZigTest;
+ if (comptime !panes.terminal.enabled) return error.SkipZigTest;
const gpa = std.testing.allocator;
const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = 40, .rows = 12 });
defer p.deinit();
diff --git a/test/perf-baseline-gui-Debug.json b/test/perf-baseline-gui-Debug.json
index 659e49d1..567a8b02 100644
--- a/test/perf-baseline-gui-Debug.json
+++ b/test/perf-baseline-gui-Debug.json
@@ -1 +1 @@
-{"harness":"b96624a78789195f","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"Debug","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseSafe","platform":"gui","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1841,"med_us":2197,"p90_us":2308,"max_us":2328},{"op":"render","fixture":"medium","min_us":72,"med_us":81,"p90_us":84,"max_us":86},{"op":"key-down","fixture":"medium","min_us":74,"med_us":74,"p90_us":80,"max_us":81},{"op":"key-right","fixture":"medium","min_us":74,"med_us":80,"p90_us":85,"max_us":100},{"op":"page-down","fixture":"medium","min_us":74,"med_us":81,"p90_us":84,"max_us":176},{"op":"wheel","fixture":"medium","min_us":71,"med_us":75,"p90_us":79,"max_us":93},{"op":"edit-char","fixture":"medium","min_us":1086,"med_us":1515,"p90_us":1692,"max_us":1822},{"op":"body-2m","fixture":"medium","min_us":9044,"med_us":16111,"p90_us":19782,"max_us":21405}]}
+{"harness":"75a55b9f838d3f0","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"Debug","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseSafe","platform":"gui","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":2022,"med_us":2284,"p90_us":2363,"max_us":2431},{"op":"render","fixture":"medium","min_us":80,"med_us":83,"p90_us":90,"max_us":94},{"op":"key-down","fixture":"medium","min_us":79,"med_us":82,"p90_us":87,"max_us":97},{"op":"key-right","fixture":"medium","min_us":82,"med_us":83,"p90_us":91,"max_us":95},{"op":"page-down","fixture":"medium","min_us":79,"med_us":83,"p90_us":84,"max_us":86},{"op":"wheel","fixture":"medium","min_us":79,"med_us":82,"p90_us":85,"max_us":87},{"op":"edit-char","fixture":"medium","min_us":1190,"med_us":1621,"p90_us":1843,"max_us":2008},{"op":"body-2m","fixture":"medium","min_us":9178,"med_us":16872,"p90_us":20886,"max_us":22596}]}
diff --git a/test/perf-baseline-tty-Debug.json b/test/perf-baseline-tty-Debug.json
index bed59c8c..8ba0cdec 100644
--- a/test/perf-baseline-tty-Debug.json
+++ b/test/perf-baseline-tty-Debug.json
@@ -1 +1 @@
-{"harness":"b96624a78789195f","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"Debug","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseSafe","platform":"tty","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1800,"med_us":2113,"p90_us":2193,"max_us":2326},{"op":"render","fixture":"medium","min_us":71,"med_us":76,"p90_us":85,"max_us":97},{"op":"key-down","fixture":"medium","min_us":73,"med_us":76,"p90_us":82,"max_us":84},{"op":"key-right","fixture":"medium","min_us":72,"med_us":76,"p90_us":81,"max_us":95},{"op":"page-down","fixture":"medium","min_us":74,"med_us":78,"p90_us":83,"max_us":83},{"op":"wheel","fixture":"medium","min_us":72,"med_us":75,"p90_us":82,"max_us":83},{"op":"edit-char","fixture":"medium","min_us":1027,"med_us":1418,"p90_us":1699,"max_us":1834},{"op":"body-2m","fixture":"medium","min_us":8357,"med_us":17722,"p90_us":20080,"max_us":21699}]}
+{"harness":"75a55b9f838d3f0","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"Debug","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseSafe","platform":"tty","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1902,"med_us":2386,"p90_us":2484,"max_us":2686},{"op":"render","fixture":"medium","min_us":83,"med_us":87,"p90_us":93,"max_us":118},{"op":"key-down","fixture":"medium","min_us":88,"med_us":94,"p90_us":100,"max_us":156},{"op":"key-right","fixture":"medium","min_us":83,"med_us":86,"p90_us":88,"max_us":92},{"op":"page-down","fixture":"medium","min_us":78,"med_us":84,"p90_us":89,"max_us":101},{"op":"wheel","fixture":"medium","min_us":77,"med_us":84,"p90_us":89,"max_us":132},{"op":"edit-char","fixture":"medium","min_us":1177,"med_us":1519,"p90_us":1721,"max_us":1909},{"op":"body-2m","fixture":"medium","min_us":8533,"med_us":15772,"p90_us":21361,"max_us":23276}]}
diff --git a/test/perf-baseline-tty-ReleaseFast.json b/test/perf-baseline-tty-ReleaseFast.json
index d13442a5..ab5ed3b6 100644
--- a/test/perf-baseline-tty-ReleaseFast.json
+++ b/test/perf-baseline-tty-ReleaseFast.json
@@ -1 +1 @@
-{"harness":"b96624a78789195f","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"ReleaseFast","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseFast","platform":"tty","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":1819,"med_us":2002,"p90_us":2068,"max_us":2122},{"op":"render","fixture":"medium","min_us":71,"med_us":76,"p90_us":88,"max_us":89},{"op":"key-down","fixture":"medium","min_us":73,"med_us":75,"p90_us":76,"max_us":82},{"op":"key-right","fixture":"medium","min_us":73,"med_us":77,"p90_us":86,"max_us":126},{"op":"page-down","fixture":"medium","min_us":71,"med_us":75,"p90_us":81,"max_us":83},{"op":"wheel","fixture":"medium","min_us":71,"med_us":74,"p90_us":76,"max_us":97},{"op":"edit-char","fixture":"medium","min_us":1036,"med_us":1493,"p90_us":1805,"max_us":1967},{"op":"body-2m","fixture":"medium","min_us":8482,"med_us":17227,"p90_us":20296,"max_us":23040}]}
+{"harness":"75a55b9f838d3f0","build":{"compiler":"0.16.0","target":"x86_64-linux.5.10...6.19-gnu.2.38","cpu":"x86_64","driver_optimize":"ReleaseFast","core_optimize":"ReleaseFast","c_optimize":"ReleaseFast","terminal_optimize":"ReleaseFast","platform":"tty","grammars":"full","mupdf":true,"jpx":true,"tracy":false,"quic":false,"theme_animation":true,"terminal_simd":true,"prebuilt_shaders":false},"cols":120,"rows":40,"reps":10,"only":"medium","fixtures":[{"name":"small","lines":1000,"cols":60,"bytes":62694},{"name":"medium","lines":50000,"cols":60,"bytes":3201944},{"name":"large","lines":300000,"cols":60,"bytes":19464444},{"name":"longline","lines":400,"cols":8000,"bytes":3200400},{"name":"esp32p4","lines":12,"cols":240,"bytes":2892}],"cells":[{"op":"open","fixture":"medium","min_us":2061,"med_us":2326,"p90_us":2462,"max_us":2503},{"op":"render","fixture":"medium","min_us":76,"med_us":80,"p90_us":86,"max_us":98},{"op":"key-down","fixture":"medium","min_us":78,"med_us":83,"p90_us":91,"max_us":95},{"op":"key-right","fixture":"medium","min_us":77,"med_us":81,"p90_us":86,"max_us":90},{"op":"page-down","fixture":"medium","min_us":77,"med_us":81,"p90_us":89,"max_us":90},{"op":"wheel","fixture":"medium","min_us":75,"med_us":82,"p90_us":87,"max_us":94},{"op":"edit-char","fixture":"medium","min_us":1277,"med_us":1713,"p90_us":1948,"max_us":2063},{"op":"body-2m","fixture":"medium","min_us":9261,"med_us":17043,"p90_us":23512,"max_us":23722}]}
diff --git a/test/perf.zig b/test/perf.zig
index c639178f..fca4a3b0 100644
--- a/test/perf.zig
+++ b/test/perf.zig
@@ -275,12 +275,12 @@ fn measureCreation(io: std.Io, reps: usize, json: bool) !void {
const allocator = counter.allocator();
const begin = nowNs();
const pane = if (terminal)
- try pardes.panes.Terminal.create(allocator, screen_cols, screen_rows)
+ try pardes.panes.terminal.create(allocator, screen_cols, screen_rows)
else
- try pardes.panes.Terminal.createDoc(allocator, screen_cols, screen_rows);
+ try pardes.panes.terminal.createDoc(allocator, screen_cols, screen_rows);
const constructed = nowNs();
const retained = counter.live;
- pardes.panes.Terminal.deinitEmulator(pane, allocator);
+ pardes.panes.terminal.deinitEmulator(pane, allocator);
allocator.destroy(pane);
const destroyed = nowNs();
if (counter.live != 0) return error.LeakedPaneMemory;
@@ -707,7 +707,7 @@ fn checkTerminalTail(core: *pardes.Pardes, surface: *pardes.Surface, batch: usiz
const pane = core.panes[0] orelse return error.MissingTerminal;
const rect = core.rects[0];
const x = rect.x + pardes.config.GUTTER;
- const y = (if (core.settings.tag_bottom) rect.y else rect.y + pardes.BOX_H) + pardes.panes.Terminal.gridCursor(pane).y;
+ const y = (if (core.settings.tag_bottom) rect.y else rect.y + pardes.BOX_H) + pardes.panes.terminal.gridCursor(pane).y;
var expected_buffer: [32]u8 = undefined;
const expected = try std.fmt.bufPrint(&expected_buffer, "TURN {d:0>10} READY", .{batch});
for (expected, 0..) |byte, col| {
@@ -773,7 +773,7 @@ fn measureUndoOutput(core: *pardes.Pardes, counter: *Allocations, arena: *std.he
pane.body.cur_row = first + 1;
pane.body.cur_col = 1;
pane.body.vsel = .{ .active = true, .row = first, .col = 0 };
- pardes.panes.Terminal.pushUndo(core, pane);
+ pardes.panes.terminal.pushUndo(core, pane);
core.gpa.free(pane.ovl.?.text);
pane.ovl = null;
if (pane.body.ed_undo_len != index + 1) return error.TerminalUndoSnapshotFailed;
@@ -782,7 +782,7 @@ fn measureUndoOutput(core: *pardes.Pardes, counter: *Allocations, arena: *std.he
for (pane.body.ed_undo[0..pane.body.ed_undo_len]) |snapshot| if (snapshot.ovl) |overlay| core.gpa.free(overlay.text);
pane.body.ed_undo_len = 0;
}
- pardes.panes.Terminal.enterTty(core, 0);
+ pardes.panes.terminal.enterTty(core, 0);
pump(core);
const tracked_before = pages.countTrackedPins();
const start = try pages.trackPin(pages.pin(.{ .screen = .{ .y = @intCast(first) } }).?);
@@ -895,7 +895,7 @@ fn measureTerminalSession(io: std.Io, mib: usize, reps: usize, json: bool) !void
}
const stream_wall_ns = nowNs() - stream_begin;
const live_after_output = counter.live;
- const first_row = @as(i32, @intCast(state.vt.screens.active.pages.scrollbar().offset)) + @as(i32, pardes.panes.Terminal.gridCursor(pane).y);
+ const first_row = @as(i32, @intCast(state.vt.screens.active.pages.scrollbar().offset)) + @as(i32, pardes.panes.terminal.gridCursor(pane).y);
_ = arena.reset(.retain_capacity);
const modal_begin = nowNs();
core.update(.{ .key = .{ .cp = core.opts.tty_toggle, .ctrl = true } });
@@ -943,7 +943,7 @@ fn measureTerminalSession(io: std.Io, mib: usize, reps: usize, json: bool) !void
const begin = nowNs();
core.update(.{ .output = .{ .pane = 0, .bytes = update } });
pump(core);
- const cursor_row = pardes.panes.Terminal.gridOffset(pane) + @as(i32, pardes.panes.Terminal.gridCursor(pane).y);
+ const cursor_row = pardes.panes.terminal.gridOffset(pane) + @as(i32, pardes.panes.terminal.gridCursor(pane).y);
pane.ovl.?.row = if (phase == 0) 0 else cursor_row - 1;
pane.body.cur_row = pane.ovl.?.row;
pane.body.cur_col = 0;
@@ -1023,12 +1023,12 @@ const MiniMeasurement = struct {
live_after_deinit: usize = 0,
};
-fn checkMini(actual: pardes.panes.Mini.Result, expected: pardes.panes.Mini.Result) !void {
+fn checkMini(actual: pardes.panes.mini.Result, expected: pardes.panes.mini.Result) !void {
if (!std.mem.eql(u8, actual.content, expected.content) or !std.mem.eql(u8, actual.colors, expected.colors))
return error.MiniOutputMismatch;
}
-fn miniMeasurement(fallback: std.mem.Allocator, probe: MiniProbe, path: []const u8, source: []const u8, styles: []const u8, expected: pardes.panes.Mini.Result, reps: usize) !MiniMeasurement {
+fn miniMeasurement(fallback: std.mem.Allocator, probe: MiniProbe, path: []const u8, source: []const u8, styles: []const u8, expected: pardes.panes.mini.Result, reps: usize) !MiniMeasurement {
const samples = try gpa.alloc(u64, reps);
defer gpa.free(samples);
const allocators = pardes.memory.init(fallback);
@@ -1059,7 +1059,7 @@ fn miniMeasurement(fallback: std.mem.Allocator, probe: MiniProbe, path: []const
defer arena.deinit();
if (core) |p| {
pump(p);
- try pardes.panes.Mini.open(p, 0, path);
+ try pardes.panes.mini.open(p, 0, path);
for (p.panes, 0..) |slot, id| if (slot != null and id != p.active) try p.removePane(id, null);
pump(p);
p.settings.colors = true;
@@ -1095,7 +1095,7 @@ fn miniMeasurement(fallback: std.mem.Allocator, probe: MiniProbe, path: []const
else
styles;
defer if (probe == .highlight_generate) syntax_allocator.free(highlighted);
- const converted = try pardes.panes.Mini.generate(allocator, source, highlighted);
+ const converted = try pardes.panes.mini.generate(allocator, source, highlighted);
const elapsed = nowNs() - begin;
defer converted.deinit(allocator);
if (index == 0) result.cold_ns = elapsed else samples[index - 1] = elapsed;
@@ -1151,7 +1151,7 @@ fn measureMini(io: std.Io, fallback: std.mem.Allocator, reps: usize, json: bool)
defer gpa.free(styles);
if (styles.len != source.len or std.mem.indexOfScalar(u8, styles, @intFromEnum(pardes.syntax.Syn.comment)) == null)
return error.MiniSyntaxUnavailable;
- const expected = try pardes.panes.Mini.generate(gpa, source, styles);
+ const expected = try pardes.panes.mini.generate(gpa, source, styles);
defer expected.deinit(gpa);
if (expected.content.len == 0 or expected.content.len != expected.colors.len or !std.unicode.utf8ValidateSlice(expected.content))
return error.InvalidMiniOutput;
diff --git a/test/snapshots/theme.golden b/test/snapshots/theme.golden
index 622c704b..f4e4f9ec 100644
--- a/test/snapshots/theme.golden
+++ b/test/snapshots/theme.golden
@@ -523,7 +523,7 @@
== style acme grid=100x31
|0: 0-50 #000000,#eaffff, 51-59 #000000,#9eeeee, 60-99 #000000,#eaffff,
|1: 0-1 d,#8888cc, 2-2 d,#eaffff, 3-49 #000000,#eaffff, 50-51 #000000,#8888cc, 52-99 #000000,#eaffff,
-|2: 0-1 d,#8888cc, 2-2 d,#eaffff, 3-49 #000000,#eaffff, 50-51 #000000,#b9c3e5, 52-99 #000000,#eaffff,
+|2: 0-1 d,#8888cc, 2-2 d,#eaffff, 3-29 #000000,#eaffff, 30-38 #000099,#eaffff, 39-49 #000000,#eaffff, 50-51 #000000,#b9c3e5, 52-79 #000000,#eaffff, 80-88 #000099,#eaffff, 89-99 #000000,#eaffff,
|3: 0-2 d,#eaffff, 3-99 #000000,#eaffff,
|4: 0-1 d,#ffffea, 2-99 #000000,#ffffea,
|5: 0-1 d,#ffffea, 2-99 #000000,#ffffea,
@@ -538,7 +538,7 @@
|14: 0-99 d,#ffffea,
|15: 0-99 d,#ffffea,
|16: 0-99 d,#ffffea,
-|17: 0-1 d,#b9c3e5, 2-2 d,#eaffff, 3-49 #000000,#eaffff, 50-99 #000000,#ffffea,
+|17: 0-1 d,#b9c3e5, 2-2 d,#eaffff, 3-29 #000000,#eaffff, 30-38 #000099,#eaffff, 39-49 #000000,#eaffff, 50-99 #000000,#ffffea,
|18: 0-2 d,#eaffff, 3-49 #000000,#eaffff, 50-99 #000000,#ffffea,
|19: 0-1 d,#ffffea, 2-99 #000000,#ffffea,
|20: 0-1 d,#ffffea, 2-99 #000000,#ffffea,
@@ -589,7 +589,7 @@
== style dark-plus grid=100x31
|0: 0-50 #a7a7a7,#3c3c3c, 51-59 #d4d4d4,#264f78, 60-60 #d4d4d4,#3c3c3c, 61-99 #a7a7a7,#3c3c3c,
|1: 0-1 d,#458276, 2-2 d,#1e1e1e, 3-49 #ffffff,#1e1e1e, 50-51 #ffffff,#458276, 52-52 #ffffff,#3c3c3c, 53-99 #a7a7a7,#3c3c3c,
-|2: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-49 #ffffff,#1e1e1e, 50-51 #ffffff,#1c608e, 52-52 #ffffff,#3c3c3c, 53-99 #a7a7a7,#3c3c3c,
+|2: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-29 #ffffff,#1e1e1e, 30-38 #cbe5fc,#1e1e1e, 39-49 #ffffff,#1e1e1e, 50-51 #ffffff,#1c608e, 52-52 #ffffff,#3c3c3c, 53-79 #a7a7a7,#3c3c3c, 80-88 #68aee9,#3c3c3c, 89-99 #a7a7a7,#3c3c3c,
|3: 0-2 d,#1e1e1e, 3-49 #ffffff,#1e1e1e, 50-52 #ffffff,#3c3c3c, 53-99 #a7a7a7,#3c3c3c,
|4: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 #d4d4d4,#424242, 51-99 #d4d4d4,#1e1e1e,
|5: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 #d4d4d4,#424242, 51-99 #d4d4d4,#1e1e1e,
@@ -604,7 +604,7 @@
|14: 0-0 d,#424242, 1-49 d,#1e1e1e, 50-50 d,#424242, 51-99 d,#1e1e1e,
|15: 0-0 d,#424242, 1-49 d,#1e1e1e, 50-50 d,#424242, 51-99 d,#1e1e1e,
|16: 0-0 d,#424242, 1-49 d,#1e1e1e, 50-50 d,#424242, 51-99 d,#1e1e1e,
-|17: 0-1 d,#1c608e, 2-2 d,#3c3c3c, 3-49 #a7a7a7,#3c3c3c, 50-50 #a7a7a7,#424242, 51-99 #a7a7a7,#1e1e1e,
+|17: 0-1 d,#1c608e, 2-2 d,#3c3c3c, 3-29 #a7a7a7,#3c3c3c, 30-38 #68aee9,#3c3c3c, 39-49 #a7a7a7,#3c3c3c, 50-50 #a7a7a7,#424242, 51-99 #a7a7a7,#1e1e1e,
|18: 0-2 d,#3c3c3c, 3-49 #a7a7a7,#3c3c3c, 50-50 #a7a7a7,#424242, 51-99 #a7a7a7,#1e1e1e,
|19: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 #d4d4d4,#424242, 51-99 #d4d4d4,#1e1e1e,
|20: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 #d4d4d4,#424242, 51-99 #d4d4d4,#1e1e1e,
@@ -622,7 +622,7 @@
== style solarized-dark grid=100x31
|0: 0-50 #01090b,#657b83, 51-59 #002b36,#586e75, 60-60 #002b36,#657b83, 61-99 #01090b,#657b83,
|1: 0-1 d,#758a41, 2-2 d,#93a1a1, 3-49 #073642,#93a1a1, 50-51 #073642,#758a41, 52-52 #073642,#657b83, 53-99 #01090b,#657b83,
-|2: 0-1 d,#2483c5, 2-2 d,#93a1a1, 3-49 #073642,#93a1a1, 50-51 #073642,#185986, 52-52 #073642,#657b83, 53-99 #01090b,#657b83,
+|2: 0-1 d,#2483c5, 2-2 d,#93a1a1, 3-29 #073642,#93a1a1, 30-38 #2d3502,#93a1a1, 39-49 #073642,#93a1a1, 50-51 #073642,#185986, 52-52 #073642,#657b83, 53-79 #01090b,#657b83, 80-88 #121600,#657b83, 89-99 #01090b,#657b83,
|3: 0-2 d,#93a1a1, 3-49 #073642,#93a1a1, 50-52 #073642,#657b83, 53-99 #01090b,#657b83,
|4: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 #839496,#839496, 51-99 #839496,#002b36,
|5: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 #839496,#839496, 51-99 #839496,#002b36,
@@ -637,7 +637,7 @@
|14: 0-0 d,#839496, 1-49 d,#002b36, 50-50 d,#839496, 51-99 d,#002b36,
|15: 0-0 d,#839496, 1-49 d,#002b36, 50-50 d,#839496, 51-99 d,#002b36,
|16: 0-0 d,#839496, 1-49 d,#002b36, 50-50 d,#839496, 51-99 d,#002b36,
-|17: 0-1 d,#185986, 2-2 d,#657b83, 3-49 #01090b,#657b83, 50-50 #01090b,#839496, 51-99 #01090b,#002b36,
+|17: 0-1 d,#185986, 2-2 d,#657b83, 3-29 #01090b,#657b83, 30-38 #121600,#657b83, 39-49 #01090b,#657b83, 50-50 #01090b,#839496, 51-99 #01090b,#002b36,
|18: 0-2 d,#657b83, 3-49 #01090b,#657b83, 50-50 #01090b,#839496, 51-99 #01090b,#002b36,
|19: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 #839496,#839496, 51-99 #839496,#002b36,
|20: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 #839496,#839496, 51-99 #839496,#002b36,
diff --git a/test/snapshots/themesel.golden b/test/snapshots/themesel.golden
index 05a8af39..d9c4f4ba 100644
--- a/test/snapshots/themesel.golden
+++ b/test/snapshots/themesel.golden
@@ -533,7 +533,7 @@
== style step-acme grid=100x31
|0: 0-99 #000000,#eaffff,
|1: 0-1 d,#8888cc, 2-2 d,#eaffff, 3-99 #000000,#eaffff,
-|2: 0-1 d,#b9c3e5, 2-2 d,#eaffff, 3-99 #000000,#eaffff,
+|2: 0-1 d,#b9c3e5, 2-2 d,#eaffff, 3-32 #000000,#eaffff, 33-38 #000099,#eaffff, 39-99 #000000,#eaffff,
|3: 0-4 d,#ffffea, 5-6 #999980,#ffffea, 7-99 #000000,#ffffea,
|4: 0-4 d,#ffffea, 5-6 #999980,#ffffea, 7-99 #000000,#ffffea,
|5: 0-4 d,#ffffea, 5-6 #999980,#ffffea, 7-99 #000000,#ffffea,
@@ -547,7 +547,7 @@
|13: 0-0 d,#99994c, 1-3 d,#ffffea, 4-6 #999980,#ffffea, 7-99 #000000,#ffffea,
|14: 0-0 d,#99994c, 1-3 d,#ffffea, 4-6 #999980,#ffffea, 7-99 #000000,#ffffea,
|15: 0-0 d,#99994c, 1-3 d,#ffffea, 4-6 #999980,#ffffea, 7-99 #000000,#ffffea,
-|16: 0-1 d,#8888cc, 2-2 d,#eaffff, 3-99 #000000,#eaffff,
+|16: 0-1 d,#8888cc, 2-2 d,#eaffff, 3-32 #000000,#eaffff, 33-40 #000099,#eaffff, 41-99 #000000,#eaffff,
|17: 0-4 d,#ffffea, 5-6 #999980,#ffffea, 7-92 #000000,#ffffea, 93-99 #000000,#eaffff,
|18: 0-0 d,#99994c, 1-4 d,#ffffea, 5-6 #999980,#ffffea, 7-99 #000000,#ffffea,
|19: 0-0 d,#99994c, 1-4 d,#ffffea, 5-6 #999980,#ffffea, 7-99 #000000,#ffffea,
@@ -627,7 +627,7 @@
== style step-dark-plus grid=100x31
|0: 0-99 #a7a7a7,#3c3c3c,
|1: 0-1 d,#458276, 2-2 d,#1e1e1e, 3-99 #ffffff,#1e1e1e,
-|2: 0-1 d,#1c608e, 2-2 d,#3c3c3c, 3-99 #a7a7a7,#3c3c3c,
+|2: 0-1 d,#1c608e, 2-2 d,#3c3c3c, 3-32 #a7a7a7,#3c3c3c, 33-38 #68aee9,#3c3c3c, 39-99 #a7a7a7,#3c3c3c,
|3: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|4: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|5: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
@@ -641,7 +641,7 @@
|13: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|14: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|15: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
-|16: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-99 #ffffff,#1e1e1e,
+|16: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-32 #ffffff,#1e1e1e, 33-40 #cbe5fc,#1e1e1e, 41-99 #ffffff,#1e1e1e,
|17: 0-0 d,#424242, 1-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-92 #d4d4d4,#1e1e1e, 93-93 #d4d4d4,#3c3c3c, 94-99 #a7a7a7,#3c3c3c,
|18: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|19: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
@@ -674,7 +674,7 @@
== style step-solarized-dark grid=100x31
|0: 0-99 #01090b,#657b83,
|1: 0-1 d,#758a41, 2-2 d,#93a1a1, 3-99 #073642,#93a1a1,
-|2: 0-1 d,#185986, 2-2 d,#657b83, 3-99 #01090b,#657b83,
+|2: 0-1 d,#185986, 2-2 d,#657b83, 3-32 #01090b,#657b83, 33-38 #121600,#657b83, 39-99 #01090b,#657b83,
|3: 0-0 d,#839496, 1-4 d,#002b36, 5-6 #586e75,#002b36, 7-99 #839496,#002b36,
|4: 0-0 d,#839496, 1-4 d,#002b36, 5-6 #586e75,#002b36, 7-99 #839496,#002b36,
|5: 0-0 d,#839496, 1-4 d,#002b36, 5-6 #586e75,#002b36, 7-99 #839496,#002b36,
@@ -688,7 +688,7 @@
|13: 0-0 d,#eee8d5, 1-3 d,#002b36, 4-6 #586e75,#002b36, 7-99 #839496,#002b36,
|14: 0-0 d,#eee8d5, 1-3 d,#002b36, 4-6 #586e75,#002b36, 7-99 #839496,#002b36,
|15: 0-0 d,#eee8d5, 1-3 d,#002b36, 4-6 #586e75,#002b36, 7-99 #839496,#002b36,
-|16: 0-1 d,#2483c5, 2-2 d,#93a1a1, 3-99 #073642,#93a1a1,
+|16: 0-1 d,#2483c5, 2-2 d,#93a1a1, 3-32 #073642,#93a1a1, 33-40 #2d3502,#93a1a1, 41-99 #073642,#93a1a1,
|17: 0-0 d,#839496, 1-3 d,#002b36, 4-6 #586e75,#002b36, 7-92 #839496,#002b36, 93-93 #839496,#657b83, 94-99 #01090b,#657b83,
|18: 0-0 d,#eee8d5, 1-3 d,#002b36, 4-6 #586e75,#002b36, 7-99 #839496,#002b36,
|19: 0-0 d,#eee8d5, 1-3 d,#002b36, 4-6 #586e75,#002b36, 7-99 #839496,#002b36,
@@ -707,7 +707,7 @@
== style step-back grid=100x31
|0: 0-99 #a7a7a7,#3c3c3c,
|1: 0-1 d,#458276, 2-2 d,#1e1e1e, 3-99 #ffffff,#1e1e1e,
-|2: 0-1 d,#1c608e, 2-2 d,#3c3c3c, 3-99 #a7a7a7,#3c3c3c,
+|2: 0-1 d,#1c608e, 2-2 d,#3c3c3c, 3-32 #a7a7a7,#3c3c3c, 33-38 #68aee9,#3c3c3c, 39-99 #a7a7a7,#3c3c3c,
|3: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|4: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|5: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
@@ -721,7 +721,7 @@
|13: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|14: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|15: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
-|16: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-99 #ffffff,#1e1e1e,
+|16: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-32 #ffffff,#1e1e1e, 33-40 #cbe5fc,#1e1e1e, 41-99 #ffffff,#1e1e1e,
|17: 0-0 d,#424242, 1-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-92 #d4d4d4,#1e1e1e, 93-93 #d4d4d4,#3c3c3c, 94-99 #a7a7a7,#3c3c3c,
|18: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|19: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
@@ -768,7 +768,7 @@
|29: 27 line 27
|30: 28 line 28
== style picked grid=100x31
-|2: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-99 #ffffff,#1e1e1e,
+|2: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-32 #ffffff,#1e1e1e, 33-38 #cbe5fc,#1e1e1e, 39-99 #ffffff,#1e1e1e,
|3: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-4 d,#1e1e1e,b 5-6 #c6c6c6,#1e1e1e,b 7-99 #d4d4d4,#1e1e1e,
|7: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|8: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,