feat: show diagnostics in the file list view if they refer to another file

This commit is contained in:
CJ van den Berg 2024-08-18 16:21:38 +02:00
parent 28640633be
commit 4127cf8bcf
4 changed files with 115 additions and 53 deletions

View file

@ -485,25 +485,25 @@ pub fn references(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usi
if (try response.match(.{ "child", tp.string, "result", tp.null_ })) { if (try response.match(.{ "child", tp.string, "result", tp.null_ })) {
return; return;
} else if (try response.match(.{ "child", tp.string, "result", tp.extract_cbor(&locations) })) { } else if (try response.match(.{ "child", tp.string, "result", tp.extract_cbor(&locations) })) {
try self.send_location_list(from, locations); try self.send_reference_list(from, locations);
} }
} }
fn send_location_list(self: *Self, to: tp.pid_ref, locations: []const u8) !void { fn send_reference_list(self: *Self, to: tp.pid_ref, locations: []const u8) !void {
defer to.send(.{ "FIF", "done" }) catch {}; defer to.send(.{ "REF", "done" }) catch {};
var iter = locations; var iter = locations;
var len = try cbor.decodeArrayHeader(&iter); var len = try cbor.decodeArrayHeader(&iter);
const count = len; const count = len;
while (len > 0) : (len -= 1) { while (len > 0) : (len -= 1) {
var location: []const u8 = undefined; var location: []const u8 = undefined;
if (try cbor.matchValue(&iter, cbor.extract_cbor(&location))) { if (try cbor.matchValue(&iter, cbor.extract_cbor(&location))) {
try self.send_location(to, location); try self.send_reference(to, location);
} else return error.InvalidMessageField; } else return error.InvalidMessageField;
} }
log.logger("lsp").print("found {d} references", .{count}); log.logger("lsp").print("found {d} references", .{count});
} }
fn send_location(self: *Self, to: tp.pid_ref, location: []const u8) !void { fn send_reference(self: *Self, to: tp.pid_ref, location: []const u8) !void {
var iter = location; var iter = location;
var targetUri: ?[]const u8 = null; var targetUri: ?[]const u8 = null;
var targetRange: ?Range = null; var targetRange: ?Range = null;
@ -545,7 +545,7 @@ fn send_location(self: *Self, to: tp.pid_ref, location: []const u8) !void {
else else
file_path; file_path;
try to.send(.{ try to.send(.{
"FIF", "REF",
file_path_, file_path_,
targetRange.?.start.line + 1, targetRange.?.start.line + 1,
targetRange.?.start.character, targetRange.?.start.character,

View file

@ -164,7 +164,7 @@ pub const Diagnostic = struct {
a.free(self.message); a.free(self.message);
} }
const Severity = enum { Error, Warning, Information, Hint }; pub const Severity = enum { Error, Warning, Information, Hint };
pub fn get_severity(self: Diagnostic) Severity { pub fn get_severity(self: Diagnostic) Severity {
return to_severity(self.severity); return to_severity(self.severity);
} }
@ -3314,7 +3314,7 @@ pub const Editor = struct {
} }
pub fn goto_next_diagnostic(self: *Self, _: Context) Result { pub fn goto_next_diagnostic(self: *Self, _: Context) Result {
if (self.diagnostics.items.len == 0) return; if (self.diagnostics.items.len == 0) return command.executeName("goto_next_file", .{});
self.sort_diagnostics(); self.sort_diagnostics();
const primary = self.get_primary(); const primary = self.get_primary();
for (self.diagnostics.items) |*diag| { for (self.diagnostics.items) |*diag| {
@ -3325,7 +3325,7 @@ pub const Editor = struct {
} }
pub fn goto_prev_diagnostic(self: *Self, _: Context) Result { pub fn goto_prev_diagnostic(self: *Self, _: Context) Result {
if (self.diagnostics.items.len == 0) return; if (self.diagnostics.items.len == 0) return command.executeName("goto_prev_file", .{});
self.sort_diagnostics(); self.sort_diagnostics();
const primary = self.get_primary(); const primary = self.get_primary();
var i = self.diagnostics.items.len - 1; var i = self.diagnostics.items.len - 1;
@ -3440,21 +3440,6 @@ pub const Editor = struct {
return project_manager.completion(file_path, primary.cursor.row, primary.cursor.col); return project_manager.completion(file_path, primary.cursor.row, primary.cursor.col);
} }
pub fn clear_diagnostics(self: *Self, ctx: Context) Result {
var file_path: []const u8 = undefined;
if (!try ctx.args.match(.{tp.extract(&file_path)})) return error.InvalidArgument;
file_path = project_manager.normalize_file_path(file_path);
if (!std.mem.eql(u8, file_path, self.file_path orelse return)) return;
for (self.diagnostics.items) |*d| d.deinit(self.diagnostics.allocator);
self.diagnostics.clearRetainingCapacity();
self.diag_errors = 0;
self.diag_warnings = 0;
self.diag_info = 0;
self.diag_hints = 0;
self.send_editor_diagnostics() catch {};
self.need_render();
}
pub fn add_diagnostic( pub fn add_diagnostic(
self: *Self, self: *Self,
file_path: []const u8, file_path: []const u8,
@ -3484,6 +3469,16 @@ pub const Editor = struct {
self.need_render(); self.need_render();
} }
pub fn clear_diagnostics(self: *Self) void {
self.diagnostics.clearRetainingCapacity();
self.diag_errors = 0;
self.diag_warnings = 0;
self.diag_info = 0;
self.diag_hints = 0;
self.send_editor_diagnostics() catch {};
self.need_render();
}
pub fn select(self: *Self, ctx: Context) Result { pub fn select(self: *Self, ctx: Context) Result {
var sel: Selection = .{}; var sel: Selection = .{};
if (!try ctx.args.match(.{ tp.extract(&sel.begin.row), tp.extract(&sel.begin.col), tp.extract(&sel.end.row), tp.extract(&sel.end.col) })) if (!try ctx.args.match(.{ tp.extract(&sel.begin.row), tp.extract(&sel.begin.col), tp.extract(&sel.end.row), tp.extract(&sel.end.col) }))

View file

@ -22,6 +22,7 @@ const Menu = @import("Menu.zig");
const EventHandler = @import("EventHandler.zig"); const EventHandler = @import("EventHandler.zig");
const Button = @import("Button.zig"); const Button = @import("Button.zig");
const scrollbar_v = @import("scrollbar_v.zig"); const scrollbar_v = @import("scrollbar_v.zig");
const editor = @import("editor.zig");
const escape = fmt.fmtSliceEscapeLower; const escape = fmt.fmtSliceEscapeLower;
@ -52,6 +53,7 @@ const Entry = struct {
end_line: usize, end_line: usize,
end_pos: usize, end_pos: usize,
lines: []const u8, lines: []const u8,
severity: editor.Diagnostic.Severity = .Information,
}; };
pub fn create(allocator: Allocator, parent: Plane) !Widget { pub fn create(allocator: Allocator, parent: Plane) !Widget {
@ -137,7 +139,10 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
fn handle_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool { fn handle_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
const style_base = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.panel; const style_base = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.panel;
const style_info: Widget.Theme.Style = .{ .fg = theme.editor_information.fg, .fs = theme.editor_information.fs, .bg = style_base.bg }; const style_hint: Widget.Theme.Style = .{ .fg = theme.editor_hint.fg, .fs = theme.editor_hint.fs, .bg = style_base.bg };
const style_information: Widget.Theme.Style = .{ .fg = theme.editor_information.fg, .fs = theme.editor_information.fs, .bg = style_base.bg };
const style_warning: Widget.Theme.Style = .{ .fg = theme.editor_warning.fg, .fs = theme.editor_warning.fs, .bg = style_base.bg };
const style_error: Widget.Theme.Style = .{ .fg = theme.editor_error.fg, .fs = theme.editor_error.fs, .bg = style_base.bg };
const style_separator: Widget.Theme.Style = .{ .fg = theme.editor_selection.bg, .bg = style_base.bg }; const style_separator: Widget.Theme.Style = .{ .fg = theme.editor_selection.bg, .bg = style_base.bg };
// const style_error: Widget.Theme.Style = .{ .fg = theme.editor_error.fg, .fs = theme.editor_error.fs, .bg = style_base.bg }; // const style_error: Widget.Theme.Style = .{ .fg = theme.editor_error.fg, .fs = theme.editor_error.fs, .bg = style_base.bg };
var idx: usize = undefined; var idx: usize = undefined;
@ -166,7 +171,12 @@ fn handle_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), th
button.plane.cursor_move_yx(0, @intCast(max_len)) catch return false; button.plane.cursor_move_yx(0, @intCast(max_len)) catch return false;
button.plane.set_style(style_separator); button.plane.set_style(style_separator);
_ = button.plane.print("", .{}) catch {}; _ = button.plane.print("", .{}) catch {};
button.plane.set_style(style_info); switch (entry.severity) {
.Hint => button.plane.set_style(style_hint),
.Information => button.plane.set_style(style_information),
.Warning => button.plane.set_style(style_warning),
.Error => button.plane.set_style(style_error),
}
_ = button.plane.print("{s}", .{entry.lines}) catch {}; _ = button.plane.print("{s}", .{entry.lines}) catch {};
return false; return false;
} }

View file

@ -22,6 +22,9 @@ const WidgetStack = @import("WidgetStack.zig");
const ed = @import("editor.zig"); const ed = @import("editor.zig");
const home = @import("home.zig"); const home = @import("home.zig");
const logview = @import("logview.zig");
const filelist_view = @import("filelist_view.zig");
const Self = @This(); const Self = @This();
const Commands = command.Collection(cmds); const Commands = command.Collection(cmds);
@ -38,6 +41,7 @@ last_match_text: ?[]const u8 = null,
location_history: location_history, location_history: location_history,
file_stack: std.ArrayList([]const u8), file_stack: std.ArrayList([]const u8),
find_in_files_done: bool = false, find_in_files_done: bool = false,
file_list_type: FileListType = .find_in_files,
panel_height: ?usize = null, panel_height: ?usize = null,
const NavState = struct { const NavState = struct {
@ -49,6 +53,12 @@ const NavState = struct {
matches: usize = 0, matches: usize = 0,
}; };
const FileListType = enum {
diagnostics,
references,
find_in_files,
};
pub fn create(a: std.mem.Allocator) !Widget { pub fn create(a: std.mem.Allocator) !Widget {
const self = try a.create(Self); const self = try a.create(Self);
self.* = .{ self.* = .{
@ -92,8 +102,14 @@ pub fn receive(self: *Self, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
var end_line: usize = undefined; var end_line: usize = undefined;
var end_pos: usize = undefined; var end_pos: usize = undefined;
var lines: []const u8 = undefined; var lines: []const u8 = undefined;
if (try m.match(.{ "FIF", tp.extract(&path), tp.extract(&begin_line), tp.extract(&begin_pos), tp.extract(&end_line), tp.extract(&end_pos), tp.extract(&lines) })) { if (try m.match(.{ "REF", tp.extract(&path), tp.extract(&begin_line), tp.extract(&begin_pos), tp.extract(&end_line), tp.extract(&end_pos), tp.extract(&lines) })) {
try self.add_find_in_files_result(path, begin_line, begin_pos, end_line, end_pos, lines); try self.add_find_in_files_result(.references, path, begin_line, begin_pos, end_line, end_pos, lines, .Information);
return true;
} else if (try m.match(.{ "FIF", tp.extract(&path), tp.extract(&begin_line), tp.extract(&begin_pos), tp.extract(&end_line), tp.extract(&end_pos), tp.extract(&lines) })) {
try self.add_find_in_files_result(.find_in_files, path, begin_line, begin_pos, end_line, end_pos, lines, .Information);
return true;
} else if (try m.match(.{ "REF", "done" })) {
self.find_in_files_done = true;
return true; return true;
} else if (try m.match(.{ "FIF", "done" })) { } else if (try m.match(.{ "FIF", "done" })) {
self.find_in_files_done = true; self.find_in_files_done = true;
@ -145,8 +161,7 @@ fn statusbar_primary_drag(self: *Self, y: usize) tp.result {
panels.layout = .{ .static = self.panel_height.? }; panels.layout = .{ .static = self.panel_height.? };
} }
fn toggle_panel_view(self: *Self, view: anytype, enable_only: bool) !bool { fn toggle_panel_view(self: *Self, view: anytype, enable_only: bool) !void {
var enabled = true;
if (self.panels) |panels| { if (self.panels) |panels| {
if (panels.get(@typeName(view))) |w| { if (panels.get(@typeName(view))) |w| {
if (!enable_only) { if (!enable_only) {
@ -155,7 +170,6 @@ fn toggle_panel_view(self: *Self, view: anytype, enable_only: bool) !bool {
self.widgets.remove(panels.widget()); self.widgets.remove(panels.widget());
self.panels = null; self.panels = null;
} }
enabled = false;
} }
} else { } else {
try panels.add(try view.create(self.a, self.widgets.plane)); try panels.add(try view.create(self.a, self.widgets.plane));
@ -167,7 +181,6 @@ fn toggle_panel_view(self: *Self, view: anytype, enable_only: bool) !bool {
self.panels = panels; self.panels = panels;
} }
tui.current().resize(); tui.current().resize();
return enabled;
} }
fn get_panel_view(self: *Self, comptime view: type) ?*view { fn get_panel_view(self: *Self, comptime view: type) ?*view {
@ -312,32 +325,32 @@ const cmds = struct {
} }
pub fn toggle_panel(self: *Self, _: Ctx) Result { pub fn toggle_panel(self: *Self, _: Ctx) Result {
if (self.is_panel_view_showing(@import("logview.zig"))) if (self.is_panel_view_showing(logview))
_ = try self.toggle_panel_view(@import("logview.zig"), false) try self.toggle_panel_view(logview, false)
else if (self.is_panel_view_showing(@import("filelist_view.zig"))) else if (self.is_panel_view_showing(filelist_view))
_ = try self.toggle_panel_view(@import("filelist_view.zig"), false) try self.toggle_panel_view(filelist_view, false)
else else
_ = try self.toggle_panel_view(@import("logview.zig"), false); try self.toggle_panel_view(logview, false);
} }
pub fn toggle_logview(self: *Self, _: Ctx) Result { pub fn toggle_logview(self: *Self, _: Ctx) Result {
_ = try self.toggle_panel_view(@import("logview.zig"), false); try self.toggle_panel_view(logview, false);
} }
pub fn show_logview(self: *Self, _: Ctx) Result { pub fn show_logview(self: *Self, _: Ctx) Result {
_ = try self.toggle_panel_view(@import("logview.zig"), true); try self.toggle_panel_view(logview, true);
} }
pub fn toggle_inputview(self: *Self, _: Ctx) Result { pub fn toggle_inputview(self: *Self, _: Ctx) Result {
_ = try self.toggle_panel_view(@import("inputview.zig"), false); try self.toggle_panel_view(@import("inputview.zig"), false);
} }
pub fn toggle_inspector_view(self: *Self, _: Ctx) Result { pub fn toggle_inspector_view(self: *Self, _: Ctx) Result {
_ = try self.toggle_panel_view(@import("inspector_view.zig"), false); try self.toggle_panel_view(@import("inspector_view.zig"), false);
} }
pub fn show_inspector_view(self: *Self, _: Ctx) Result { pub fn show_inspector_view(self: *Self, _: Ctx) Result {
_ = try self.toggle_panel_view(@import("inspector_view.zig"), true); try self.toggle_panel_view(@import("inspector_view.zig"), true);
} }
pub fn jump_back(self: *Self, _: Ctx) Result { pub fn jump_back(self: *Self, _: Ctx) Result {
@ -377,18 +390,22 @@ const cmds = struct {
} }
pub fn goto_next_file_or_diagnostic(self: *Self, ctx: Ctx) Result { pub fn goto_next_file_or_diagnostic(self: *Self, ctx: Ctx) Result {
const filelist_view = @import("filelist_view.zig");
if (self.is_panel_view_showing(filelist_view)) { if (self.is_panel_view_showing(filelist_view)) {
try command.executeName("goto_next_file", ctx); switch (self.file_list_type) {
.diagnostics => try command.executeName("goto_next_diagnostic", ctx),
else => try command.executeName("goto_next_file", ctx),
}
} else { } else {
try command.executeName("goto_next_diagnostic", ctx); try command.executeName("goto_next_diagnostic", ctx);
} }
} }
pub fn goto_prev_file_or_diagnostic(self: *Self, ctx: Ctx) Result { pub fn goto_prev_file_or_diagnostic(self: *Self, ctx: Ctx) Result {
const filelist_view = @import("filelist_view.zig");
if (self.is_panel_view_showing(filelist_view)) { if (self.is_panel_view_showing(filelist_view)) {
try command.executeName("goto_prev_file", ctx); switch (self.file_list_type) {
.diagnostics => try command.executeName("goto_prev_diagnostic", ctx),
else => try command.executeName("goto_prev_file", ctx),
}
} else { } else {
try command.executeName("goto_prev_diagnostic", ctx); try command.executeName("goto_prev_diagnostic", ctx);
} }
@ -414,7 +431,21 @@ const cmds = struct {
})) return error.InvalidArgument; })) return error.InvalidArgument;
file_path = project_manager.normalize_file_path(file_path); file_path = project_manager.normalize_file_path(file_path);
if (self.editor) |editor| if (std.mem.eql(u8, file_path, editor.file_path orelse "")) if (self.editor) |editor| if (std.mem.eql(u8, file_path, editor.file_path orelse ""))
try editor.add_diagnostic(file_path, source, code, message, severity, sel); try editor.add_diagnostic(file_path, source, code, message, severity, sel)
else
try self.add_find_in_files_result(.diagnostics, file_path, sel.begin.row, sel.begin.col, sel.end.row, sel.end.col, message, ed.Diagnostic.to_severity(severity));
}
pub fn clear_diagnostics(self: *Self, ctx: Ctx) Result {
var file_path: []const u8 = undefined;
if (!try ctx.args.match(.{tp.extract(&file_path)})) return error.InvalidArgument;
file_path = project_manager.normalize_file_path(file_path);
if (self.editor) |editor| if (std.mem.eql(u8, file_path, editor.file_path orelse ""))
editor.clear_diagnostics();
self.clear_find_in_files_results(.diagnostics);
if (self.file_list_type == .diagnostics and self.is_panel_view_showing(filelist_view))
try self.toggle_panel_view(filelist_view, false);
} }
}; };
@ -597,14 +628,40 @@ fn pop_file_stack(self: *Self, closed: ?[]const u8) ?[]const u8 {
return self.file_stack.popOrNull(); return self.file_stack.popOrNull();
} }
fn add_find_in_files_result(self: *Self, path: []const u8, begin_line: usize, begin_pos: usize, end_line: usize, end_pos: usize, lines: []const u8) tp.result { fn add_find_in_files_result(
const filelist_view = @import("filelist_view.zig"); self: *Self,
file_list_type: FileListType,
path: []const u8,
begin_line: usize,
begin_pos: usize,
end_line: usize,
end_pos: usize,
lines: []const u8,
severity: ed.Diagnostic.Severity,
) tp.result {
if (!self.is_panel_view_showing(filelist_view)) if (!self.is_panel_view_showing(filelist_view))
_ = self.toggle_panel_view(filelist_view, false) catch |e| return tp.exit_error(e, @errorReturnTrace()); _ = self.toggle_panel_view(filelist_view, false) catch |e| return tp.exit_error(e, @errorReturnTrace());
const fl = self.get_panel_view(filelist_view) orelse @panic("filelist_view missing"); const fl = self.get_panel_view(filelist_view) orelse @panic("filelist_view missing");
if (self.find_in_files_done) { if (self.find_in_files_done or self.file_list_type != file_list_type) {
self.find_in_files_done = false; self.clear_find_in_files_results(self.file_list_type);
fl.reset(); self.file_list_type = file_list_type;
} }
fl.add_item(.{ .path = path, .begin_line = @max(1, begin_line) - 1, .begin_pos = @max(1, begin_pos) - 1, .end_line = @max(1, end_line) - 1, .end_pos = @max(1, end_pos) - 1, .lines = lines }) catch |e| return tp.exit_error(e, @errorReturnTrace()); fl.add_item(.{
.path = path,
.begin_line = @max(1, begin_line) - 1,
.begin_pos = @max(1, begin_pos) - 1,
.end_line = @max(1, end_line) - 1,
.end_pos = @max(1, end_pos) - 1,
.lines = lines,
.severity = severity,
}) catch |e| return tp.exit_error(e, @errorReturnTrace());
}
fn clear_find_in_files_results(self: *Self, file_list_type: FileListType) void {
if (self.file_list_type != file_list_type) return;
if (!self.is_panel_view_showing(filelist_view)) return;
const fl = self.get_panel_view(filelist_view) orelse @panic("filelist_view missing");
self.find_in_files_done = false;
self.file_list_type = file_list_type;
fl.reset();
} }