Skip to content

Commit db178f4

Browse files
authored
fix: improve path escaping for commands on Windows (#1353)
1 parent f3941c5 commit db178f4

File tree

3 files changed

+58
-8
lines changed

3 files changed

+58
-8
lines changed

lua/neo-tree/sources/common/components.lua

+2-2
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ end
115115

116116
M.diagnostics = function(config, node, state)
117117
local diag = state.diagnostics_lookup or {}
118-
local diag_state = diag[node:get_id()]
118+
local diag_state = utils.index_by_path(diag, node:get_id())
119119
if config.hide_when_expanded and node.type == "directory" and node:is_expanded() then
120120
return {}
121121
end
@@ -322,7 +322,7 @@ end
322322

323323
M.modified = function(config, node, state)
324324
local opened_buffers = state.opened_buffers or {}
325-
local buf_info = opened_buffers[node.path]
325+
local buf_info = utils.index_by_path(opened_buffers, node.path)
326326

327327
if buf_info and buf_info.modified then
328328
return {

lua/neo-tree/sources/manager.lua

+4-3
Original file line numberDiff line numberDiff line change
@@ -351,12 +351,13 @@ M.set_cwd = function(state)
351351

352352
local _, cwd = pcall(vim.fn.getcwd, winid, tabnr)
353353
if state.path ~= cwd then
354+
local path = utils.escape_path_for_cmd(state.path)
354355
if winid > 0 then
355-
vim.cmd("lcd " .. state.path)
356+
vim.cmd("lcd " .. path)
356357
elseif tabnr > 0 then
357-
vim.cmd("tcd " .. state.path)
358+
vim.cmd("tcd " .. path)
358359
else
359-
vim.cmd("cd " .. state.path)
360+
vim.cmd("cd " .. path)
360361
end
361362
end
362363
end

lua/neo-tree/utils/init.lua

+52-3
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ M.open_file = function(state, path, open_cmd, bufnr)
664664
end
665665

666666
if M.truthy(path) then
667-
local escaped_path = M.escape_path(path)
667+
local escaped_path = M.escape_path_for_cmd(path)
668668
local bufnr_or_path = bufnr or escaped_path
669669
local events = require("neo-tree.events")
670670
local result = true
@@ -1000,10 +1000,29 @@ M.windowize_path = function(path)
10001000
return path:gsub("/", "\\")
10011001
end
10021002

1003-
M.escape_path = function(path)
1003+
---Escapes a path primarily relying on `vim.fn.fnameescape`. This function should
1004+
---only be used when preparing a path to be used in a vim command, such as `:e`.
1005+
---
1006+
---For Windows systems, this function handles punctuation characters that will
1007+
---be escaped, but may appear at the beginning of a path segment. For example,
1008+
---the path `C:\foo\(bar)\baz.txt` (where foo, (bar), and baz.txt are segments)
1009+
---will remain unchanged when escaped by `fnaemescape` on a Windows system.
1010+
---However, if that string is used to edit a file with `:e`, `:b`, etc., the open
1011+
---parenthesis will be treated as an escaped character and the path separator will
1012+
---be lost.
1013+
---
1014+
---For more details, see issue #889 when this function was introduced, and further
1015+
---discussions in #1264 and #1352.
1016+
---@param path string
1017+
---@return string
1018+
M.escape_path_for_cmd = function(path)
10041019
local escaped_path = vim.fn.fnameescape(path)
10051020
if M.is_windows then
1006-
escaped_path = escaped_path:gsub("\\", "/"):gsub("/ ", " ")
1021+
-- on windows, any punctuation preceeded by a `\` needs to have a second `\`
1022+
-- added to preserve the path separator. this is a naive replacement and
1023+
-- definitely not bullet proof. if we start finding issues with opening files
1024+
-- or changing directories, look here first.
1025+
escaped_path = escaped_path:gsub("\\%p", "\\%1")
10071026
end
10081027
return escaped_path
10091028
end
@@ -1201,4 +1220,34 @@ M.brace_expand = function(s)
12011220
return result
12021221
end
12031222

1223+
---Indexes a table that uses paths as keys. Case-insensitive logic is used when
1224+
---running on Windows.
1225+
---
1226+
---Consideration should be taken before using this function, because it is a
1227+
---bit expensive on Windows. However, this function helps when trying to index
1228+
---with absolute path keys, which can have inconsistent casing on Windows (such
1229+
---as with drive letters).
1230+
---@param tbl table
1231+
---@param key string
1232+
---@return unknown
1233+
M.index_by_path = function(tbl, key)
1234+
local value = tbl[key]
1235+
if value ~= nil then
1236+
return value
1237+
end
1238+
1239+
-- on windows, paths that differ only by case are considered equal
1240+
-- TODO: we should optimize this, see discussion in #1353
1241+
if M.is_windows then
1242+
local key_lower = key:lower()
1243+
for k, v in pairs(tbl) do
1244+
if key_lower == k:lower() then
1245+
return v
1246+
end
1247+
end
1248+
end
1249+
1250+
return value
1251+
end
1252+
12041253
return M

0 commit comments

Comments
 (0)