@@ -288,11 +288,51 @@ function M.rename_loaded_buffers(old_path, new_path)
288288 end
289289end
290290
291+ local is_windows_drive = function (path )
292+ return (M .is_windows ) and (path :match (" ^%a:\\ $" ) ~= nil )
293+ end
294+
291295--- @param path string path to file or directory
292296--- @return boolean
293297function M .file_exists (path )
294- local _ , error = vim .loop .fs_stat (path )
295- return error == nil
298+ if not (M .is_windows or M .is_wsl ) then
299+ local _ , error = vim .loop .fs_stat (path )
300+ return error == nil
301+ end
302+
303+ -- Windows is case-insensetive, but case-preserving
304+ -- If a file's name is being changed into itself
305+ -- with different casing, windows will falsely
306+ -- report that file is already existing, so a hand-rolled
307+ -- implementation of checking for existance is needed.
308+ -- Same holds for WSL, since it can sometimes
309+ -- access Windows files directly.
310+ -- For more details see (#3117).
311+
312+ if is_windows_drive (path ) then
313+ return vim .fn .isdirectory (path ) == 1
314+ end
315+
316+ local parent = vim .fn .fnamemodify (path , " :h" )
317+ local filename = vim .fn .fnamemodify (path , " :t" )
318+
319+ local handle = vim .loop .fs_scandir (parent )
320+ if not handle then
321+ -- File can not exist if its parent directory does not exist
322+ return false
323+ end
324+
325+ while true do
326+ local name , _ = vim .loop .fs_scandir_next (handle )
327+ if not name then
328+ break
329+ end
330+ if name == filename then
331+ return true
332+ end
333+ end
334+
335+ return false
296336end
297337
298338--- @param path string
0 commit comments