Compare commits

...

3 commits

Author SHA1 Message Date
1e02d978de
fix: render inserted and changed lines the same in the diff gutter
The diffing algo is unstable which causes the diff gutter to change
a lot while typing. This is a little annoying and not that useful so
we'll just render them the same until we get a stable diff algo.
2026-01-13 12:44:41 +01:00
25d5f80a4c
refactor: drop all c_(u)int usage in Plane
The use of c_int and c_uint comes from the days when flow supported
building agains notcurses has a c API.
2026-01-13 12:44:40 +01:00
35be98f95c
fix: Make absolute Plane writing functions update column correctly
Writing to an absolute plane position should update the plane's cursor
position before incrementing it even if it is not actually used.

closes #446
2026-01-13 12:44:40 +01:00
9 changed files with 95 additions and 84 deletions

View file

@ -25,7 +25,7 @@ pub const Metrics = struct {
egc_chunk_width: egc_chunk_width_func,
egc_last: egc_last_func,
tab_width: usize,
pub const egc_length_func = *const fn (self: Metrics, egcs: []const u8, colcount: *c_int, abs_col: usize) usize;
pub const egc_length_func = *const fn (self: Metrics, egcs: []const u8, colcount: *usize, abs_col: usize) usize;
pub const egc_chunk_width_func = *const fn (self: Metrics, chunk_: []const u8, abs_col_: usize) usize;
pub const egc_last_func = *const fn (self: Metrics, egcs: []const u8) []const u8;
};
@ -185,7 +185,7 @@ pub const Leaf = struct {
fn pos_to_width(self: *const Leaf, pos: *usize, abs_col_: usize, metrics: Metrics) usize {
var col: usize = 0;
var abs_col = abs_col_;
var cols: c_int = 0;
var cols: usize = 0;
var buf = self.buf;
while (buf.len > 0 and pos.* > 0) {
if (buf[0] == '\t') {
@ -214,7 +214,7 @@ pub const Leaf = struct {
inline fn width_to_pos(self: *const Leaf, col_: usize, abs_col_: usize, metrics: Metrics) !usize {
var abs_col = abs_col_;
var col = col_;
var cols: c_int = 0;
var cols: usize = 0;
var buf = self.buf;
return while (buf.len > 0) {
if (col == 0)
@ -242,7 +242,7 @@ pub const Leaf = struct {
}
fn debug_render_chunk(chunk: []const u8, l: *std.Io.Writer, metrics: Metrics) !void {
var cols: c_int = 0;
var cols: usize = 0;
var buf = chunk;
while (buf.len > 0) {
switch (buf[0]) {
@ -511,7 +511,7 @@ const Node = union(enum) {
const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_)));
var buf: []const u8 = leaf.buf;
while (buf.len > 0) {
var cols: c_int = undefined;
var cols: usize = undefined;
const bytes = metrics.egc_length(metrics, buf, &cols, ctx.abs_col);
const ret = ctx.walker_f(ctx.walker_ctx, buf[0..bytes], @intCast(cols), metrics);
if (ret.err) |e| return .{ .err = e };

View file

@ -78,23 +78,23 @@ pub fn erase(self: Plane) void {
self.window.fill(.{ .style = self.style_base });
}
pub inline fn abs_y(self: Plane) c_int {
return @intCast(self.window.y_off);
pub inline fn abs_y(self: Plane) i32 {
return self.window.y_off;
}
pub inline fn abs_x(self: Plane) c_int {
return @intCast(self.window.x_off);
pub inline fn abs_x(self: Plane) i32 {
return self.window.x_off;
}
pub inline fn dim_y(self: Plane) c_uint {
return @intCast(self.window.height);
pub inline fn dim_y(self: Plane) u31 {
return self.window.height;
}
pub inline fn dim_x(self: Plane) c_uint {
return @intCast(self.window.width);
pub inline fn dim_x(self: Plane) u31 {
return self.window.width;
}
pub fn abs_yx_to_rel_nearest_x(self: Plane, y: c_int, x: c_int, xoffset: c_int) struct { c_int, c_int } {
pub fn abs_yx_to_rel_nearest_x(self: Plane, y: i32, x: i32, xoffset: i32) struct { i32, i32 } {
if (self.window.screen.width == 0 or self.window.screen.height == 0) return self.abs_yx_to_rel(y, x);
const xextra = self.window.screen.width_pix % self.window.screen.width;
const xcell = (self.window.screen.width_pix - xextra) / self.window.screen.width;
@ -105,26 +105,26 @@ pub fn abs_yx_to_rel_nearest_x(self: Plane, y: c_int, x: c_int, xoffset: c_int)
return self.abs_yx_to_rel(y, x);
}
pub fn abs_yx_to_rel(self: Plane, y: c_int, x: c_int) struct { c_int, c_int } {
pub fn abs_yx_to_rel(self: Plane, y: i32, x: i32) struct { i32, i32 } {
return .{ y - self.abs_y(), x - self.abs_x() };
}
pub fn abs_y_to_rel(self: Plane, y: c_int) c_int {
pub fn abs_y_to_rel(self: Plane, y: i32) i32 {
return y - self.abs_y();
}
pub fn rel_yx_to_abs(self: Plane, y: c_int, x: c_int) struct { c_int, c_int } {
pub fn rel_yx_to_abs(self: Plane, y: i32, x: i32) struct { i32, i32 } {
return .{ self.abs_y() + y, self.abs_x() + x };
}
pub fn hide(_: Plane) void {}
pub fn move_yx(self: *Plane, y: c_int, x: c_int) !void {
pub fn move_yx(self: *Plane, y: i32, x: i32) !void {
self.window.y_off = @intCast(y);
self.window.x_off = @intCast(x);
}
pub fn resize_simple(self: *Plane, ylen: c_uint, xlen: c_uint) !void {
pub fn resize_simple(self: *Plane, ylen: u32, xlen: u32) !void {
self.window.height = @intCast(ylen);
self.window.width = @intCast(xlen);
}
@ -137,7 +137,8 @@ pub fn home(self: *Plane) void {
pub fn fill(self: *Plane, egc: []const u8) void {
for (0..self.dim_y()) |y|
for (0..self.dim_x()) |x|
self.write_cell(x, y, egc);
self.write_cell(@intCast(x), @intCast(y), egc);
if (self.col > 100000) std.log.debug("column: {}", .{self.col});
}
pub fn fill_width(self: *Plane, comptime fmt: anytype, args: anytype) !usize {
@ -159,12 +160,12 @@ pub fn print(self: *Plane, comptime fmt: anytype, args: anytype) !usize {
return self.putstr(text);
}
pub fn print_aligned_right(self: *Plane, y: c_int, comptime fmt: anytype, args: anytype) !usize {
pub fn print_aligned_right(self: *Plane, y: i32, comptime fmt: anytype, args: anytype) !usize {
var buf: [fmt.len + 4096]u8 = undefined;
const width = self.window.width;
const text = try std.fmt.bufPrint(&buf, fmt, args);
const text_width = self.egc_chunk_width(text, 0, 8);
self.row = @intCast(y);
self.row = y;
self.col = @intCast(if (text_width >= width) 0 else width - text_width);
return self.putstr(text);
}
@ -180,12 +181,12 @@ pub fn print_right(self: *Plane, comptime fmt: anytype, args: anytype) !usize {
return self.putstr(text);
}
pub fn print_aligned_center(self: *Plane, y: c_int, comptime fmt: anytype, args: anytype) !usize {
pub fn print_aligned_center(self: *Plane, y: i32, comptime fmt: anytype, args: anytype) !usize {
var buf: [fmt.len + 4096]u8 = undefined;
const width = self.window.width;
const text = try std.fmt.bufPrint(&buf, fmt, args);
const text_width = self.egc_chunk_width(text, 0, 8);
self.row = @intCast(y);
self.row = y;
self.col = @intCast(if (text_width >= width) 0 else (width - text_width) / 2);
return self.putstr(text);
}
@ -212,7 +213,7 @@ pub fn putstr(self: *Plane, text: []const u8) !usize {
self.col = 0;
} else return result;
}
self.write_cell(@intCast(self.col), @intCast(self.row), s);
self.write_cell(self.col, self.row, s);
result += 1;
}
return result;
@ -227,30 +228,30 @@ pub fn putstr_unicode(self: *Plane, text: []const u8) !usize {
0...31 => |code| Buffer.unicode.control_code_to_unicode(code),
else => s_,
};
self.write_cell(@intCast(self.col), @intCast(self.row), s);
self.write_cell(self.col, self.row, s);
result += 1;
}
return result;
}
pub fn putchar(self: *Plane, ecg: []const u8) void {
self.write_cell(@intCast(self.col), @intCast(self.row), ecg);
self.write_cell(self.col, self.row, ecg);
}
pub fn putc(self: *Plane, cell: *const Cell) !usize {
return self.putc_yx(@intCast(self.row), @intCast(self.col), cell);
return self.putc_yx(self.row, self.col, cell);
}
pub fn putc_yx(self: *Plane, y: c_int, x: c_int, cell: *const Cell) !usize {
pub fn putc_yx(self: *Plane, y: i32, x: i32, cell: *const Cell) !usize {
try self.cursor_move_yx(y, x);
const w = if (cell.cell.char.width == 0) self.window.gwidth(cell.cell.char.grapheme) else cell.cell.char.width;
if (w == 0) return w;
self.window.writeCell(@intCast(self.col), @intCast(self.row), cell.cell);
self.col += @intCast(w);
self.col += w;
return w;
}
fn write_cell(self: *Plane, col: usize, row: usize, egc: []const u8) void {
fn write_cell(self: *Plane, col: i32, row: i32, egc: []const u8) void {
var cell: vaxis.Cell = self.window.readCell(@intCast(col), @intCast(row)) orelse .{ .style = self.style };
const w = self.window.gwidth(egc);
cell.char.grapheme = self.cache.put(egc);
@ -261,39 +262,39 @@ fn write_cell(self: *Plane, col: usize, row: usize, egc: []const u8) void {
cell.style = self.style;
}
self.window.writeCell(@intCast(col), @intCast(row), cell);
self.col += @intCast(w);
self.row = row;
self.col = col;
self.col += w;
}
pub fn cursor_yx(self: Plane, y: *c_uint, x: *c_uint) void {
y.* = @intCast(self.row);
x.* = @intCast(self.col);
pub fn cursor_yx(self: Plane, y: *i32, x: *i32) void {
y.* = self.row;
x.* = self.col;
}
pub fn cursor_y(self: Plane) c_uint {
return @intCast(self.row);
pub fn cursor_y(self: Plane) i32 {
return self.row;
}
pub fn cursor_x(self: Plane) c_uint {
return @intCast(self.col);
pub fn cursor_x(self: Plane) i32 {
return self.col;
}
pub fn cursor_move_yx(self: *Plane, y: c_int, x: c_int) !void {
pub fn cursor_move_yx(self: *Plane, y: i32, x: i32) !void {
if (self.window.height == 0 or self.window.width == 0) return;
if (self.window.height <= y or self.window.width <= x) return;
if (y >= 0)
self.row = @intCast(y);
if (x >= 0)
self.col = @intCast(x);
self.row = y;
self.col = x;
}
pub fn cursor_move_rel(self: *Plane, y: c_int, x: c_int) !void {
pub fn cursor_move_rel(self: *Plane, y: i32, x: i32) !void {
if (self.window.height == 0 or self.window.width == 0) return error.OutOfBounds;
const new_y: isize = @as(c_int, @intCast(self.row)) + y;
const new_x: isize = @as(c_int, @intCast(self.col)) + x;
const new_y = self.row + y;
const new_x = self.col + x;
if (new_y < 0 or new_x < 0) return error.OutOfBounds;
if (self.window.height <= new_y or self.window.width <= new_x) return error.OutOfBounds;
self.row = @intCast(new_y);
self.col = @intCast(new_x);
self.row = new_y;
self.col = new_x;
}
pub fn cell_init(self: Plane) Cell {
@ -301,7 +302,7 @@ pub fn cell_init(self: Plane) Cell {
}
pub fn cell_load(self: *Plane, cell: *Cell, gcluster: []const u8) !usize {
var cols: c_int = 0;
var cols: usize = 0;
const bytes = self.egc_length(gcluster, &cols, 0, 1);
cell.cell.char.grapheme = self.cache.put(gcluster[0..bytes]);
cell.cell.char.width = @intCast(cols);
@ -354,12 +355,12 @@ pub fn set_bg_rgb_alpha(self: *Plane, alpha_bg: ThemeColor, col: ThemeColor) !vo
self.style.bg = apply_alpha_theme(alpha_bg, col);
}
pub fn set_fg_palindex(self: *Plane, idx: c_uint) !void {
self.style.fg = .{ .index = @intCast(idx) };
pub fn set_fg_palindex(self: *Plane, idx: u8) !void {
self.style.fg = .{ .index = idx };
}
pub fn set_bg_palindex(self: *Plane, idx: c_uint) !void {
self.style.bg = .{ .index = @intCast(idx) };
pub fn set_bg_palindex(self: *Plane, idx: u8) !void {
self.style.bg = .{ .index = idx };
}
pub inline fn set_base_style(self: *Plane, style_: Style) void {
@ -443,7 +444,7 @@ inline fn is_control_code(c: u8) bool {
};
}
pub fn egc_length(self: *const Plane, egcs: []const u8, colcount: *c_int, abs_col: usize, tab_width: usize) usize {
pub fn egc_length(self: *const Plane, egcs: []const u8, colcount: *usize, abs_col: usize, tab_width: usize) usize {
if (egcs.len == 0) {
colcount.* = 0;
return 0;
@ -453,7 +454,7 @@ pub fn egc_length(self: *const Plane, egcs: []const u8, colcount: *c_int, abs_co
return 1;
}
if (egcs[0] == '\t') {
colcount.* = @intCast(tab_width - (abs_col % tab_width));
colcount.* = tab_width - (abs_col % tab_width);
return 1;
}
var iter = vaxis.unicode.graphemeIterator(egcs);
@ -463,7 +464,7 @@ pub fn egc_length(self: *const Plane, egcs: []const u8, colcount: *c_int, abs_co
};
const s = grapheme.bytes(egcs);
const w = self.window.gwidth(s);
colcount.* = @intCast(w);
colcount.* = w;
return s.len;
}
@ -471,11 +472,11 @@ pub fn egc_chunk_width(self: *const Plane, chunk_: []const u8, abs_col_: usize,
var abs_col = abs_col_;
var chunk = chunk_;
var colcount: usize = 0;
var cols: c_int = 0;
var cols: usize = 0;
while (chunk.len > 0) {
const bytes = self.egc_length(chunk, &cols, abs_col, tab_width);
colcount += @intCast(cols);
abs_col += @intCast(cols);
colcount += cols;
abs_col += cols;
if (chunk.len < bytes) break;
chunk = chunk[bytes..];
}
@ -486,11 +487,11 @@ pub fn egc_chunk_col_pos(self: *const Plane, chunk_: []const u8, abs_col_: usize
var abs_col = abs_col_;
var chunk = chunk_;
var colcount: usize = 0;
var cols: c_int = 0;
var cols: usize = 0;
while (chunk.len > 0 and colcount < col) {
const bytes = self.egc_length(chunk, &cols, abs_col, tab_width);
colcount += @intCast(cols);
abs_col += @intCast(cols);
colcount += cols;
abs_col += cols;
if (chunk.len < bytes) break;
chunk = chunk[bytes..];
}
@ -508,7 +509,7 @@ pub fn metrics(self: *const Plane, tab_width: usize) Buffer.Metrics {
return .{
.ctx = self,
.egc_length = struct {
fn f(self_: Buffer.Metrics, egcs: []const u8, colcount: *c_int, abs_col: usize) usize {
fn f(self_: Buffer.Metrics, egcs: []const u8, colcount: *usize, abs_col: usize) usize {
const plane: *const Plane = @ptrCast(@alignCast(self_.ctx));
return plane.egc_length(egcs, colcount, abs_col, self_.tab_width);
}

View file

@ -39,8 +39,8 @@ bracketed_paste_buffer: std.Io.Writer.Allocating,
handler_ctx: *anyopaque,
dispatch_input: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null,
dispatch_mouse: ?*const fn (ctx: *anyopaque, y: c_int, x: c_int, cbor_msg: []const u8) void = null,
dispatch_mouse_drag: ?*const fn (ctx: *anyopaque, y: c_int, x: c_int, cbor_msg: []const u8) void = null,
dispatch_mouse: ?*const fn (ctx: *anyopaque, y: i32, x: i32, cbor_msg: []const u8) void = null,
dispatch_mouse_drag: ?*const fn (ctx: *anyopaque, y: i32, x: i32, cbor_msg: []const u8) void = null,
dispatch_event: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null,
logger: log.Logger,

View file

@ -83,8 +83,8 @@ event_buffer: std.Io.Writer.Allocating,
handler_ctx: *anyopaque,
dispatch_initialized: *const fn (ctx: *anyopaque) void,
dispatch_input: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null,
dispatch_mouse: ?*const fn (ctx: *anyopaque, y: c_int, x: c_int, cbor_msg: []const u8) void = null,
dispatch_mouse_drag: ?*const fn (ctx: *anyopaque, y: c_int, x: c_int, cbor_msg: []const u8) void = null,
dispatch_mouse: ?*const fn (ctx: *anyopaque, y: i32, x: i32, cbor_msg: []const u8) void = null,
dispatch_mouse_drag: ?*const fn (ctx: *anyopaque, y: i32, x: i32, cbor_msg: []const u8) void = null,
dispatch_event: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null,
thread: ?std.Thread = null,
@ -473,7 +473,7 @@ pub fn request_mouse_cursor_default(self: *Self, push_or_pop: bool) void {
_ = push_or_pop;
//@panic("todo");
}
pub fn cursor_enable(self: *Self, y: c_int, x: c_int, shape: CursorShape) !void {
pub fn cursor_enable(self: *Self, y: i32, x: i32, shape: CursorShape) !void {
_ = self;
_ = y;
_ = x;
@ -488,7 +488,7 @@ pub fn clear_all_multi_cursors(self: *Self) !void {
_ = self;
//@panic("todo");
}
pub fn show_multi_cursor_yx(self: *Self, y: c_int, x: c_int) !void {
pub fn show_multi_cursor_yx(self: *Self, y: i32, x: i32) !void {
_ = self;
_ = y;
_ = x;

View file

@ -1361,8 +1361,8 @@ pub const Editor = struct {
}
fn render_matches(self: *const Self, last_idx: *usize, theme: *const Widget.Theme, cell: *Cell) void {
var y: c_uint = undefined;
var x: c_uint = undefined;
var y: i32 = undefined;
var x: i32 = undefined;
self.plane.cursor_yx(&y, &x);
while (true) {
if (last_idx.* >= self.matches.items.len)
@ -1380,8 +1380,8 @@ pub const Editor = struct {
}
fn render_selections(self: *const Self, theme: *const Widget.Theme, cell: *Cell) void {
var y: c_uint = undefined;
var x: c_uint = undefined;
var y: i32 = undefined;
var x: i32 = undefined;
self.plane.cursor_yx(&y, &x);
for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel|
@ -1728,7 +1728,9 @@ pub const Editor = struct {
return style_cache_lookup(theme, cache, scope, id);
}
inline fn is_point_in_selection(self: *const Self, sel_: anytype, y: c_uint, x: c_uint) bool {
inline fn is_point_in_selection(self: *const Self, sel_: anytype, y_: i32, x_: i32) bool {
const y: u32 = if (y_ < 0) return false else @intCast(y_);
const x: u32 = if (x_ < 0) return false else @intCast(x_);
const sel = sel_;
const row = self.view.row + y;
const col = self.view.col + x;
@ -1737,11 +1739,14 @@ pub const Editor = struct {
return sel.begin.row <= row and row <= sel.end.row and b_col <= col and col < e_col;
}
inline fn is_point_before_selection(self: *const Self, sel_: anytype, y: c_uint, x: c_uint) bool {
inline fn is_point_before_selection(self: *const Self, sel_: anytype, y_: i32, x_: i32) bool {
const y: u32 = if (y_ < 0) return true else @intCast(y_);
const sel = sel_;
const row = self.view.row + y;
if (row < sel.begin.row) return true;
const x: u32 = if (x_ < 0) return true else @intCast(x_);
const col = self.view.col + x;
return row < sel.begin.row or (row == sel.begin.row and col < sel.begin.col);
return row == sel.begin.row and col < sel.begin.col;
}
inline fn screen_cursor(self: *const Self, cursor: *const Cursor) ?Cursor {
@ -3958,7 +3963,7 @@ pub const Editor = struct {
var text_egcs = text;
while (text_egcs.len > 0) {
var colcount: c_int = 1;
var colcount: usize = 1;
const egc_len = self.metrics.egc_length(self.metrics, text_egcs, &colcount, 0);
switch (text_egcs[0]) {
'\t' => underlined.writer.writeAll("\t") catch {},

View file

@ -259,7 +259,12 @@ inline fn render_diff_symbols(self: *Self, diff_symbols: *[]Symbol, pos: usize,
if ((diff_symbols.*)[0].line > linenum) return;
const sym = (diff_symbols.*)[0];
const char = switch (sym.kind) {
const kind: Kind = switch (sym.kind) {
.insert => .insert,
.modified => .insert, //TODO: we map .modified to .insert here because the diff algo is unstable
.delete => .delete,
};
const char = switch (kind) {
.insert => "",
.modified => "",
.delete => "",
@ -268,7 +273,7 @@ inline fn render_diff_symbols(self: *Self, diff_symbols: *[]Symbol, pos: usize,
self.plane.cursor_move_yx(@intCast(pos), @intCast(self.get_width() - 1)) catch return;
var cell = self.plane.cell_init();
_ = self.plane.at_cursor_cell(&cell) catch return;
cell.set_style_fg(switch (sym.kind) {
cell.set_style_fg(switch (kind) {
.insert => theme.editor_gutter_added,
.modified => theme.editor_gutter_modified,
.delete => theme.editor_gutter_deleted,

View file

@ -439,8 +439,8 @@ const Tab = struct {
tabbar: *TabBar,
buffer_ref: usize,
tab_style: *const Style,
close_pos: ?c_uint = null,
save_pos: ?c_uint = null,
close_pos: ?i32 = null,
save_pos: ?i32 = null,
on_event: ?EventHandler = null,
const Mode = enum { active, inactive, selected };

View file

@ -8,7 +8,7 @@ fn metrics() Buffer.Metrics {
return .{
.ctx = undefined,
.egc_length = struct {
fn f(_: Buffer.Metrics, _: []const u8, colcount: *c_int, _: usize) usize {
fn f(_: Buffer.Metrics, _: []const u8, colcount: *usize, _: usize) usize {
colcount.* = 1;
return 1;
}

View file

@ -49,7 +49,7 @@ fn metrics() Buffer.Metrics {
return .{
.ctx = undefined,
.egc_length = struct {
fn f(_: Buffer.Metrics, _: []const u8, colcount: *c_int, _: usize) usize {
fn f(_: Buffer.Metrics, _: []const u8, colcount: *usize, _: usize) usize {
colcount.* = 1;
return 1;
}