From c334a0e1ee320fb7d070cde255e74726014c409b Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 22 Jan 2026 17:33:10 +0100 Subject: [PATCH] fix: force normalization of all paths on windows closes #442 --- src/project_manager.zig | 21 ++++++++++++++++++--- src/tui/mainview.zig | 24 ++++++++++++++++-------- src/tui/mode/mini/file_browser.zig | 3 ++- test/tests_project_manager.zig | 15 +++++++++++++++ 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/project_manager.zig b/src/project_manager.zig index a1db4d9..834b9c7 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -1001,9 +1001,9 @@ fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_ }.spawn_link(a_, parent_, project_, max_, path_); } -pub fn normalize_file_path(file_path: []const u8) []const u8 { +fn normalize_file_path_project(file_path: []const u8) []const u8 { const project = tp.env.get().str("project"); - const file_path_ = if (project.len == 0) + return if (project.len == 0) file_path else if (project.len >= file_path.len) file_path @@ -1011,7 +1011,6 @@ pub fn normalize_file_path(file_path: []const u8) []const u8 { 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 { @@ -1029,6 +1028,22 @@ pub fn normalize_file_path_dot_prefix(file_path: []const u8) []const u8 { return file_path; } +pub fn normalize_file_path_windows(file_path_: []const u8, file_path_buf: []u8) []const u8 { + var file_path = file_path_buf[0..file_path_.len]; + @memcpy(file_path, file_path_); + for (file_path, 0..) |c, i| if (c == '/') { + file_path[i] = '\\'; + }; + return file_path; +} + +pub fn normalize_file_path(file_path: []const u8, file_path_buf: []u8) []const u8 { + return normalize_file_path_dot_prefix(normalize_file_path_project(if (builtin.os.tag == .windows) + normalize_file_path_windows(file_path, file_path_buf) + else + file_path)); +} + pub fn abbreviate_home(buf: []u8, path: []const u8) []const u8 { const a = std.heap.c_allocator; if (builtin.os.tag == .windows) return path; diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index 1ff50df..c20cc97 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -536,7 +536,8 @@ const cmds = struct { try open_project_cwd(self, .{}); } - const f_ = project_manager.normalize_file_path(file orelse return); + var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; + const f_ = project_manager.normalize_file_path(file orelse return, &file_path_buf); var buf: std.ArrayList(u8) = .empty; defer buf.deinit(self.allocator); const f = project_manager.expand_home(self.allocator, &buf, f_); @@ -1053,7 +1054,8 @@ const cmds = struct { tp.extract(&sel.end.row), tp.extract(&sel.end.col), })) return error.InvalidAddDiagnosticArgument; - file_path = project_manager.normalize_file_path(file_path); + var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; + file_path = project_manager.normalize_file_path(file_path, &file_path_buf); if (self.get_editor_for_file(file_path)) |editor| { try editor.add_diagnostic(file_path, source, code, message, severity, sel); if (!tui.config().show_local_diagnostics_in_panel) @@ -1085,7 +1087,8 @@ const cmds = struct { tp.extract(&is_incomplete), tp.more, })) return error.InvalidAddDiagnosticArgument; - file_path = project_manager.normalize_file_path(file_path); + var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; + file_path = project_manager.normalize_file_path(file_path, &file_path_buf); if (self.get_editor_for_file(file_path)) |editor| try editor.add_completion(row, col, is_incomplete, ctx.args); } @@ -1122,7 +1125,8 @@ const cmds = struct { if (!try ctx.args.match(.{ tp.extract(&file_path), })) return error.InvalidAddDiagnosticArgument; - file_path = project_manager.normalize_file_path(file_path); + var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; + file_path = project_manager.normalize_file_path(file_path, &file_path_buf); if (self.get_active_editor()) |editor| if (std.mem.eql(u8, file_path, editor.file_path orelse "")) { self.symbols_complete = true; try tui.open_overlay(@import("mode/overlay/symbol_palette.zig").Type); @@ -1147,7 +1151,8 @@ const cmds = struct { tp.extract(&kind), tp.more, })) return error.InvalidAddDiagnosticArgument; - file_path = project_manager.normalize_file_path(file_path); + var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; + file_path = project_manager.normalize_file_path(file_path, &file_path_buf); if (self.get_active_editor()) |editor| if (std.mem.eql(u8, file_path, editor.file_path orelse "")) { self.symbols_complete = false; try self.symbols.appendSlice(self.allocator, ctx.args.buf); @@ -1183,7 +1188,8 @@ const cmds = struct { tp.extract(&row), tp.extract(&col), })) return error.InvalidAddDiagnosticArgument; - file_path = project_manager.normalize_file_path(file_path); + var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; + file_path = project_manager.normalize_file_path(file_path, &file_path_buf); if (self.get_active_editor()) |editor| if (std.mem.eql(u8, file_path, editor.file_path orelse "")) { if (editor.completions.items.len > 0) { switch (tui.config().completion_style) { @@ -1220,7 +1226,8 @@ const cmds = struct { var line_text: []const u8 = undefined; if (!try cbor.matchString(&iter, &line_text)) return error.MissingArgument; - file_path = project_manager.normalize_file_path(file_path); + var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; + file_path = project_manager.normalize_file_path(file_path, &file_path_buf); const editor = self.get_editor_for_file(file_path) orelse continue; const primary_cursor = editor.get_primary().cursor; @@ -1250,7 +1257,8 @@ const cmds = struct { 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.InvalidClearDiagnosticsArgument; - file_path = project_manager.normalize_file_path(file_path); + var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; + file_path = project_manager.normalize_file_path(file_path, &file_path_buf); if (self.get_editor_for_file(file_path)) |editor| editor.clear_diagnostics(); diff --git a/src/tui/mode/mini/file_browser.zig b/src/tui/mode/mini/file_browser.zig index 82e29d9..e66b4d2 100644 --- a/src/tui/mode/mini/file_browser.zig +++ b/src/tui/mode/mini/file_browser.zig @@ -217,7 +217,8 @@ pub fn Create(options: type) type { fn construct_path(self: *Self, path_: []const u8, entry_name: []const u8, entry_type: EntryType, entry_no: usize) error{OutOfMemory}!void { self.matched_entry = entry_no; - const path = project_manager.normalize_file_path(path_); + var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; + const path = project_manager.normalize_file_path(path_, &file_path_buf); try self.file_path.appendSlice(self.allocator, path); if (path.len > 0 and path[path.len - 1] != std.fs.path.sep) try self.file_path.append(self.allocator, std.fs.path.sep); diff --git a/test/tests_project_manager.zig b/test/tests_project_manager.zig index ded91e0..559a0ca 100644 --- a/test/tests_project_manager.zig +++ b/test/tests_project_manager.zig @@ -19,6 +19,21 @@ test "normalize_file_path_dot_prefix" { try std.testing.expectEqualStrings(P1("."), pm.normalize_file_path_dot_prefix(P2("."))); } +test "normalize_file_path_windows" { + var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; + try std.testing.expectEqualStrings("example.txt", pm.normalize_file_path_windows("example.txt", &file_path_buf)); + try std.testing.expectEqualStrings("\\example.txt", pm.normalize_file_path_windows("/example.txt", &file_path_buf)); + try std.testing.expectEqualStrings(".\\example.txt", pm.normalize_file_path_windows("./example.txt", &file_path_buf)); + try std.testing.expectEqualStrings(".\\.\\example.txt", pm.normalize_file_path_windows("././example.txt", &file_path_buf)); + try std.testing.expectEqualStrings(".\\\\example.txt", pm.normalize_file_path_windows(".//example.txt", &file_path_buf)); + try std.testing.expectEqualStrings(".\\\\.\\example.txt", pm.normalize_file_path_windows(".//./example.txt", &file_path_buf)); + try std.testing.expectEqualStrings(".\\", pm.normalize_file_path_windows("./", &file_path_buf)); + try std.testing.expectEqualStrings(".", pm.normalize_file_path_windows(".", &file_path_buf)); + try std.testing.expectEqualStrings("C:\\User\\x\\example.txt", pm.normalize_file_path_windows("C:\\User\\x/example.txt", &file_path_buf)); + try std.testing.expectEqualStrings("C:\\User\\x\\path\\example.txt", pm.normalize_file_path_windows("C:\\User\\x/path/example.txt", &file_path_buf)); + try std.testing.expectEqualStrings("C:\\User\\x\\path\\example.txt", pm.normalize_file_path_windows("C:/User/x/path/example.txt", &file_path_buf)); +} + fn P1(file_path: []const u8) []const u8 { const local = struct { var fixed_file_path: [256]u8 = undefined;