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_ })) {
return;
} 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 {
defer to.send(.{ "FIF", "done" }) catch {};
fn send_reference_list(self: *Self, to: tp.pid_ref, locations: []const u8) !void {
defer to.send(.{ "REF", "done" }) catch {};
var iter = locations;
var len = try cbor.decodeArrayHeader(&iter);
const count = len;
while (len > 0) : (len -= 1) {
var location: []const u8 = undefined;
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;
}
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 targetUri: ?[]const u8 = null;
var targetRange: ?Range = null;
@ -545,7 +545,7 @@ fn send_location(self: *Self, to: tp.pid_ref, location: []const u8) !void {
else
file_path;
try to.send(.{
"FIF",
"REF",
file_path_,
targetRange.?.start.line + 1,
targetRange.?.start.character,

View file

@ -164,7 +164,7 @@ pub const Diagnostic = struct {
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 {
return to_severity(self.severity);
}
@ -3314,7 +3314,7 @@ pub const Editor = struct {
}
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();
const primary = self.get_primary();
for (self.diagnostics.items) |*diag| {
@ -3325,7 +3325,7 @@ pub const Editor = struct {
}
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();
const primary = self.get_primary();
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);
}
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(
self: *Self,
file_path: []const u8,
@ -3484,6 +3469,16 @@ pub const Editor = struct {
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 {
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) }))

View file

@ -22,6 +22,7 @@ const Menu = @import("Menu.zig");
const EventHandler = @import("EventHandler.zig");
const Button = @import("Button.zig");
const scrollbar_v = @import("scrollbar_v.zig");
const editor = @import("editor.zig");
const escape = fmt.fmtSliceEscapeLower;
@ -52,6 +53,7 @@ const Entry = struct {
end_line: usize,
end_pos: usize,
lines: []const u8,
severity: editor.Diagnostic.Severity = .Information,
};
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 {
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_error: Widget.Theme.Style = .{ .fg = theme.editor_error.fg, .fs = theme.editor_error.fs, .bg = style_base.bg };
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.set_style(style_separator);
_ = 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 {};
return false;
}

View file

@ -22,6 +22,9 @@ const WidgetStack = @import("WidgetStack.zig");
const ed = @import("editor.zig");
const home = @import("home.zig");
const logview = @import("logview.zig");
const filelist_view = @import("filelist_view.zig");
const Self = @This();
const Commands = command.Collection(cmds);
@ -38,6 +41,7 @@ last_match_text: ?[]const u8 = null,
location_history: location_history,
file_stack: std.ArrayList([]const u8),
find_in_files_done: bool = false,
file_list_type: FileListType = .find_in_files,
panel_height: ?usize = null,
const NavState = struct {
@ -49,6 +53,12 @@ const NavState = struct {
matches: usize = 0,
};
const FileListType = enum {
diagnostics,
references,
find_in_files,
};
pub fn create(a: std.mem.Allocator) !Widget {
const self = try a.create(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_pos: usize = 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) })) {
try self.add_find_in_files_result(path, begin_line, begin_pos, end_line, end_pos, 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(.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;
} else if (try m.match(.{ "FIF", "done" })) {
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.? };
}
fn toggle_panel_view(self: *Self, view: anytype, enable_only: bool) !bool {
var enabled = true;
fn toggle_panel_view(self: *Self, view: anytype, enable_only: bool) !void {
if (self.panels) |panels| {
if (panels.get(@typeName(view))) |w| {
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.panels = null;
}
enabled = false;
}
} else {
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;
}
tui.current().resize();
return enabled;
}
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 {
if (self.is_panel_view_showing(@import("logview.zig")))
_ = try self.toggle_panel_view(@import("logview.zig"), false)
else if (self.is_panel_view_showing(@import("filelist_view.zig")))
_ = try self.toggle_panel_view(@import("filelist_view.zig"), false)
if (self.is_panel_view_showing(logview))
try self.toggle_panel_view(logview, false)
else if (self.is_panel_view_showing(filelist_view))
try self.toggle_panel_view(filelist_view, false)
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 {
_ = try self.toggle_panel_view(@import("logview.zig"), false);
try self.toggle_panel_view(logview, false);
}
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 {
_ = 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 {
_ = 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 {
_ = 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 {
@ -377,18 +390,22 @@ const cmds = struct {
}
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)) {
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 {
try command.executeName("goto_next_diagnostic", ctx);
}
}
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)) {
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 {
try command.executeName("goto_prev_diagnostic", ctx);
}
@ -414,7 +431,21 @@ const cmds = struct {
})) 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 ""))
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();
}
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 {
const filelist_view = @import("filelist_view.zig");
fn add_find_in_files_result(
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))
_ = 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");
if (self.find_in_files_done) {
self.find_in_files_done = false;
fl.reset();
if (self.find_in_files_done or self.file_list_type != file_list_type) {
self.clear_find_in_files_results(self.file_list_type);
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();
}