feat: return file type and icon along with file names from the project manager

This commit is contained in:
CJ van den Berg 2025-08-09 18:06:49 +02:00
parent 680c6f770e
commit ca33259ba4
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9

View file

@ -8,6 +8,7 @@ const Buffer = @import("Buffer");
const fuzzig = @import("fuzzig"); const fuzzig = @import("fuzzig");
const tracy = @import("tracy"); const tracy = @import("tracy");
const git = @import("git"); const git = @import("git");
const file_type_config = @import("file_type_config");
const builtin = @import("builtin"); const builtin = @import("builtin");
const LSP = @import("LSP.zig"); const LSP = @import("LSP.zig");
@ -52,6 +53,9 @@ pub const LspOrClientError = (LspError || ClientError);
const File = struct { const File = struct {
path: []const u8, path: []const u8,
type: []const u8,
icon: []const u8,
color: u24,
mtime: i128, mtime: i128,
pos: FilePos = .{}, pos: FilePos = .{},
visited: bool = false, visited: bool = false,
@ -341,7 +345,7 @@ pub fn request_n_most_recent_file(self: *Self, from: tp.pid_ref, n: usize) Clien
pub fn request_recent_files(self: *Self, from: tp.pid_ref, max: usize) ClientError!void { pub fn request_recent_files(self: *Self, from: tp.pid_ref, max: usize) ClientError!void {
defer from.send(.{ "PRJ", "recent_done", self.longest_file_path, "" }) catch {}; defer from.send(.{ "PRJ", "recent_done", self.longest_file_path, "" }) catch {};
for (self.files.items, 0..) |file, i| { for (self.files.items, 0..) |file, i| {
from.send(.{ "PRJ", "recent", self.longest_file_path, file.path }) catch return error.ClientFailed; from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, file.type, file.icon, file.color }) catch return error.ClientFailed;
if (i >= max) return; if (i >= max) return;
} }
} }
@ -356,7 +360,7 @@ fn simple_query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: [
defer self.allocator.free(matches); defer self.allocator.free(matches);
var n: usize = 0; var n: usize = 0;
while (n < query.len) : (n += 1) matches[n] = idx + n; while (n < query.len) : (n += 1) matches[n] = idx + n;
from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, matches }) catch return error.ClientFailed; from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, file.type, file.icon, file.color, matches }) catch return error.ClientFailed;
i += 1; i += 1;
if (i >= max) return i; if (i >= max) return i;
} }
@ -379,6 +383,9 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
const Match = struct { const Match = struct {
path: []const u8, path: []const u8,
type: []const u8,
icon: []const u8,
color: u24,
score: i32, score: i32,
matches: []const usize, matches: []const usize,
}; };
@ -389,6 +396,9 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
if (match.score) |score| { if (match.score) |score| {
(try matches.addOne()).* = .{ (try matches.addOne()).* = .{
.path = file.path, .path = file.path,
.type = file.type,
.icon = file.icon,
.color = file.color,
.score = score, .score = score,
.matches = try self.allocator.dupe(usize, match.matches), .matches = try self.allocator.dupe(usize, match.matches),
}; };
@ -404,13 +414,24 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
std.mem.sort(Match, matches.items, {}, less_fn); std.mem.sort(Match, matches.items, {}, less_fn);
for (matches.items[0..@min(max, matches.items.len)]) |match| for (matches.items[0..@min(max, matches.items.len)]) |match|
from.send(.{ "PRJ", "recent", self.longest_file_path, match.path, match.matches }) catch return error.ClientFailed; from.send(.{ "PRJ", "recent", self.longest_file_path, match.path, match.type, match.icon, match.color, match.matches }) catch return error.ClientFailed;
return @min(max, matches.items.len); return @min(max, matches.items.len);
} }
pub fn walk_tree_entry(self: *Self, file_path: []const u8, mtime: i128) OutOfMemoryError!void { pub fn walk_tree_entry(
self: *Self,
file_path: []const u8,
mtime: i128,
) OutOfMemoryError!void {
const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(file_path);
self.longest_file_path = @max(self.longest_file_path, file_path.len); self.longest_file_path = @max(self.longest_file_path, file_path.len);
(try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime }; (try self.pending.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, file_path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = mtime,
};
} }
pub fn walk_tree_done(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void { pub fn walk_tree_done(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void {
@ -420,6 +441,25 @@ pub fn walk_tree_done(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void {
return self.loaded(parent); return self.loaded(parent);
} }
fn guess_file_type(file_path: []const u8) struct { []const u8, []const u8, u24 } {
var buf: [1024]u8 = undefined;
const content: []const u8 = blk: {
const file = std.fs.cwd().openFile(file_path, .{ .mode = .read_only }) catch break :blk &.{};
defer file.close();
const size = file.read(&buf) catch break :blk &.{};
break :blk buf[0..size];
};
return if (file_type_config.guess_file_type(file_path, content)) |ft| .{
ft.name,
ft.icon orelse file_type_config.default.icon,
ft.color orelse file_type_config.default.color,
} else .{
file_type_config.default.name,
file_type_config.default.icon,
file_type_config.default.color,
};
}
fn merge_pending_files(self: *Self) OutOfMemoryError!void { fn merge_pending_files(self: *Self) OutOfMemoryError!void {
defer self.sort_files_by_mtime(); defer self.sort_files_by_mtime();
const existing = try self.files.toOwnedSlice(self.allocator); const existing = try self.files.toOwnedSlice(self.allocator);
@ -469,9 +509,13 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi
} }
return; return;
} }
const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(file_path);
if (row != 0) { if (row != 0) {
(try self.files.addOne(self.allocator)).* = .{ (try self.files.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, file_path), .path = try self.allocator.dupe(u8, file_path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = mtime, .mtime = mtime,
.pos = .{ .row = row, .col = col }, .pos = .{ .row = row, .col = col },
.visited = true, .visited = true,
@ -479,6 +523,9 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi
} else { } else {
(try self.files.addOne(self.allocator)).* = .{ (try self.files.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, file_path), .path = try self.allocator.dupe(u8, file_path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = mtime, .mtime = mtime,
}; };
} }
@ -1941,7 +1988,14 @@ pub fn process_git(self: *Self, parent: tp.pid_ref, m: tp.message) (OutOfMemoryE
} else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.extract(&path) })) { } else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.extract(&path) })) {
self.longest_file_path = @max(self.longest_file_path, path.len); self.longest_file_path = @max(self.longest_file_path, path.len);
const stat = std.fs.cwd().statFile(path) catch return; const stat = std.fs.cwd().statFile(path) catch return;
(try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, path), .mtime = stat.mtime }; const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(path);
(try self.pending.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = stat.mtime,
};
} else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.null_ })) { } else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.null_ })) {
self.state.workspace_files = .done; self.state.workspace_files = .done;
try self.loaded(parent); try self.loaded(parent);