@@ -288,11 +288,51 @@ function M.rename_loaded_buffers(old_path, new_path)
288
288
end
289
289
end
290
290
291
+ local is_windows_drive = function (path )
292
+ return (M .is_windows ) and (path :match (" ^%a:\\ $" ) ~= nil )
293
+ end
294
+
291
295
--- @param path string path to file or directory
292
296
--- @return boolean
293
297
function 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
296
336
end
297
337
298
338
--- @param path string
0 commit comments