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