Apply newed palette to editor chrome
This commit is contained in:
+112
-20
@@ -12,6 +12,7 @@ const repo_mod = @import("repo.zig");
|
||||
const session_mod = @import("session.zig");
|
||||
const symbol_mod = @import("symbol.zig");
|
||||
const syntax_mod = @import("syntax.zig");
|
||||
const theme_mod = @import("theme.zig");
|
||||
|
||||
// First terminal thin client surface, scriptable for E2E-style tests.
|
||||
// req: session/001, session/003, ui/001, coding/001, testing/001, testing/002
|
||||
@@ -366,6 +367,9 @@ pub const Client = struct {
|
||||
cursor_col,
|
||||
snap.cursor_byte,
|
||||
snap.selection,
|
||||
self.search_matches.items,
|
||||
self.search_query.len,
|
||||
if (self.search_matches.items.len == 0) null else self.search_matches.items[self.search_index],
|
||||
);
|
||||
body_lines_used += 1;
|
||||
line_start_byte += line.len + 1;
|
||||
@@ -392,8 +396,8 @@ pub const Client = struct {
|
||||
else
|
||||
try std.fmt.allocPrint(
|
||||
allocator,
|
||||
"mode:{s} row={d} col={d} bytes={d}{s}",
|
||||
.{ @tagName(self.effectiveMode()), cursor_line + 1, cursor_col + 1, snap.bytes.len, if (self.quit) " quit" else "" },
|
||||
"mode:{s} {s} row={d} col={d} bytes={d}{s}",
|
||||
.{ @tagName(self.effectiveMode()), if (self.isDirtyBytes(snap.bytes)) "dirty" else "clean", cursor_line + 1, cursor_col + 1, snap.bytes.len, if (self.quit) " quit" else "" },
|
||||
);
|
||||
defer allocator.free(status);
|
||||
try appendStatusLine(allocator, &out, status, self.viewport.width);
|
||||
@@ -410,7 +414,7 @@ pub const Client = struct {
|
||||
defer allocator.free(path);
|
||||
const header = try std.fmt.allocPrint(allocator, "panel {s}", .{path});
|
||||
defer allocator.free(header);
|
||||
try appendVisibleCells(allocator, &out, header, self.viewport.width);
|
||||
try appendThemedRow(allocator, &out, header, self.viewport.width, ansi_text);
|
||||
try out.append(allocator, '\n');
|
||||
body_lines_used += 1;
|
||||
|
||||
@@ -423,7 +427,7 @@ pub const Client = struct {
|
||||
}
|
||||
for (list_rows) |row| {
|
||||
if (body_lines_used >= max_body_lines) break;
|
||||
try appendVisibleCells(allocator, &out, row, self.viewport.width);
|
||||
try appendThemedRow(allocator, &out, row, self.viewport.width, ansi_text);
|
||||
try out.append(allocator, '\n');
|
||||
body_lines_used += 1;
|
||||
}
|
||||
@@ -431,12 +435,12 @@ pub const Client = struct {
|
||||
const title = snap.active_panel_title.?;
|
||||
const detail = try std.fmt.allocPrint(allocator, "{s}: no content yet", .{title});
|
||||
defer allocator.free(detail);
|
||||
try appendVisibleCells(allocator, &out, detail, self.viewport.width);
|
||||
try appendThemedRow(allocator, &out, detail, self.viewport.width, ansi_text);
|
||||
try out.append(allocator, '\n');
|
||||
body_lines_used += 1;
|
||||
}
|
||||
while (body_lines_used < max_body_lines) : (body_lines_used += 1) {
|
||||
try out.append(allocator, '~');
|
||||
try appendThemedRow(allocator, &out, "~", self.viewport.width, ansi_virtual);
|
||||
try out.append(allocator, '\n');
|
||||
}
|
||||
|
||||
@@ -449,7 +453,7 @@ pub const Client = struct {
|
||||
.{ snap.active_panel_index.? + 1, snap.panel_depth },
|
||||
);
|
||||
defer allocator.free(status);
|
||||
try appendVisibleCells(allocator, &out, status, self.viewport.width);
|
||||
try appendStatusLine(allocator, &out, status, self.viewport.width);
|
||||
return out.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
@@ -537,6 +541,11 @@ pub const Client = struct {
|
||||
self.message = message;
|
||||
}
|
||||
|
||||
fn isDirtyBytes(self: *const Client, bytes: []const u8) bool {
|
||||
const saved_bytes = self.saved_bytes orelse return bytes.len != 0;
|
||||
return !std.mem.eql(u8, saved_bytes, bytes);
|
||||
}
|
||||
|
||||
fn effectiveMode(self: *const Client) EditorMode {
|
||||
if (self.search_prompt_active or self.project_search_prompt_active or self.leader.isPromptActive()) return .prompt;
|
||||
const snap = self.session.snapshot() catch return self.mode;
|
||||
@@ -2560,14 +2569,16 @@ pub const Client = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const ansi_reset = "\x1b[0m";
|
||||
const ansi_gutter = "\x1b[38;2;88;96;112m";
|
||||
const ansi_virtual = "\x1b[38;2;64;70;86m";
|
||||
const ansi_text = "\x1b[38;2;214;222;235m";
|
||||
const ansi_current_line = "\x1b[48;2;26;31;43m";
|
||||
const ansi_cursor = "\x1b[38;2;18;22;30;48;2;245;197;92m";
|
||||
const ansi_selection = "\x1b[38;2;214;222;235;48;2;64;96;140m";
|
||||
const ansi_status = "\x1b[38;2;18;22;30;48;2;126;231;135m";
|
||||
const ansi_reset = theme_mod.resetSgr();
|
||||
const ansi_gutter = "\x1b[38;2;103;93;122;48;2;25;22;33m";
|
||||
const ansi_virtual = "\x1b[38;2;72;67;84;48;2;25;22;33m";
|
||||
const ansi_text = "\x1b[38;2;230;230;230;48;2;25;22;33m";
|
||||
const ansi_current_line = "\x1b[48;2;20;18;26m";
|
||||
const ansi_cursor = "\x1b[38;2;25;22;33;48;2;230;230;230m";
|
||||
const ansi_selection = "\x1b[38;2;230;230;230;48;2;55;65;92m";
|
||||
const ansi_search = "\x1b[38;2;25;22;33;48;2;231;168;78m";
|
||||
const ansi_search_active = "\x1b[38;2;25;22;33;48;2;235;119;84m";
|
||||
const ansi_status = "\x1b[38;2;25;22;33;48;2;235;119;84m";
|
||||
|
||||
fn appendEditorLine(
|
||||
allocator: std.mem.Allocator,
|
||||
@@ -2581,7 +2592,11 @@ fn appendEditorLine(
|
||||
cursor_col: usize,
|
||||
cursor_byte: usize,
|
||||
selection: ?session_mod.Selection,
|
||||
search_matches: []const usize,
|
||||
search_len: usize,
|
||||
active_search: ?usize,
|
||||
) !void {
|
||||
const line_start = out.items.len;
|
||||
try appendLineNumber(allocator, out, line_no, gutter_digits);
|
||||
if (content_width == 0) {
|
||||
try out.append(allocator, '\n');
|
||||
@@ -2593,7 +2608,8 @@ fn appendEditorLine(
|
||||
0;
|
||||
if (is_cursor_line) try out.appendSlice(allocator, ansi_current_line);
|
||||
try out.appendSlice(allocator, ansi_text);
|
||||
try appendEditorCells(allocator, out, line, line_start_byte, content_width, is_cursor_line, cursor_col, cursor_byte, selection, horizontal_scroll);
|
||||
try appendEditorCells(allocator, out, line, line_start_byte, content_width, is_cursor_line, cursor_col, cursor_byte, selection, horizontal_scroll, search_matches, search_len, active_search);
|
||||
try padStyledLineToWidth(allocator, out, out.items[line_start..], if (is_cursor_line) ansi_current_line else ansi_text, gutter_digits + 2 + content_width);
|
||||
try out.appendSlice(allocator, ansi_reset);
|
||||
try out.append(allocator, '\n');
|
||||
}
|
||||
@@ -2611,13 +2627,14 @@ fn appendLineNumber(allocator: std.mem.Allocator, out: *std.ArrayList(u8), line_
|
||||
}
|
||||
|
||||
fn appendVirtualLine(allocator: std.mem.Allocator, out: *std.ArrayList(u8), gutter_digits: usize, content_width: usize) !void {
|
||||
_ = content_width;
|
||||
const line_start = out.items.len;
|
||||
try out.appendSlice(allocator, ansi_gutter);
|
||||
var pad: usize = 0;
|
||||
while (pad < gutter_digits) : (pad += 1) try out.append(allocator, ' ');
|
||||
try out.appendSlice(allocator, "│");
|
||||
try out.appendSlice(allocator, ansi_virtual);
|
||||
try out.appendSlice(allocator, " ·");
|
||||
try padStyledLineToWidth(allocator, out, out.items[line_start..], ansi_text, gutter_digits + 2 + content_width);
|
||||
try out.appendSlice(allocator, ansi_reset);
|
||||
try out.append(allocator, '\n');
|
||||
}
|
||||
@@ -2633,6 +2650,9 @@ fn appendEditorCells(
|
||||
cursor_byte: usize,
|
||||
selection: ?session_mod.Selection,
|
||||
horizontal_scroll: usize,
|
||||
search_matches: []const usize,
|
||||
search_len: usize,
|
||||
active_search: ?usize,
|
||||
) !void {
|
||||
var i: usize = 0;
|
||||
var source_col: usize = 0;
|
||||
@@ -2655,14 +2675,17 @@ fn appendEditorCells(
|
||||
_ = cursor_col;
|
||||
const is_cursor_cell = is_cursor_line and !drew_cursor and cursor_byte == absolute_start;
|
||||
const is_selected_cell = isSelectedByteRange(selection, absolute_start, absolute_end);
|
||||
const search_style = searchStyle(search_matches, search_len, active_search, absolute_start, absolute_end);
|
||||
if (is_cursor_cell) {
|
||||
try out.appendSlice(allocator, ansi_cursor);
|
||||
drew_cursor = true;
|
||||
} else if (is_selected_cell) {
|
||||
try out.appendSlice(allocator, ansi_selection);
|
||||
} else if (search_style) |style| {
|
||||
try out.appendSlice(allocator, style);
|
||||
}
|
||||
try out.appendSlice(allocator, bytes[i..end]);
|
||||
if (is_cursor_cell or is_selected_cell) {
|
||||
if (is_cursor_cell or is_selected_cell or search_style != null) {
|
||||
try out.appendSlice(allocator, ansi_reset);
|
||||
if (is_cursor_line) try out.appendSlice(allocator, ansi_current_line);
|
||||
try out.appendSlice(allocator, ansi_text);
|
||||
@@ -2688,9 +2711,38 @@ fn isSelectedByteRange(selection: ?session_mod.Selection, start: usize, end: usi
|
||||
return start < hi and end > lo;
|
||||
}
|
||||
|
||||
fn searchStyle(matches: []const usize, query_len: usize, active_match: ?usize, start: usize, end: usize) ?[]const u8 {
|
||||
if (query_len == 0) return null;
|
||||
for (matches) |match_start| {
|
||||
const match_end = match_start + query_len;
|
||||
if (start < match_end and end > match_start) {
|
||||
if (active_match != null and active_match.? == match_start) return ansi_search_active;
|
||||
return ansi_search;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn padStyledLineToWidth(allocator: std.mem.Allocator, out: *std.ArrayList(u8), rendered_line: []const u8, style: []const u8, width: usize) !void {
|
||||
var cells = visibleCellWidth(rendered_line);
|
||||
if (cells >= width) return;
|
||||
try out.appendSlice(allocator, style);
|
||||
while (cells < width) : (cells += 1) try out.append(allocator, ' ');
|
||||
}
|
||||
|
||||
fn appendThemedRow(allocator: std.mem.Allocator, out: *std.ArrayList(u8), text: []const u8, width: usize, style: []const u8) !void {
|
||||
const line_start = out.items.len;
|
||||
try out.appendSlice(allocator, style);
|
||||
try appendVisibleCells(allocator, out, text, width);
|
||||
try padStyledLineToWidth(allocator, out, out.items[line_start..], style, width);
|
||||
try out.appendSlice(allocator, ansi_reset);
|
||||
}
|
||||
|
||||
fn appendStatusLine(allocator: std.mem.Allocator, out: *std.ArrayList(u8), status: []const u8, width: usize) !void {
|
||||
const line_start = out.items.len;
|
||||
try out.appendSlice(allocator, ansi_status);
|
||||
try appendVisibleCells(allocator, out, status, width);
|
||||
try padStyledLineToWidth(allocator, out, out.items[line_start..], ansi_status, width);
|
||||
try out.appendSlice(allocator, ansi_reset);
|
||||
}
|
||||
|
||||
@@ -3225,7 +3277,7 @@ test "regular: render diagnostics report ok for representative narrow editor fra
|
||||
defer result.deinit(std.testing.allocator);
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 5), result.diagnostics.frame_lines);
|
||||
try std.testing.expect(result.diagnostics.frame_bytes <= 32 * 5 * 4);
|
||||
try std.testing.expect(result.diagnostics.frame_bytes > 0);
|
||||
try std.testing.expect(result.diagnostics.max_line_cells <= 32);
|
||||
try std.testing.expectEqual(@as(usize, 0), result.diagnostics.clipped_sources);
|
||||
try std.testing.expect(std.mem.indexOf(u8, result.warning, "render ok") != null);
|
||||
@@ -5160,7 +5212,8 @@ test "regular: editor render has colorscheme gutter cursor and status chrome" {
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, " 2│") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, ansi_cursor) != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "▌") == null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "\x1b[38;2;18;22;30;48;2;126;231;135m") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, ansi_text) != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, ansi_status) != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "^\n") == null);
|
||||
try assertLinesFit(frame, 40);
|
||||
}
|
||||
@@ -5375,3 +5428,42 @@ test "regular: Space P opens Pi panel" {
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "panel [pi]") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "pi_ask:explain_current_buffer") != null);
|
||||
}
|
||||
|
||||
test "regular: global background fills editor and panel rows" {
|
||||
var client = try Client.init(std.testing.allocator, .{ .width = 28, .height = 5 });
|
||||
defer client.deinit();
|
||||
|
||||
try client.handleTraceLine("open a");
|
||||
const editor = try client.render(std.testing.allocator);
|
||||
defer std.testing.allocator.free(editor);
|
||||
try std.testing.expect(std.mem.indexOf(u8, editor, "\x1b[38;2;230;230;230;48;2;25;22;33m") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, editor, ansi_status) != null);
|
||||
try assertLinesFit(editor, 28);
|
||||
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("P");
|
||||
const panel = try client.render(std.testing.allocator);
|
||||
defer std.testing.allocator.free(panel);
|
||||
try std.testing.expect(std.mem.indexOf(u8, panel, "\x1b[38;2;230;230;230;48;2;25;22;33m") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, panel, ansi_status) != null);
|
||||
try assertLinesFit(panel, 28);
|
||||
}
|
||||
|
||||
test "regular: search matches distinguish active match from other matches" {
|
||||
var client = try Client.init(std.testing.allocator, .{ .width = 48, .height = 6 });
|
||||
defer client.deinit();
|
||||
|
||||
try client.handleTraceLine("open alpha beta alpha");
|
||||
try client.handleInput("/");
|
||||
try client.handleInput("a");
|
||||
try client.handleInput("l");
|
||||
try client.handleInput("p");
|
||||
try client.handleInput("h");
|
||||
try client.handleInput("a");
|
||||
try client.handleInput("\n");
|
||||
const frame = try client.render(std.testing.allocator);
|
||||
defer std.testing.allocator.free(frame);
|
||||
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, ansi_search_active) != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, ansi_search) != null);
|
||||
}
|
||||
|
||||
+12
-12
@@ -87,13 +87,13 @@ def classify_sgr(params: str) -> str:
|
||||
return ""
|
||||
parts = [p for p in params.split(";") if p]
|
||||
joined = ";".join(parts)
|
||||
if "48;2;245;197;92" in joined:
|
||||
if "48;2;230;230;230" in joined:
|
||||
return "cursor"
|
||||
if "48;2;64;96;140" in joined:
|
||||
if "48;2;55;65;92" in joined:
|
||||
return "selection"
|
||||
if "48;2;26;31;43" in joined:
|
||||
if "48;2;20;18;26" in joined:
|
||||
return "current-line"
|
||||
if "48;2;126;231;135" in joined:
|
||||
if "48;2;235;119;84" in joined:
|
||||
return "status"
|
||||
return ""
|
||||
|
||||
@@ -341,10 +341,10 @@ def write_artifacts(out_dir: Path, scenario: Scenario, transcript: bytes, lines:
|
||||
normalized_visible = normalize_visible_controls(visible)
|
||||
raw_seen_attrs = set(seen_attrs)
|
||||
for sgr, attr in (
|
||||
("48;2;245;197;92", "cursor"),
|
||||
("48;2;64;96;140", "selection"),
|
||||
("48;2;26;31;43", "current-line"),
|
||||
("48;2;126;231;135", "status"),
|
||||
("48;2;230;230;230", "cursor"),
|
||||
("48;2;55;65;92", "selection"),
|
||||
("48;2;20;18;26", "current-line"),
|
||||
("48;2;235;119;84", "status"),
|
||||
):
|
||||
if sgr in normalized_visible:
|
||||
raw_seen_attrs.add(attr)
|
||||
@@ -617,10 +617,10 @@ def run_one(mim: Path, root_out: Path, scenario: Scenario) -> tuple[str, Path]:
|
||||
plain_text = strip_csi(raw_text)
|
||||
raw_attrs: set[str] = set()
|
||||
for sgr, attr in (
|
||||
("48;2;245;197;92", "cursor"),
|
||||
("48;2;64;96;140", "selection"),
|
||||
("48;2;26;31;43", "current-line"),
|
||||
("48;2;126;231;135", "status"),
|
||||
("48;2;230;230;230", "cursor"),
|
||||
("48;2;55;65;92", "selection"),
|
||||
("48;2;20;18;26", "current-line"),
|
||||
("48;2;235;119;84", "status"),
|
||||
):
|
||||
if sgr in raw_text:
|
||||
raw_attrs.add(attr)
|
||||
|
||||
Reference in New Issue
Block a user