diff --git a/build.zig.zon b/build.zig.zon index 5cb2614..ea5800e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,8 +6,8 @@ .dependencies = .{ .syntax = .{ - .url = "git+https://github.com/neurocyte/flow-syntax?ref=master#851789d5d62f7822574b02eceb4ba3998d5445e5", - .hash = "flow_syntax-0.7.2-X8jOoRJEAQAPdfml35HuLRBlYy88Y5P97AZOC2Jk9ZUV", + .url = "git+https://github.com/neurocyte/flow-syntax?ref=master#272a2d0b01f708dcd02d43c2ed997fd7b82d6224", + .hash = "flow_syntax-0.7.2-X8jOoeFTAQAHSg9vmbAahKdrA8DDf0N-MJl8l09vMDZh", }, .flags = .{ .url = "git+https://github.com/neurocyte/flags?ref=main#984b27948da3e4e40a253f76c85b51ec1a9ada11", diff --git a/contrib/cleanup_nightly_builds b/contrib/cleanup_nightly_builds new file mode 100755 index 0000000..c4e7ac7 --- /dev/null +++ b/contrib/cleanup_nightly_builds @@ -0,0 +1,49 @@ +#!/bin/bash +set -e + +REPO=neurocyte/flow-nightly +KEEP=7 + +if [ -n "$1" ]; then + TEAARGS="--login $1" +else + TEAARGS="--login codeberg" +fi + +RELEASES="$( + page=1 + while :; do + data=$(tea api $TEAARGS "repos/${REPO}/releases?page=${page}&limit=50") + [ "$(echo "$data" | jq length)" = 0 ] && break + echo "$data" + page=$((page + 1)) + done | jq -s 'add | sort_by(.id)|reverse' +)" + +echo latest: +echo "$RELEASES" | jq -r '.[0:1] | .[] | "\(.name // .tag_name)\t\(.assets | length)"' + +echo +echo to keep: +echo "$RELEASES" | jq -r ".[1:${KEEP}] | .[] | \"\(.name // .tag_name)\t\(.assets | length)\"" + +echo +echo to clean-up: +REMOVE="$(echo "$RELEASES" | jq -r ".[${KEEP}:]")" +echo "$REMOVE" | jq -r '.[] | select((.assets | length) > 3) | "\(.name // .tag_name)\t\(.assets | length)"' + +echo +echo running clean-up... +echo + +echo "$REMOVE" | + jq -r '.[] | select((.assets | length) > 3) | "\(.id) \(.tag_name) \(.assets | length) \(.name)"' | + while read -r release_id release_tag asset_count release_name; do + echo + echo "cleaning up $asset_count assets from release $release_tag - $release_name" + echo "$REMOVE" | jq -r ".[] | select(.id == $release_id) | .assets | .[] | \"\(.id) \(.name)\" " | + while read -r asset_id asset_name; do + echo -n " $asset_name " + tea api $TEAARGS -X DELETE "/repos/${REPO}/releases/${release_id}/assets/${asset_id}" || true + done + done diff --git a/src/Project.zig b/src/Project.zig index 817ada8..bb00fda 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -1059,7 +1059,6 @@ fn send_goto_request(self: *Self, from: tp.pid_ref, file_path: []const u8, row: const handler: struct { from: tp.pid, name: []const u8, - project: *Self, pub fn deinit(self_: *@This()) void { std.heap.c_allocator.free(self_.name); @@ -1084,7 +1083,6 @@ fn send_goto_request(self: *Self, from: tp.pid_ref, file_path: []const u8, row: } = .{ .from = from.clone(), .name = try std.heap.c_allocator.dupe(u8, self.name), - .project = self, }; lsp.send_request(self.allocator, method, .{ @@ -1157,7 +1155,6 @@ pub fn references(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usi const handler: struct { from: tp.pid, name: []const u8, - project: *Self, pub fn deinit(self_: *@This()) void { std.heap.c_allocator.free(self_.name); @@ -1170,13 +1167,12 @@ pub fn references(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usi return; } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&locations) })) { const count = try send_reference_list("REF", self_.from.ref(), locations, self_.name); - self_.project.logger_lsp.print("found {d} references", .{count}); + std.log.info("found {d} references", .{count}); } } } = .{ .from = from.clone(), .name = try std.heap.c_allocator.dupe(u8, self.name), - .project = self, }; lsp.send_request(self.allocator, "textDocument/references", .{ @@ -1417,7 +1413,7 @@ fn send_symbol_items(to: tp.pid_ref, file_path: []const u8, items: []const u8) ( var node_count: usize = 0; while (len > 0) : (len -= 1) { if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&item)))) return error.InvalidSymbolInformationArray; - node_count += try send_symbol_information(to, file_path, item, ""); + node_count += try send_symbol_information(to, file_path, item, "", 0); } return to.send(.{ "cmd", "add_document_symbol_done", .{file_path} }) catch |e| { std.log.err("send add_document_symbol_done failed: {t}", .{e}); @@ -1484,7 +1480,7 @@ pub const SymbolInformationError = error{ InvalidSymbolInformationField, InvalidTargetURI, } || LocationLinkError || cbor.Error; -fn send_symbol_information(to: tp.pid_ref, file_path: []const u8, item: []const u8, parent_name: []const u8) SymbolInformationError!usize { +fn send_symbol_information(to: tp.pid_ref, file_path: []const u8, item: []const u8, parent_name: []const u8, depth: u8) SymbolInformationError!usize { var name: []const u8 = ""; var detail: ?[]const u8 = ""; var kind: usize = 0; @@ -1537,7 +1533,7 @@ fn send_symbol_information(to: tp.pid_ref, file_path: []const u8, item: []const var descendant: []const u8 = ""; while (len_ > 0) : (len_ -= 1) { if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&descendant)))) return error.InvalidSymbolInformationField; - descendant_count += try send_symbol_information(to, file_path, descendant, name); + descendant_count += try send_symbol_information(to, file_path, descendant, name, depth + 1); } } else if (std.mem.eql(u8, field_name, "location")) {} else if (std.mem.eql(u8, field_name, "location")) { var location_: []const u8 = undefined; @@ -1569,6 +1565,7 @@ fn send_symbol_information(to: tp.pid_ref, file_path: []const u8, item: []const selectionRange.end.character, deprecated, detail, + depth, } }) catch |e| { std.log.err("send add_document_symbol failed: {t}", .{e}); return 0; @@ -2313,7 +2310,6 @@ fn send_lsp_init_request(self: *Self, from: tp.pid_ref, lsp: *const LSP, project from: tp.pid, language_server: []const u8, lsp: LSP, - project: *Self, project_path: []const u8, pub fn deinit(self_: *@This()) void { @@ -2326,7 +2322,7 @@ fn send_lsp_init_request(self: *Self, from: tp.pid_ref, lsp: *const LSP, project pub fn receive(self_: @This(), response: tp.message) !void { self_.lsp.send_notification("initialized", .{}) catch return error.LspFailed; if (self_.lsp.pid.expired()) return error.LspFailed; - self_.project.logger_lsp.print("initialized LSP: {f}", .{fmt_lsp_name_func(self_.language_server)}); + std.log.info("initialized LSP: {f}", .{fmt_lsp_name_func(self_.language_server)}); var result: []const u8 = undefined; if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) { @@ -2343,7 +2339,6 @@ fn send_lsp_init_request(self: *Self, from: tp.pid_ref, lsp: *const LSP, project .allocator = lsp.allocator, .pid = lsp.pid.clone(), }, - .project = self, .project_path = try std.heap.c_allocator.dupe(u8, project_path), }; @@ -2928,7 +2923,7 @@ pub fn request_vcs_id(self: *Self, file_path: []const u8) error{OutOfMemory}!voi const request = try self.allocator.create(VcsIdRequest); request.* = .{ .allocator = self.allocator, - .project = self, + .project = @intFromPtr(self), .file_path = try self.allocator.dupe(u8, file_path), }; git.rev_parse(@intFromPtr(request), "HEAD", file_path) catch |e| @@ -2937,7 +2932,7 @@ pub fn request_vcs_id(self: *Self, file_path: []const u8) error{OutOfMemory}!voi pub const VcsIdRequest = struct { allocator: std.mem.Allocator, - project: *Self, + project: usize, file_path: []const u8, pub fn deinit(self: *@This()) void { @@ -2950,7 +2945,7 @@ pub fn request_vcs_content(self: *Self, file_path: []const u8, vcs_id: []const u const request = try self.allocator.create(VcsContentRequest); request.* = .{ .allocator = self.allocator, - .project = self, + .project = @intFromPtr(self), .file_path = try self.allocator.dupe(u8, file_path), .vcs_id = try self.allocator.dupe(u8, vcs_id), }; @@ -2960,7 +2955,7 @@ pub fn request_vcs_content(self: *Self, file_path: []const u8, vcs_id: []const u pub const VcsContentRequest = struct { allocator: std.mem.Allocator, - project: *Self, + project: usize, file_path: []const u8, vcs_id: []const u8, @@ -3006,7 +3001,7 @@ pub fn request_vcs_blame(self: *Self, file_path: []const u8) error{OutOfMemory}! const request = try self.allocator.create(GitBlameRequest); request.* = .{ .allocator = self.allocator, - .project = self, + .project = @intFromPtr(self), .file_path = try self.allocator.dupe(u8, file_path), }; git.blame(@intFromPtr(request), file_path) catch |e| @@ -3015,7 +3010,7 @@ pub fn request_vcs_blame(self: *Self, file_path: []const u8) error{OutOfMemory}! pub const GitBlameRequest = struct { allocator: std.mem.Allocator, - project: *Self, + project: usize, file_path: []const u8, pub fn deinit(self: *@This()) void { diff --git a/src/project_manager.zig b/src/project_manager.zig index 9d3a59a..fb8e009 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -429,13 +429,16 @@ const Process = struct { project.walk_tree_done(self.parent.ref()) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } else if (try cbor.match(m.buf, .{ "git", tp.extract(&context), "rev_parse", tp.more })) { const request: *Project.VcsIdRequest = @ptrFromInt(context); - request.project.process_git_response(self.parent.ref(), m) catch |e| self.logger.err("git-rev-parse", e); + if (self.project_from_ref(request.project)) |project| + project.process_git_response(self.parent.ref(), m) catch |e| self.logger.err("git-rev-parse", e); } else if (try cbor.match(m.buf, .{ "git", tp.extract(&context), "cat_file", tp.more })) { const request: *Project.VcsContentRequest = @ptrFromInt(context); - request.project.process_git_response(self.parent.ref(), m) catch |e| self.logger.err("git-cat-file", e); + if (self.project_from_ref(request.project)) |project| + project.process_git_response(self.parent.ref(), m) catch |e| self.logger.err("git-cat-file", e); } else if (try cbor.match(m.buf, .{ "git", tp.extract(&context), "blame", tp.more })) { const request: *Project.GitBlameRequest = @ptrFromInt(context); - request.project.process_git_response(self.parent.ref(), m) catch |e| self.logger.err("git-blame", e); + if (self.project_from_ref(request.project)) |project| + project.process_git_response(self.parent.ref(), m) catch |e| self.logger.err("git-blame", e); } else if (try cbor.match(m.buf, .{ "git", tp.extract(&context), tp.more })) { const project: *Project = @ptrFromInt(context); project.process_git(self.parent.ref(), m) catch {}; @@ -941,6 +944,13 @@ const Process = struct { }.less_fn; std.mem.sort(RecentProject, recent_projects.items, {}, less_fn); } + + fn project_from_ref(self: *const Process, project_ref: usize) ?*Project { + var iter = self.projects.valueIterator(); + while (iter.next()) |project| if (@intFromPtr(project.*) == project_ref) + return project.*; + return null; + } }; fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_: *Project, max_: usize, path_: []const u8) (SpawnError || std.fs.Dir.OpenError)!void { diff --git a/src/tui/filelist_view.zig b/src/tui/filelist_view.zig index 9001b07..f597885 100644 --- a/src/tui/filelist_view.zig +++ b/src/tui/filelist_view.zig @@ -111,12 +111,20 @@ pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool { return self.menu.container_widget.walk(walk_ctx, f) or f(walk_ctx, Widget.to(self)); } +fn entry_less_than(_: void, a: Entry, b: Entry) bool { + const path_order = std.mem.order(u8, a.path, b.path); + if (path_order != .eq) return path_order == .lt; + if (a.begin_line != b.begin_line) return a.begin_line < b.begin_line; + return a.begin_pos < b.begin_pos; +} + pub fn add_item(self: *Self, entry_: Entry) !void { const idx = self.entries.items.len; const entry = (try self.entries.addOne(self.allocator)); entry.* = entry_; entry.path = try self.allocator.dupe(u8, entry_.path); entry.lines = try self.allocator.dupe(u8, entry_.lines); + std.mem.sort(Entry, self.entries.items, {}, entry_less_than); var label: std.Io.Writer.Allocating = .init(self.allocator); defer label.deinit(); cbor.writeValue(&label.writer, idx) catch return; diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index d15b253..f6b18f6 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -1286,6 +1286,7 @@ const cmds = struct { .integer, // selectionRange.end.col .boolean, // deprecated .string, //detail + .integer, //depth }, }; diff --git a/src/tui/mode/overlay/completion_dropdown.zig b/src/tui/mode/overlay/completion_dropdown.zig index d9beb30..ceb7a34 100644 --- a/src/tui/mode/overlay/completion_dropdown.zig +++ b/src/tui/mode/overlay/completion_dropdown.zig @@ -209,6 +209,7 @@ pub fn on_render_menu(self: *Type, button: *Type.ButtonType, theme: *const Widge }; return tui.render_symbol( &button.plane, + 0, values.label, icon_, color, diff --git a/src/tui/mode/overlay/completion_palette.zig b/src/tui/mode/overlay/completion_palette.zig index 57219fa..ae8f9d2 100644 --- a/src/tui/mode/overlay/completion_palette.zig +++ b/src/tui/mode/overlay/completion_palette.zig @@ -116,6 +116,7 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonType, theme: *const Widget.T return tui.render_symbol( &button.plane, + 0, values.label, icon_, color, diff --git a/src/tui/mode/overlay/file_tree_palette.zig b/src/tui/mode/overlay/file_tree_palette.zig index e29575f..13657d5 100644 --- a/src/tui/mode/overlay/file_tree_palette.zig +++ b/src/tui/mode/overlay/file_tree_palette.zig @@ -125,6 +125,7 @@ fn receive_project_manager(palette: *Type, _: tp.pid_ref, m: tp.message) Message } else if (try cbor.match(m.buf, .{ "PRJ", "path_done", tp.any, tp.any, tp.any })) { const pending = palette.value.pending_node; palette.value.pending_node = null; + if (pending) |p| if (p.children) |*children| sort_children(children); palette.entries.clearRetainingCapacity(); if (palette.value.root_node) |root| try build_visible_list(palette, root, 0); palette.longest_hint = max_entry_overhead(palette); @@ -138,6 +139,16 @@ fn receive_project_manager(palette: *Type, _: tp.pid_ref, m: tp.message) Message return true; } +fn sort_children(children: *std.ArrayList(Node)) void { + const less_fn = struct { + fn less_fn(_: void, lhs: Node, rhs: Node) bool { + if (lhs.type_ != rhs.type_) return lhs.type_ == .folder; + return std.mem.lessThan(u8, lhs.name, rhs.name); + } + }.less_fn; + std.mem.sort(Node, children.items, {}, less_fn); +} + fn append_pending_child(palette: *Type, file_name: []const u8, node_type: NodeType, icon_: []const u8, color_: u24) !void { const node = palette.value.pending_node orelse return; if (node.children == null) diff --git a/src/tui/mode/overlay/symbol_palette.zig b/src/tui/mode/overlay/symbol_palette.zig index 2bbfb51..b02dc4b 100644 --- a/src/tui/mode/overlay/symbol_palette.zig +++ b/src/tui/mode/overlay/symbol_palette.zig @@ -45,7 +45,7 @@ pub fn load_entries(palette: *Type) !usize { while (iter.len > 0) { var cbor_item: []const u8 = undefined; if (!try cbor.matchValue(&iter, cbor.extract_cbor(&cbor_item))) return error.BadCompletion; - const label_, _, _, const sel = get_values(cbor_item); + const label_, _, _, const sel, _ = get_values(cbor_item); const label_len_ = tui.egc_chunk_width(label_, 0, 1); (try palette.entries.addOne(palette.allocator)).* = .{ .cbor = cbor_item, .label = label_, .range = sel }; @@ -89,11 +89,12 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonType, theme: *const Widget.T if (!(cbor.matchValue(&iter, cbor.extract_cbor(&item_cbor)) catch false)) return false; if (!(cbor.matchValue(&iter, cbor.extract_cbor(&matches_cbor)) catch false)) return false; - const label_, const container, const kind, _ = get_values(item_cbor); + const label_, const container, const kind, _, const indent = get_values(item_cbor); const icon_: []const u8 = kind.icon(); const color: u24 = 0x0; return tui.render_symbol( &button.plane, + indent, label_, icon_, color, @@ -110,10 +111,11 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonType, theme: *const Widget.T ); } -fn get_values(item_cbor: []const u8) struct { []const u8, []const u8, SymbolKind, ed.Selection } { +fn get_values(item_cbor: []const u8) struct { []const u8, []const u8, SymbolKind, ed.Selection, u8 } { var label_: []const u8 = ""; var container: []const u8 = ""; var kind: u8 = 0; + var depth: u8 = 0; var range: ed.Selection = .{}; _ = cbor.match(item_cbor, .{ cbor.any, // file_path @@ -131,8 +133,9 @@ fn get_values(item_cbor: []const u8) struct { []const u8, []const u8, SymbolKind cbor.any, // selectionRange.end.col cbor.any, // deprecated cbor.any, // detail + cbor.extract(&depth), // number of ancestors }) catch false; - return .{ label_, container, @enumFromInt(kind), range }; + return .{ label_, container, @enumFromInt(kind), range, depth }; } fn find_closest(palette: *Type) ?usize { @@ -140,7 +143,7 @@ fn find_closest(palette: *Type) ?usize { const cursor = editor.get_primary().cursor; var previous: usize = 0; for (palette.entries.items, 0..) |entry, idx| { - _, _, _, const sel = get_values(entry.cbor); + _, _, _, const sel, _ = get_values(entry.cbor); if (cursor.row < sel.begin.row) return previous + 1; previous = idx; } @@ -151,7 +154,7 @@ fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Pos) void { const self = menu.*.opts.ctx; const editor = tui.get_active_editor() orelse return; editor.clear_matches(); - _, _, _, const sel = get_values(button.opts.label); + _, _, _, const sel, _ = get_values(button.opts.label); tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e); switch (self.activate) { .normal => tp.self_pid().send(.{ "cmd", "goto_line_and_column", .{ @@ -169,7 +172,7 @@ fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Pos) void { pub fn updated(palette: *Type, button_: ?*Type.ButtonType) !void { const button = button_ orelse return cancel(palette); - _, _, _, const sel = get_values(button.opts.label); + _, _, _, const sel, _ = get_values(button.opts.label); tp.self_pid().send(.{ "cmd", "focus_on_range", .{ sel.begin.row, sel.begin.col, sel.end.row, sel.end.col } }) catch {}; } diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 951ab6d..7908d4e 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -2423,6 +2423,7 @@ pub fn render_file_vcs_item_cbor(self: *renderer.Plane, file_item_cbor: []const pub fn render_symbol( self: *renderer.Plane, + indent: u8, symbol: []const u8, icon: []const u8, color: u24, @@ -2456,6 +2457,9 @@ pub fn render_symbol( const icon_width = render_file_icon(self, icon, color); self.set_style(style_symbol); + + for (0..indent) |_| _ = self.print(" ", .{}) catch {}; + _ = self.print("{s}", .{symbol}) catch {}; self.set_style(style_detail); @@ -2473,7 +2477,7 @@ pub fn render_symbol( while (len > 0) : (len -= 1) { if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) { const col = egc_chunk_width(symbol[0..@min(symbol.len, index)], 0, 1); - render_match_cell(self, 0, col + 2 + icon_width, theme_) catch break; + render_match_cell(self, 0, col + 2 + icon_width + indent, theme_) catch break; } else break; } return false;