From e053a0dcf4b4c93f1ce1fe6d14a3c04e886d393c Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 23 Oct 2025 23:24:58 +0200 Subject: [PATCH] fix: normalize away './' path prefixes --- build.zig | 1 + src/Project.zig | 15 +++++++++------ src/project_manager.zig | 27 +++++++++++++++++++++++---- test/tests.zig | 1 + test/tests_project_manager.zig | 19 +++++++++++++++++++ 5 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 test/tests_project_manager.zig diff --git a/build.zig b/build.zig index f9228a5..cdc1868 100644 --- a/build.zig +++ b/build.zig @@ -743,6 +743,7 @@ pub fn build_exe( tests.root_module.addImport("color", color_mod); tests.root_module.addImport("tui", tui_mod); tests.root_module.addImport("command", command_mod); + tests.root_module.addImport("project_manager", project_manager_mod); // b.installArtifact(tests); const test_run_cmd = b.addRunArtifact(tests); diff --git a/src/Project.zig b/src/Project.zig index 67231c1..b061f06 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -11,6 +11,7 @@ const git = @import("git"); const file_type_config = @import("file_type_config"); const builtin = @import("builtin"); +const project_manager = @import("project_manager.zig"); const LSP = @import("LSP.zig"); const walk_tree = @import("walk_tree.zig"); @@ -182,12 +183,12 @@ pub fn restore_state_v1(self: *Self, data: []const u8) !void { var files = try cbor.decodeArrayHeader(&iter); tp.trace(tp.channel.debug, .{ "restore_state_v1", "files", files }); while (files > 0) : (files -= 1) { - var path: []const u8 = undefined; + var path_: []const u8 = undefined; var mtime: i128 = undefined; var row: usize = undefined; var col: usize = undefined; if (!try cbor.matchValue(&iter, .{ - tp.extract(&path), + tp.extract(&path_), tp.extract(&mtime), tp.extract(&row), tp.extract(&col), @@ -195,7 +196,8 @@ pub fn restore_state_v1(self: *Self, data: []const u8) !void { try cbor.skipValue(&iter); continue; } - tp.trace(tp.channel.debug, .{ "restore_state_v1", "file", path, mtime, row, col }); + tp.trace(tp.channel.debug, .{ "restore_state_v1", "file", path_, mtime, row, col }); + const path = project_manager.normalize_file_path_dot_prefix(path_); self.longest_file_path = @max(self.longest_file_path, path.len); const stat = std.fs.cwd().statFile(path) catch |e| switch (e) { error.FileNotFound => continue, @@ -249,7 +251,7 @@ pub fn restore_state_v0(self: *Self, data: []const u8) error{ tp.trace(tp.channel.debug, .{"restore_state_v0"}); defer self.sort_files_by_mtime(); var name: []const u8 = undefined; - var path: []const u8 = undefined; + var path_: []const u8 = undefined; var mtime: i128 = undefined; var row: usize = undefined; var col: usize = undefined; @@ -257,7 +259,7 @@ pub fn restore_state_v0(self: *Self, data: []const u8) error{ _ = cbor.matchValue(&iter, tp.extract(&name)) catch {}; tp.trace(tp.channel.debug, .{ "restore_state_v0", "name", name }); while (cbor.matchValue(&iter, .{ - tp.extract(&path), + tp.extract(&path_), tp.extract(&mtime), tp.extract(&row), tp.extract(&col), @@ -265,7 +267,8 @@ pub fn restore_state_v0(self: *Self, data: []const u8) error{ error.TooShort => return, else => return e, }) { - tp.trace(tp.channel.debug, .{ "restore_state_v0", "file", path, mtime, row, col }); + tp.trace(tp.channel.debug, .{ "restore_state_v0", "file", path_, mtime, row, col }); + const path = project_manager.normalize_file_path_dot_prefix(path_); self.longest_file_path = @max(self.longest_file_path, path.len); const stat = std.fs.cwd().statFile(path) catch |e| switch (e) { error.FileNotFound => continue, diff --git a/src/project_manager.zig b/src/project_manager.zig index f404362..f11cb36 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -825,10 +825,29 @@ fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_ pub fn normalize_file_path(file_path: []const u8) []const u8 { const project = tp.env.get().str("project"); - if (project.len == 0) return file_path; - if (project.len >= file_path.len) return file_path; - if (std.mem.eql(u8, project, file_path[0..project.len]) and file_path[project.len] == std.fs.path.sep) - return file_path[project.len + 1 ..]; + const file_path_ = if (project.len == 0) + file_path + else if (project.len >= file_path.len) + file_path + else if (std.mem.eql(u8, project, file_path[0..project.len]) and file_path[project.len] == std.fs.path.sep) + file_path[project.len + 1 ..] + else + file_path; + return normalize_file_path_dot_prefix(file_path_); +} + +pub fn normalize_file_path_dot_prefix(file_path: []const u8) []const u8 { + if (file_path.len == 2 and file_path[0] == '.' and file_path[1] == std.fs.path.sep) + return file_path; + if (file_path.len >= 2 and file_path[0] == '.' and file_path[1] == std.fs.path.sep) { + const file_path_ = file_path[2..]; + return if (file_path_.len > 1 and file_path_[0] == std.fs.path.sep) + normalize_file_path_dot_prefix(file_path_[1..]) + else if (file_path_.len > 1) + normalize_file_path_dot_prefix(file_path_) + else + file_path_; + } return file_path; } diff --git a/test/tests.zig b/test/tests.zig index af75213..a838dc2 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2,6 +2,7 @@ const std = @import("std"); pub const buffer = @import("tests_buffer.zig"); pub const color = @import("tests_color.zig"); pub const helix = @import("tests_helix.zig"); +pub const project_manager = @import("tests_project_manager.zig"); test { std.testing.refAllDecls(@This()); diff --git a/test/tests_project_manager.zig b/test/tests_project_manager.zig new file mode 100644 index 0000000..a5901b1 --- /dev/null +++ b/test/tests_project_manager.zig @@ -0,0 +1,19 @@ +const std = @import("std"); +const pm = @import("project_manager"); + +test "normalize_file_path_dot_prefix" { + try std.testing.expectEqualStrings("example.txt", pm.normalize_file_path_dot_prefix("example.txt")); + try std.testing.expectEqualStrings("/example.txt", pm.normalize_file_path_dot_prefix("/example.txt")); + try std.testing.expectEqualStrings("example.txt", pm.normalize_file_path_dot_prefix("./example.txt")); + try std.testing.expectEqualStrings("example.txt", pm.normalize_file_path_dot_prefix("././example.txt")); + try std.testing.expectEqualStrings("example.txt", pm.normalize_file_path_dot_prefix(".//example.txt")); + try std.testing.expectEqualStrings("example.txt", pm.normalize_file_path_dot_prefix(".//./example.txt")); + try std.testing.expectEqualStrings("example.txt", pm.normalize_file_path_dot_prefix(".//.//example.txt")); + try std.testing.expectEqualStrings("../example.txt", pm.normalize_file_path_dot_prefix("./../example.txt")); + try std.testing.expectEqualStrings("../example.txt", pm.normalize_file_path_dot_prefix(".//../example.txt")); + try std.testing.expectEqualStrings("../example.txt", pm.normalize_file_path_dot_prefix("././../example.txt")); + try std.testing.expectEqualStrings("../example.txt", pm.normalize_file_path_dot_prefix("././/../example.txt")); + try std.testing.expectEqualStrings("../example.txt", pm.normalize_file_path_dot_prefix(".//.//../example.txt")); + try std.testing.expectEqualStrings("./", pm.normalize_file_path_dot_prefix("./")); + try std.testing.expectEqualStrings(".", pm.normalize_file_path_dot_prefix(".")); +}