feat: use diffs instead of cursor nudges to refresh tree-sitter
This commit is contained in:
parent
d412f92cc4
commit
f313ea4a09
1 changed files with 119 additions and 54 deletions
|
@ -36,7 +36,7 @@ const scroll_step_small = 3;
|
||||||
const scroll_cursor_min_border_distance = 5;
|
const scroll_cursor_min_border_distance = 5;
|
||||||
|
|
||||||
const double_click_time_ms = 350;
|
const double_click_time_ms = 350;
|
||||||
const syntax_full_reparse_time_limit = 30; // ms
|
const syntax_full_reparse_time_limit = 0; // ms (0 = always use incremental)
|
||||||
|
|
||||||
pub const max_matches = if (builtin.mode == std.builtin.OptimizeMode.Debug) 10_000 else 100_000;
|
pub const max_matches = if (builtin.mode == std.builtin.OptimizeMode.Debug) 10_000 else 100_000;
|
||||||
pub const max_match_lines = 15;
|
pub const max_match_lines = 15;
|
||||||
|
@ -249,8 +249,9 @@ pub const Editor = struct {
|
||||||
|
|
||||||
syntax: ?*syntax = null,
|
syntax: ?*syntax = null,
|
||||||
syntax_no_render: bool = false,
|
syntax_no_render: bool = false,
|
||||||
|
syntax_report_timing: bool = false,
|
||||||
syntax_refresh_full: bool = false,
|
syntax_refresh_full: bool = false,
|
||||||
syntax_token: usize = 0,
|
syntax_last_rendered_root: ?Buffer.Root = null,
|
||||||
syntax_incremental_reparse: bool = false,
|
syntax_incremental_reparse: bool = false,
|
||||||
|
|
||||||
style_cache: ?StyleCache = null,
|
style_cache: ?StyleCache = null,
|
||||||
|
@ -441,9 +442,15 @@ pub const Editor = struct {
|
||||||
if (self.buffer) |_| try self.close();
|
if (self.buffer) |_| try self.close();
|
||||||
self.buffer = new_buf;
|
self.buffer = new_buf;
|
||||||
|
|
||||||
|
if (new_buf.root.lines() > root_mod.max_syntax_lines) {
|
||||||
|
self.logger.print("large file threshold {d} lines < file size {d} lines", .{
|
||||||
|
root_mod.max_syntax_lines,
|
||||||
|
new_buf.root.lines(),
|
||||||
|
});
|
||||||
|
self.logger.print("syntax highlighting disabled", .{});
|
||||||
|
self.syntax_no_render = true;
|
||||||
|
}
|
||||||
self.syntax = syntax: {
|
self.syntax = syntax: {
|
||||||
if (new_buf.root.lines() > root_mod.max_syntax_lines)
|
|
||||||
break :syntax null;
|
|
||||||
const lang_override = tp.env.get().str("language");
|
const lang_override = tp.env.get().str("language");
|
||||||
var content = std.ArrayList(u8).init(self.allocator);
|
var content = std.ArrayList(u8).init(self.allocator);
|
||||||
defer content.deinit();
|
defer content.deinit();
|
||||||
|
@ -458,6 +465,7 @@ pub const Editor = struct {
|
||||||
break :syntax syn;
|
break :syntax syn;
|
||||||
};
|
};
|
||||||
self.syntax_no_render = tp.env.get().is("no-syntax");
|
self.syntax_no_render = tp.env.get().is("no-syntax");
|
||||||
|
self.syntax_report_timing = tp.env.get().is("syntax-report-timing");
|
||||||
|
|
||||||
const ftn = if (self.syntax) |syn| syn.file_type.name else "text";
|
const ftn = if (self.syntax) |syn| syn.file_type.name else "text";
|
||||||
const fti = if (self.syntax) |syn| syn.file_type.icon else "🖹";
|
const fti = if (self.syntax) |syn| syn.file_type.icon else "🖹";
|
||||||
|
@ -1510,28 +1518,15 @@ pub const Editor = struct {
|
||||||
self.collapse_cursors();
|
self.collapse_cursors();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nudge_insert(self: *Self, nudge: Selection, exclude: *const CurSel, size: usize) void {
|
fn nudge_insert(self: *Self, nudge: Selection, exclude: *const CurSel, _: usize) void {
|
||||||
for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel|
|
for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel|
|
||||||
if (cursel != exclude)
|
if (cursel != exclude)
|
||||||
cursel.nudge_insert(nudge);
|
cursel.nudge_insert(nudge);
|
||||||
for (self.matches.items) |*match_| if (match_.*) |*match|
|
for (self.matches.items) |*match_| if (match_.*) |*match|
|
||||||
match.nudge_insert(nudge);
|
match.nudge_insert(nudge);
|
||||||
if (self.syntax) |syn| {
|
|
||||||
const root = self.buf_root() catch return;
|
|
||||||
const eol_mode = self.buf_eol_mode() catch return;
|
|
||||||
const start_byte = root.get_byte_pos(nudge.begin, self.metrics, eol_mode) catch return;
|
|
||||||
syn.edit(.{
|
|
||||||
.start_byte = @intCast(start_byte),
|
|
||||||
.old_end_byte = @intCast(start_byte),
|
|
||||||
.new_end_byte = @intCast(start_byte + size),
|
|
||||||
.start_point = .{ .row = @intCast(nudge.begin.row), .column = @intCast(nudge.begin.col) },
|
|
||||||
.old_end_point = .{ .row = @intCast(nudge.begin.row), .column = @intCast(nudge.begin.col) },
|
|
||||||
.new_end_point = .{ .row = @intCast(nudge.end.row), .column = @intCast(nudge.end.col) },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nudge_delete(self: *Self, nudge: Selection, exclude: *const CurSel, size: usize) void {
|
fn nudge_delete(self: *Self, nudge: Selection, exclude: *const CurSel, _: usize) void {
|
||||||
for (self.cursels.items, 0..) |*cursel_, i| if (cursel_.*) |*cursel|
|
for (self.cursels.items, 0..) |*cursel_, i| if (cursel_.*) |*cursel|
|
||||||
if (cursel != exclude)
|
if (cursel != exclude)
|
||||||
if (!cursel.nudge_delete(nudge)) {
|
if (!cursel.nudge_delete(nudge)) {
|
||||||
|
@ -1541,19 +1536,6 @@ pub const Editor = struct {
|
||||||
if (!match.nudge_delete(nudge)) {
|
if (!match.nudge_delete(nudge)) {
|
||||||
self.matches.items[i] = null;
|
self.matches.items[i] = null;
|
||||||
};
|
};
|
||||||
if (self.syntax) |syn| {
|
|
||||||
const root = self.buf_root() catch return;
|
|
||||||
const eol_mode = self.buf_eol_mode() catch return;
|
|
||||||
const start_byte = root.get_byte_pos(nudge.begin, self.metrics, eol_mode) catch return;
|
|
||||||
syn.edit(.{
|
|
||||||
.start_byte = @intCast(start_byte),
|
|
||||||
.old_end_byte = @intCast(start_byte + size),
|
|
||||||
.new_end_byte = @intCast(start_byte),
|
|
||||||
.start_point = .{ .row = @intCast(nudge.begin.row), .column = @intCast(nudge.begin.col) },
|
|
||||||
.old_end_point = .{ .row = @intCast(nudge.end.row), .column = @intCast(nudge.end.col) },
|
|
||||||
.new_end_point = .{ .row = @intCast(nudge.begin.row), .column = @intCast(nudge.begin.col) },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_selection(self: *Self, root: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {
|
fn delete_selection(self: *Self, root: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {
|
||||||
|
@ -3071,36 +3053,74 @@ pub const Editor = struct {
|
||||||
pub const disable_jump_mode_meta = .{ .interactive = false };
|
pub const disable_jump_mode_meta = .{ .interactive = false };
|
||||||
|
|
||||||
fn update_syntax(self: *Self) !void {
|
fn update_syntax(self: *Self) !void {
|
||||||
const frame = tracy.initZone(@src(), .{ .name = "editor update syntax" });
|
|
||||||
defer frame.deinit();
|
|
||||||
const root = try self.buf_root();
|
const root = try self.buf_root();
|
||||||
const eol_mode = try self.buf_eol_mode();
|
const eol_mode = try self.buf_eol_mode();
|
||||||
const token = @intFromPtr(root);
|
if (self.syntax_last_rendered_root == root)
|
||||||
if (root.lines() > root_mod.max_syntax_lines)
|
|
||||||
return;
|
|
||||||
if (self.syntax_token == token)
|
|
||||||
return;
|
return;
|
||||||
|
var kind: enum { full, incremental, none } = .none;
|
||||||
|
var edit_count: usize = 0;
|
||||||
|
const start_time = std.time.milliTimestamp();
|
||||||
if (self.syntax) |syn| {
|
if (self.syntax) |syn| {
|
||||||
if (self.syntax_no_render) {
|
if (self.syntax_no_render) {
|
||||||
|
const frame = tracy.initZone(@src(), .{ .name = "editor reset syntax" });
|
||||||
|
defer frame.deinit();
|
||||||
syn.reset();
|
syn.reset();
|
||||||
self.syntax_token = 0;
|
self.syntax_last_rendered_root = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!self.syntax_incremental_reparse)
|
if (!self.syntax_incremental_reparse)
|
||||||
self.syntax_refresh_full = true;
|
self.syntax_refresh_full = true;
|
||||||
if (self.syntax_refresh_full) {
|
if (self.syntax_last_rendered_root == null)
|
||||||
syn.reset();
|
self.syntax_refresh_full = true;
|
||||||
const start_time = std.time.milliTimestamp();
|
|
||||||
var content = std.ArrayList(u8).init(self.allocator);
|
var content = std.ArrayList(u8).init(self.allocator);
|
||||||
defer content.deinit();
|
defer content.deinit();
|
||||||
|
{
|
||||||
|
const frame = tracy.initZone(@src(), .{ .name = "editor store syntax" });
|
||||||
|
defer frame.deinit();
|
||||||
try root.store(content.writer(), eol_mode);
|
try root.store(content.writer(), eol_mode);
|
||||||
|
}
|
||||||
|
if (self.syntax_refresh_full) {
|
||||||
|
{
|
||||||
|
const frame = tracy.initZone(@src(), .{ .name = "editor reset syntax" });
|
||||||
|
defer frame.deinit();
|
||||||
|
syn.reset();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const frame = tracy.initZone(@src(), .{ .name = "editor refresh_full syntax" });
|
||||||
|
defer frame.deinit();
|
||||||
try syn.refresh_full(content.items);
|
try syn.refresh_full(content.items);
|
||||||
|
}
|
||||||
|
kind = .full;
|
||||||
|
self.syntax_last_rendered_root = root;
|
||||||
self.syntax_refresh_full = false;
|
self.syntax_refresh_full = false;
|
||||||
const end_time = std.time.milliTimestamp();
|
|
||||||
if (end_time - start_time > syntax_full_reparse_time_limit)
|
|
||||||
self.syntax_incremental_reparse = true;
|
|
||||||
} else {
|
} else {
|
||||||
try syn.refresh_from_buffer(root, self.metrics);
|
if (self.syntax_last_rendered_root) |root_src| {
|
||||||
|
self.syntax_last_rendered_root = null;
|
||||||
|
var old_content = std.ArrayList(u8).init(self.allocator);
|
||||||
|
defer old_content.deinit();
|
||||||
|
{
|
||||||
|
const frame = tracy.initZone(@src(), .{ .name = "editor store syntax" });
|
||||||
|
defer frame.deinit();
|
||||||
|
try root_src.store(old_content.writer(), eol_mode);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const frame = tracy.initZone(@src(), .{ .name = "editor diff syntax" });
|
||||||
|
defer frame.deinit();
|
||||||
|
const diff = @import("diff");
|
||||||
|
const edits = try diff.diff(self.allocator, content.items, old_content.items);
|
||||||
|
defer self.allocator.free(edits);
|
||||||
|
for (edits) |edit|
|
||||||
|
syntax_process_edit(syn, edit);
|
||||||
|
edit_count = edits.len;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const frame = tracy.initZone(@src(), .{ .name = "editor refresh syntax" });
|
||||||
|
defer frame.deinit();
|
||||||
|
try syn.refresh(content.items);
|
||||||
|
}
|
||||||
|
self.syntax_last_rendered_root = root;
|
||||||
|
kind = .incremental;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var content = std.ArrayList(u8).init(self.allocator);
|
var content = std.ArrayList(u8).init(self.allocator);
|
||||||
|
@ -3110,10 +3130,43 @@ pub const Editor = struct {
|
||||||
error.NotFound => null,
|
error.NotFound => null,
|
||||||
else => return e,
|
else => return e,
|
||||||
};
|
};
|
||||||
if (self.syntax_no_render) return;
|
if (!self.syntax_no_render) {
|
||||||
if (self.syntax) |syn| try syn.refresh_full(content.items);
|
if (self.syntax) |syn| {
|
||||||
|
const frame = tracy.initZone(@src(), .{ .name = "editor parse syntax" });
|
||||||
|
defer frame.deinit();
|
||||||
|
try syn.refresh_full(content.items);
|
||||||
|
self.syntax_last_rendered_root = root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const end_time = std.time.milliTimestamp();
|
||||||
|
if (kind == .full or kind == .incremental) {
|
||||||
|
const update_time = end_time - start_time;
|
||||||
|
self.syntax_incremental_reparse = end_time - start_time > syntax_full_reparse_time_limit;
|
||||||
|
if (self.syntax_report_timing)
|
||||||
|
self.logger.print("syntax update {s} time: {d}ms ({d} edits)", .{ @tagName(kind), update_time, edit_count });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syntax_process_edit(syn: *syntax, edit: @import("diff").Diff) void {
|
||||||
|
switch (edit.kind) {
|
||||||
|
.insert => syn.edit(.{
|
||||||
|
.start_byte = @intCast(edit.start),
|
||||||
|
.old_end_byte = @intCast(edit.start),
|
||||||
|
.new_end_byte = @intCast(edit.start + edit.bytes.len),
|
||||||
|
.start_point = .{ .row = 0, .column = 0 },
|
||||||
|
.old_end_point = .{ .row = 0, .column = 0 },
|
||||||
|
.new_end_point = .{ .row = 0, .column = 0 },
|
||||||
|
}),
|
||||||
|
.delete => syn.edit(.{
|
||||||
|
.start_byte = @intCast(edit.start),
|
||||||
|
.old_end_byte = @intCast(edit.start + edit.bytes.len),
|
||||||
|
.new_end_byte = @intCast(edit.start),
|
||||||
|
.start_point = .{ .row = 0, .column = 0 },
|
||||||
|
.old_end_point = .{ .row = 0, .column = 0 },
|
||||||
|
.new_end_point = .{ .row = 0, .column = 0 },
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
self.syntax_token = token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_syntax(self: *Self) void {
|
fn reset_syntax(self: *Self) void {
|
||||||
|
@ -4014,11 +4067,23 @@ pub const Editor = struct {
|
||||||
pub fn toggle_syntax_highlighting(self: *Self, _: Context) Result {
|
pub fn toggle_syntax_highlighting(self: *Self, _: Context) Result {
|
||||||
self.syntax_no_render = !self.syntax_no_render;
|
self.syntax_no_render = !self.syntax_no_render;
|
||||||
if (self.syntax_no_render) {
|
if (self.syntax_no_render) {
|
||||||
if (self.syntax) |syn| syn.reset();
|
if (self.syntax) |syn| {
|
||||||
self.syntax_token = 0;
|
const frame = tracy.initZone(@src(), .{ .name = "editor reset syntax" });
|
||||||
|
defer frame.deinit();
|
||||||
|
syn.reset();
|
||||||
|
self.syntax_last_rendered_root = null;
|
||||||
|
self.syntax_refresh_full = true;
|
||||||
|
self.syntax_incremental_reparse = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.logger.print("syntax highlighting {s}", .{if (self.syntax_no_render) "disabled" else "enabled"});
|
||||||
|
}
|
||||||
pub const toggle_syntax_highlighting_meta = .{ .description = "Toggle syntax highlighting" };
|
pub const toggle_syntax_highlighting_meta = .{ .description = "Toggle syntax highlighting" };
|
||||||
|
|
||||||
|
pub fn toggle_syntax_timing(self: *Self, _: Context) Result {
|
||||||
|
self.syntax_report_timing = !self.syntax_report_timing;
|
||||||
|
}
|
||||||
|
pub const toggle_syntax_timing_meta = .{ .description = "Toggle tree-sitter timing reports" };
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn create(allocator: Allocator, parent: Widget) !Widget {
|
pub fn create(allocator: Allocator, parent: Widget) !Widget {
|
||||||
|
|
Loading…
Add table
Reference in a new issue