feat(whitespace): add leading, eol, tabs modes and highlight leading/trailing errors
This changes whitespace rendering to use a map of the visible viewport. The view map makes it easy and fast to implement various whitespace highlighting and rendering features.
This commit is contained in:
parent
233e881f95
commit
919d5ee9bb
2 changed files with 251 additions and 80 deletions
|
@ -41,6 +41,17 @@ pub const max_matches = if (builtin.mode == std.builtin.OptimizeMode.Debug) 10_0
|
|||
pub const max_match_lines = 15;
|
||||
pub const max_match_batch = if (builtin.mode == std.builtin.OptimizeMode.Debug) 100 else 1000;
|
||||
|
||||
pub const whitespace = struct {
|
||||
pub const char = struct {
|
||||
pub const visible = "·";
|
||||
pub const blank = " ";
|
||||
pub const indent = "│";
|
||||
pub const eol = "↩"; // alternatives: "$", "⏎", "", "↲", "⤶", "", "", "⤦", "¬", "", "❯", "❮"
|
||||
pub const tab_begin = "-";
|
||||
pub const tab_end = ">";
|
||||
};
|
||||
};
|
||||
|
||||
pub const Match = struct {
|
||||
begin: Cursor = Cursor{},
|
||||
end: Cursor = Cursor{},
|
||||
|
@ -288,7 +299,7 @@ pub const Editor = struct {
|
|||
|
||||
case_data: ?CaseData = null,
|
||||
|
||||
const WhitespaceMode = enum { visible, indent, none };
|
||||
const WhitespaceMode = enum { indent, leading, eol, tabs, visible, full, none };
|
||||
const StyleCache = std.AutoHashMap(u32, ?Widget.Theme.Token);
|
||||
|
||||
const Context = command.Context;
|
||||
|
@ -384,10 +395,18 @@ pub const Editor = struct {
|
|||
}
|
||||
|
||||
fn from_whitespace_mode(whitespace_mode: []const u8) WhitespaceMode {
|
||||
return if (std.mem.eql(u8, whitespace_mode, "visible"))
|
||||
.visible
|
||||
else if (std.mem.eql(u8, whitespace_mode, "indent"))
|
||||
return if (std.mem.eql(u8, whitespace_mode, "indent"))
|
||||
.indent
|
||||
else if (std.mem.eql(u8, whitespace_mode, "leading"))
|
||||
.leading
|
||||
else if (std.mem.eql(u8, whitespace_mode, "eol"))
|
||||
.eol
|
||||
else if (std.mem.eql(u8, whitespace_mode, "tabs"))
|
||||
.tabs
|
||||
else if (std.mem.eql(u8, whitespace_mode, "visible"))
|
||||
.visible
|
||||
else if (std.mem.eql(u8, whitespace_mode, "full"))
|
||||
.full
|
||||
else
|
||||
.none;
|
||||
}
|
||||
|
@ -735,15 +754,32 @@ pub const Editor = struct {
|
|||
return self.scroll_dest != self.view.row;
|
||||
}
|
||||
|
||||
const CellType = enum {
|
||||
empty,
|
||||
character,
|
||||
space,
|
||||
tab,
|
||||
eol,
|
||||
extension,
|
||||
};
|
||||
const CellMapEntry = struct {
|
||||
cell_type: CellType = .empty,
|
||||
cursor: bool = false,
|
||||
};
|
||||
const CellMap = ViewMap(CellMapEntry, .{});
|
||||
|
||||
fn render_screen(self: *Self, theme: *const Widget.Theme, cache: *StyleCache) void {
|
||||
const ctx = struct {
|
||||
self: *Self,
|
||||
buf_row: usize,
|
||||
buf_col: usize = 0,
|
||||
y: usize = 0,
|
||||
x: usize = 0,
|
||||
match_idx: usize = 0,
|
||||
theme: *const Widget.Theme,
|
||||
hl_row: ?usize,
|
||||
leading: bool = true,
|
||||
cell_map: CellMap,
|
||||
|
||||
fn walker(ctx_: *anyopaque, leaf: *const Buffer.Leaf, _: Buffer.Metrics) Buffer.Walker {
|
||||
const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_)));
|
||||
|
@ -773,7 +809,6 @@ pub const Editor = struct {
|
|||
if (ctx.buf_col >= view.col + view.cols)
|
||||
break;
|
||||
var cell = n.cell_init();
|
||||
var end_cell: ?Cell = null;
|
||||
const c = &cell;
|
||||
switch (chunk[0]) {
|
||||
0...8, 10...31, 32, 9 => {},
|
||||
|
@ -781,10 +816,15 @@ pub const Editor = struct {
|
|||
}
|
||||
const bytes, const colcount = switch (chunk[0]) {
|
||||
0...8, 10...31 => |code| ctx.self.render_control_code(c, n, code, ctx.theme),
|
||||
32 => ctx.self.render_space(c, n, ctx.buf_col, ctx.leading, ctx.theme),
|
||||
9 => ctx.self.render_tab(c, n, ctx.buf_col, ctx.theme, &end_cell),
|
||||
32 => ctx.self.render_space(c, n),
|
||||
9 => ctx.self.render_tab(c, n, ctx.buf_col),
|
||||
else => render_egc(c, n, chunk),
|
||||
};
|
||||
const cell_map_val: CellType = switch (chunk[0]) {
|
||||
32 => .space,
|
||||
9 => .tab,
|
||||
else => .character,
|
||||
};
|
||||
if (ctx.hl_row) |hl_row| if (hl_row == ctx.buf_row)
|
||||
self_.render_line_highlight_cell(ctx.theme, c);
|
||||
self_.render_matches(&ctx.match_idx, ctx.theme, c);
|
||||
|
@ -795,11 +835,13 @@ pub const Editor = struct {
|
|||
if (ctx.buf_col < view.col and ctx.buf_col + advanced > view.col)
|
||||
n.cursor_move_rel(0, @intCast(ctx.buf_col + advanced - view.col)) catch {};
|
||||
ctx.buf_col += advanced;
|
||||
ctx.cell_map.set_yx(ctx.y, ctx.x, .{ .cell_type = cell_map_val });
|
||||
ctx.x += advanced;
|
||||
|
||||
while (ctx.buf_col < new_col) {
|
||||
if (ctx.buf_col >= view.col + view.cols)
|
||||
break;
|
||||
var cell_ = if (end_cell) |ec| if (ctx.buf_col == new_col - 1) ec else c.* else n.cell_init();
|
||||
var cell_ = n.cell_init();
|
||||
const c_ = &cell_;
|
||||
if (ctx.hl_row) |hl_row| if (hl_row == ctx.buf_row)
|
||||
self_.render_line_highlight_cell(ctx.theme, c_);
|
||||
|
@ -807,6 +849,8 @@ pub const Editor = struct {
|
|||
self_.render_selections(ctx.theme, c_);
|
||||
const advanced_ = n.putc(c_) catch break;
|
||||
ctx.buf_col += advanced_;
|
||||
ctx.cell_map.set_yx(ctx.y, ctx.x, .{ .cell_type = .extension });
|
||||
ctx.x += advanced;
|
||||
}
|
||||
chunk = chunk[bytes..];
|
||||
}
|
||||
|
@ -826,6 +870,9 @@ pub const Editor = struct {
|
|||
n.cursor_move_rel(1, 0) catch |e| return Buffer.Walker{ .err = e };
|
||||
ctx.buf_row += 1;
|
||||
ctx.buf_col = 0;
|
||||
ctx.cell_map.set_yx(ctx.y, ctx.x, .{ .cell_type = .eol });
|
||||
ctx.y += 1;
|
||||
ctx.x = 0;
|
||||
ctx.leading = true;
|
||||
}
|
||||
return Buffer.Walker.keep_walking;
|
||||
|
@ -839,7 +886,14 @@ pub const Editor = struct {
|
|||
break :blk null;
|
||||
break :blk self.get_primary().cursor.row;
|
||||
} else null;
|
||||
var ctx_: ctx = .{ .self = self, .buf_row = self.view.row, .theme = theme, .hl_row = hl_row };
|
||||
var ctx_: ctx = .{
|
||||
.self = self,
|
||||
.buf_row = self.view.row,
|
||||
.theme = theme,
|
||||
.hl_row = hl_row,
|
||||
.cell_map = CellMap.init(self.allocator, self.view.rows, self.view.cols) catch @panic("OOM"),
|
||||
};
|
||||
defer ctx_.cell_map.deinit(self.allocator);
|
||||
const root = self.buf_root() catch return;
|
||||
|
||||
{
|
||||
|
@ -854,28 +908,31 @@ pub const Editor = struct {
|
|||
_ = root.walk_from_line_begin_const(self.view.row, ctx.walker, &ctx_, self.metrics) catch {};
|
||||
}
|
||||
self.render_syntax(theme, cache, root) catch {};
|
||||
self.render_diagnostics(theme, hl_row) catch {};
|
||||
self.render_cursors(theme) catch {};
|
||||
self.render_cursors(theme, ctx_.cell_map) catch {};
|
||||
self.render_whitespace_map(theme, ctx_.cell_map) catch {};
|
||||
self.render_diagnostics(theme, hl_row, ctx_.cell_map) catch {};
|
||||
}
|
||||
|
||||
fn render_cursors(self: *Self, theme: *const Widget.Theme) !void {
|
||||
fn render_cursors(self: *Self, theme: *const Widget.Theme, cell_map: CellMap) !void {
|
||||
const frame = tracy.initZone(@src(), .{ .name = "editor render cursors" });
|
||||
defer frame.deinit();
|
||||
for (self.cursels.items[0 .. self.cursels.items.len - 1]) |*cursel_| if (cursel_.*) |*cursel|
|
||||
try self.render_cursor_secondary(&cursel.cursor, theme);
|
||||
try self.render_cursor_primary(&self.get_primary().cursor, theme);
|
||||
try self.render_cursor_secondary(&cursel.cursor, theme, cell_map);
|
||||
try self.render_cursor_primary(&self.get_primary().cursor, theme, cell_map);
|
||||
}
|
||||
|
||||
fn render_cursor_primary(self: *Self, cursor: *const Cursor, theme: *const Widget.Theme) !void {
|
||||
fn render_cursor_primary(self: *Self, cursor: *const Cursor, theme: *const Widget.Theme, cell_map: CellMap) !void {
|
||||
const tui_ = tui.current();
|
||||
if (!tui_.is_mainview_focused() or !self.enable_terminal_cursor) {
|
||||
if (self.screen_cursor(cursor)) |pos| {
|
||||
set_cell_map_cursor(cell_map, pos.row, pos.col);
|
||||
self.plane.cursor_move_yx(@intCast(pos.row), @intCast(pos.col)) catch return;
|
||||
const style = if (tui_.is_mainview_focused()) theme.editor_cursor else theme.editor_cursor_secondary;
|
||||
self.render_cursor_cell(style);
|
||||
}
|
||||
} else {
|
||||
if (self.screen_cursor(cursor)) |pos| {
|
||||
set_cell_map_cursor(cell_map, pos.row, pos.col);
|
||||
const y, const x = self.plane.rel_yx_to_abs(@intCast(pos.row), @intCast(pos.col));
|
||||
const configured_shape = tui_.get_cursor_shape();
|
||||
const cursor_shape = if (self.cursels.items.len > 1) switch (configured_shape) {
|
||||
|
@ -892,8 +949,9 @@ pub const Editor = struct {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_cursor_secondary(self: *Self, cursor: *const Cursor, theme: *const Widget.Theme) !void {
|
||||
fn render_cursor_secondary(self: *Self, cursor: *const Cursor, theme: *const Widget.Theme, cell_map: CellMap) !void {
|
||||
if (self.screen_cursor(cursor)) |pos| {
|
||||
set_cell_map_cursor(cell_map, pos.row, pos.col);
|
||||
self.plane.cursor_move_yx(@intCast(pos.row), @intCast(pos.col)) catch return;
|
||||
self.render_cursor_cell(theme.editor_cursor_secondary);
|
||||
}
|
||||
|
@ -906,6 +964,11 @@ pub const Editor = struct {
|
|||
_ = self.plane.putc(&cell) catch {};
|
||||
}
|
||||
|
||||
inline fn set_cell_map_cursor(cell_map: CellMap, y: usize, x: usize) void {
|
||||
const cell_type = cell_map.get_yx(y, x).cell_type;
|
||||
cell_map.set_yx(y, x, .{ .cursor = true, .cell_type = cell_type });
|
||||
}
|
||||
|
||||
fn render_line_highlight(self: *Self, cursor: *const Cursor, theme: *const Widget.Theme) !void {
|
||||
const row_min = self.view.row;
|
||||
const row_max = row_min + self.view.rows;
|
||||
|
@ -954,11 +1017,11 @@ pub const Editor = struct {
|
|||
};
|
||||
}
|
||||
|
||||
fn render_diagnostics(self: *Self, theme: *const Widget.Theme, hl_row: ?usize) !void {
|
||||
for (self.diagnostics.items) |*diag| self.render_diagnostic(diag, theme, hl_row);
|
||||
fn render_diagnostics(self: *Self, theme: *const Widget.Theme, hl_row: ?usize, cell_map: CellMap) !void {
|
||||
for (self.diagnostics.items) |*diag| self.render_diagnostic(diag, theme, hl_row, cell_map);
|
||||
}
|
||||
|
||||
fn render_diagnostic(self: *Self, diag: *const Diagnostic, theme: *const Widget.Theme, hl_row: ?usize) void {
|
||||
fn render_diagnostic(self: *Self, diag: *const Diagnostic, theme: *const Widget.Theme, hl_row: ?usize, cell_map: CellMap) void {
|
||||
const screen_width = self.view.cols;
|
||||
const pos = self.screen_cursor(&diag.sel.begin) orelse return;
|
||||
var style = switch (diag.get_severity()) {
|
||||
|
@ -980,22 +1043,12 @@ pub const Editor = struct {
|
|||
self.render_diagnostic_cell(style);
|
||||
}
|
||||
}
|
||||
const space_begin = get_line_end_space_begin(&self.plane, screen_width, pos.row);
|
||||
if (space_begin < screen_width) {
|
||||
var space_begin = screen_width;
|
||||
while (space_begin > 0) : (space_begin -= 1)
|
||||
if (cell_map.get_yx(pos.row, space_begin).cell_type != .empty) break;
|
||||
if (space_begin < screen_width)
|
||||
self.render_diagnostic_message(diag.message, pos.row, screen_width - space_begin, style);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_line_end_space_begin(plane: *Plane, screen_width: usize, screen_row: usize) usize {
|
||||
var pos = screen_width;
|
||||
var cell = plane.cell_init();
|
||||
while (pos > 0) : (pos -= 1) {
|
||||
plane.cursor_move_yx(@intCast(screen_row), @intCast(pos - 1)) catch return pos;
|
||||
const cell_egc_bytes = plane.at_cursor_cell(&cell) catch return pos;
|
||||
if (cell_egc_bytes > 0) return pos;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
fn render_diagnostic_message(self: *Self, message_: []const u8, y: usize, max_space: usize, style: Widget.Theme.Style) void {
|
||||
self.plane.set_style(style);
|
||||
|
@ -1034,26 +1087,14 @@ pub const Editor = struct {
|
|||
}
|
||||
|
||||
inline fn render_eol(self: *const Self, n: *Plane, theme: *const Widget.Theme) Cell {
|
||||
const char = whitespace.char;
|
||||
var cell = n.cell_init();
|
||||
const c = &cell;
|
||||
if (self.render_whitespace == .visible) {
|
||||
c.set_style(theme.editor_whitespace);
|
||||
//_ = n.cell_load(c, "$") catch {};
|
||||
//_ = n.cell_load(c, " ") catch {};
|
||||
//_ = n.cell_load(c, "⏎") catch {};
|
||||
// _ = n.cell_load(c, "") catch {};
|
||||
_ = n.cell_load(c, "↩") catch {};
|
||||
//_ = n.cell_load(c, "↲") catch {};
|
||||
//_ = n.cell_load(c, "⤶") catch {};
|
||||
//_ = n.cell_load(c, "") catch {};
|
||||
//_ = n.cell_load(c, "") catch {};
|
||||
//_ = n.cell_load(c, "⤦") catch {};
|
||||
//_ = n.cell_load(c, "¬") catch {};
|
||||
//_ = n.cell_load(c, "") catch {};
|
||||
//_ = n.cell_load(c, "❯") catch {};
|
||||
//_ = n.cell_load(c, "❮") catch {};
|
||||
_ = n.cell_load(c, char.eol) catch {};
|
||||
} else {
|
||||
_ = n.cell_load(c, " ") catch {};
|
||||
_ = n.cell_load(c, char.blank) catch {};
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
@ -1065,42 +1106,19 @@ pub const Editor = struct {
|
|||
return cell;
|
||||
}
|
||||
|
||||
inline fn render_space(self: *const Self, c: *Cell, n: *Plane, buf_col: usize, leading: bool, theme: *const Widget.Theme) struct { usize, usize } {
|
||||
switch (self.render_whitespace) {
|
||||
.visible => {
|
||||
c.set_style(theme.editor_whitespace);
|
||||
_ = n.cell_load(c, "·") catch {};
|
||||
},
|
||||
.indent => {
|
||||
c.set_style(theme.editor_whitespace);
|
||||
_ = n.cell_load(c, if (leading) if (buf_col % self.indent_size == 0) "│" else " " else " ") catch {};
|
||||
},
|
||||
else => {
|
||||
_ = n.cell_load(c, " ") catch {};
|
||||
},
|
||||
}
|
||||
inline fn render_space(self: *const Self, c: *Cell, n: *Plane) struct { usize, usize } {
|
||||
const char = whitespace.char;
|
||||
_ = n.cell_load(c, switch (self.render_whitespace) {
|
||||
.visible => char.visible,
|
||||
else => char.blank,
|
||||
}) catch {};
|
||||
return .{ 1, 1 };
|
||||
}
|
||||
|
||||
inline fn render_tab(self: *const Self, c: *Cell, n: *Plane, abs_col: usize, theme: *const Widget.Theme, end_cell: *?Cell) struct { usize, usize } {
|
||||
const begin_char = "-";
|
||||
const end_char = ">";
|
||||
inline fn render_tab(self: *const Self, c: *Cell, n: *Plane, abs_col: usize) struct { usize, usize } {
|
||||
const char = whitespace.char;
|
||||
const colcount = 1 + self.tab_width - (abs_col % self.tab_width);
|
||||
switch (self.render_whitespace) {
|
||||
.visible => {
|
||||
c.set_style(theme.editor_whitespace);
|
||||
_ = n.cell_load(c, if (colcount == 2) end_char else begin_char) catch {};
|
||||
if (colcount > 1) {
|
||||
end_cell.* = n.cell_init();
|
||||
const c_: *Cell = &end_cell.*.?;
|
||||
_ = n.cell_load(c_, end_char) catch {};
|
||||
c_.set_style(theme.editor_whitespace);
|
||||
}
|
||||
},
|
||||
else => {
|
||||
_ = n.cell_load(c, " ") catch {};
|
||||
},
|
||||
}
|
||||
_ = n.cell_load(c, char.blank) catch {};
|
||||
return .{ 1, colcount };
|
||||
}
|
||||
|
||||
|
@ -1177,6 +1195,127 @@ pub const Editor = struct {
|
|||
return syn.render(&ctx, Ctx.cb, range);
|
||||
}
|
||||
|
||||
fn render_whitespace_map(self: *Self, theme: *const Widget.Theme, cell_map: CellMap) !void {
|
||||
const char = whitespace.char;
|
||||
const frame = tracy.initZone(@src(), .{ .name = "editor whitespace map" });
|
||||
defer frame.deinit();
|
||||
for (0..cell_map.rows) |y| {
|
||||
var leading = true;
|
||||
var leading_space = false;
|
||||
var tab_error = false;
|
||||
for (0..cell_map.cols) |x| {
|
||||
const cell_map_entry = cell_map.get_yx(y, x);
|
||||
const cell_type = cell_map_entry.cell_type;
|
||||
const next_cell_map_entry = cell_map.get_yx(y, x + 1);
|
||||
const next_cell_type = next_cell_map_entry.cell_type;
|
||||
switch (cell_type) {
|
||||
.space => {
|
||||
leading_space = true;
|
||||
tab_error = false;
|
||||
},
|
||||
.empty, .character, .eol => {
|
||||
leading = false;
|
||||
leading_space = false;
|
||||
tab_error = false;
|
||||
},
|
||||
.tab => {
|
||||
if (leading_space) tab_error = true;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
if (cell_type == .character)
|
||||
continue;
|
||||
self.plane.cursor_move_yx(@intCast(y), @intCast(x)) catch return;
|
||||
var cell = self.plane.cell_init();
|
||||
_ = self.plane.at_cursor_cell(&cell) catch return;
|
||||
switch (self.render_whitespace) {
|
||||
.indent => {
|
||||
if (leading and x % self.indent_size == 0)
|
||||
cell.cell.char.grapheme = char.indent;
|
||||
},
|
||||
.leading => {
|
||||
if (leading) {
|
||||
if (get_whitespace_char(cell_type, next_cell_type)) |c|
|
||||
cell.cell.char.grapheme = c;
|
||||
}
|
||||
},
|
||||
.eol => {
|
||||
if (cell_type == .eol)
|
||||
cell.cell.char.grapheme = char.eol;
|
||||
},
|
||||
.tabs => {
|
||||
if (cell_type == .tab or cell_type == .extension) {
|
||||
if (get_whitespace_char(cell_type, next_cell_type)) |c|
|
||||
cell.cell.char.grapheme = c;
|
||||
}
|
||||
},
|
||||
.visible => {
|
||||
if (get_whitespace_char(cell_type, next_cell_type)) |c|
|
||||
cell.cell.char.grapheme = c;
|
||||
},
|
||||
.full => {
|
||||
cell.cell.char.grapheme = get_whitespace_char(cell_type, next_cell_type) orelse switch (cell_type) {
|
||||
.eol => char.eol,
|
||||
.empty => "_",
|
||||
else => "#",
|
||||
};
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
if (tab_error) {
|
||||
cell.set_style_fg(theme.editor_error);
|
||||
if (get_whitespace_char(cell_type, next_cell_type)) |c|
|
||||
cell.cell.char.grapheme = c;
|
||||
} else {
|
||||
cell.set_style_fg(theme.editor_whitespace);
|
||||
}
|
||||
_ = self.plane.putc(&cell) catch {};
|
||||
}
|
||||
var eol = cell_map.cols;
|
||||
while (eol > 0) : (eol -= 1)
|
||||
switch (cell_map.get_yx(y, eol).cell_type) {
|
||||
.empty => continue,
|
||||
.eol => break,
|
||||
else => eol = 1,
|
||||
};
|
||||
if (eol > 0) {
|
||||
var trailing = eol;
|
||||
while (trailing > 0) : (trailing -= 1) {
|
||||
const cell_map_entry = cell_map.get_yx(y, trailing);
|
||||
switch (cell_map_entry.cell_type) {
|
||||
.space, .tab, .extension, .eol, .empty => {},
|
||||
.character => {
|
||||
trailing += 1;
|
||||
break;
|
||||
},
|
||||
}
|
||||
if (cell_map_entry.cursor)
|
||||
break;
|
||||
}
|
||||
for (trailing..eol) |x| {
|
||||
const cell_type = cell_map.get_yx(y, x).cell_type;
|
||||
const next_cell_type = cell_map.get_yx(y, x + 1).cell_type;
|
||||
self.plane.cursor_move_yx(@intCast(y), @intCast(x)) catch return;
|
||||
var cell = self.plane.cell_init();
|
||||
_ = self.plane.at_cursor_cell(&cell) catch return;
|
||||
cell.cell.char.grapheme = get_whitespace_char(cell_type, next_cell_type) orelse continue;
|
||||
cell.set_style_fg(theme.editor_error);
|
||||
_ = self.plane.putc(&cell) catch {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_whitespace_char(cell_type: CellType, next_cell_type: CellType) ?[]const u8 {
|
||||
const char = whitespace.char;
|
||||
return switch (cell_type) {
|
||||
.space => char.visible,
|
||||
.tab => if (next_cell_type != .extension) char.tab_end else char.tab_begin,
|
||||
.extension => if (next_cell_type != .extension) char.tab_end else char.tab_begin,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn style_cache_lookup(theme: *const Widget.Theme, cache: *StyleCache, scope: []const u8, id: u32) ?Widget.Theme.Token {
|
||||
return if (cache.get(id)) |sty| ret: {
|
||||
break :ret sty;
|
||||
|
@ -4645,3 +4784,27 @@ pub const PosToWidthCache = struct {
|
|||
return .{ .begin = .{ .row = start.row, .col = start_col }, .end = .{ .row = end.row, .col = end_col } };
|
||||
}
|
||||
};
|
||||
|
||||
fn ViewMap(T: type, default: T) type {
|
||||
return struct {
|
||||
rows: usize,
|
||||
cols: usize,
|
||||
data: []T = &[_]T{},
|
||||
fn init(allocator: std.mem.Allocator, rows: usize, cols: usize) !@This() {
|
||||
const data = try allocator.alloc(T, rows * cols);
|
||||
@memset(data[0 .. rows * cols], default);
|
||||
return .{ .rows = rows, .cols = cols, .data = data };
|
||||
}
|
||||
fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||
allocator.free(self.data);
|
||||
}
|
||||
fn set_yx(self: @This(), y: usize, x: usize, value: T) void {
|
||||
if (y >= self.rows or x >= self.cols) return;
|
||||
self.data[y * self.cols + x] = value;
|
||||
}
|
||||
fn get_yx(self: @This(), y: usize, x: usize) T {
|
||||
if (y >= self.rows or x >= self.cols) return default;
|
||||
return self.data[y * self.cols + x];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -685,7 +685,15 @@ const cmds = struct {
|
|||
self.config.whitespace_mode = if (std.mem.eql(u8, self.config.whitespace_mode, "none"))
|
||||
"indent"
|
||||
else if (std.mem.eql(u8, self.config.whitespace_mode, "indent"))
|
||||
"leading"
|
||||
else if (std.mem.eql(u8, self.config.whitespace_mode, "leading"))
|
||||
"eol"
|
||||
else if (std.mem.eql(u8, self.config.whitespace_mode, "eol"))
|
||||
"tabs"
|
||||
else if (std.mem.eql(u8, self.config.whitespace_mode, "tabs"))
|
||||
"visible"
|
||||
else if (std.mem.eql(u8, self.config.whitespace_mode, "visible"))
|
||||
"full"
|
||||
else
|
||||
"none";
|
||||
try self.save_config();
|
||||
|
|
Loading…
Add table
Reference in a new issue