Skip to content

feat(mini.files): make file system actions LSP aware#2340

Draft
TheLeoP wants to merge 1 commit intonvim-mini:mainfrom
TheLeoP:lsp
Draft

feat(mini.files): make file system actions LSP aware#2340
TheLeoP wants to merge 1 commit intonvim-mini:mainfrom
TheLeoP:lsp

Conversation

@TheLeoP
Copy link
Copy Markdown
Member

@TheLeoP TheLeoP commented Apr 2, 2026

Addresses #2215

This is a proof of concept to test the idea of including this kind of LSP support inside of mini.files. It may not be merged at all or it may help to add new autocmds to allow users/other plugins to implement this themselves.

A few things to take into account

  • the control flow of H.fs_do.delete had to be changed to allow checking success a single time before triggering didDeleteFile notifications (actually, it seems like I broke something)
  • a big part of the code simply filters the files before sending the requests/notifications to a language server as specified by the LSP spec (and, honestly, that logic should probably be extracted into a single function)
  • for now, the timeout for client:request_sync is harcoded
  • the code should make some considerations to support older Neovim versions (vim.glob.to_lpeg has a bug prior to 0.11 that requires manually sorting items inside brackets as a workaround, client:request_sync/client:notify are not methods prior to 0.11, etc)
  • I though about checking the user permissions on a given directory to further test if the file operations will succeed, but the file permissions semantics for these kind of operations seem to be different in Linux and Windows (and, as far as I can tell, libuv does not offer an abstraction on top of it. Maybe fs_stat related function would work on Windows with the same semantics, I haven't tested it yet)

@TheLeoP TheLeoP marked this pull request as draft April 2, 2026 21:23
@TheLeoP
Copy link
Copy Markdown
Member Author

TheLeoP commented Apr 2, 2026

To test the PR using lua_ls you can have a project with the following structure

test_project/
|- main.lua
|- something.lua
|- .git

.git is an empty file, I simply use it so `lua_ls` has a `root_marker`

with the following content

-- main.lua
local something = require("something").something

something()

-- something.lua
local M = {}

M.something = function()
	print("something")
end

return M

you can then nvim main.lua, : lua MiniFiles.open()m rename something.lua to something_else.lua and accept the changes with =. Neovim should prompt you for confirmation before updating the import statement on main.lua to local something = require("something_else").something

@TheLeoP
Copy link
Copy Markdown
Member Author

TheLeoP commented Apr 2, 2026

The tests errors seem to go away if I comment out

H.lsp_will_fs_do('delete', lsp_params)

and

if success then H.lsp_did_fs_do('delete', lsp_params) end

But I'm not sure why.

In particular, the problematic lines seem to be

local clients = vim.lsp.get_clients({ method = full_method })

and

local clients = vim.lsp.get_clients({ method = full_method })

changing them to

local clients = {}

seems to also make the test pass.

Are these tests specially time sensitive? I can't think of another reason of why they would be failing

Copy link
Copy Markdown
Member

@echasnovski echasnovski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!

There are at least two good things: the LSP complexity seems to be understood and managed (the worst part for me :) ) and there is a room for making it more concise.

uri = vim.uri_from_fname(path),
} },
}
H.lsp_will_fs_do('create', lsp_params)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that willCreateFiles is for files only, right? This will also send a request for a directory.

for _, filter in ipairs(filters) do
local ignore_case = filter.pattern.options and filter.pattern.options.ignoreCase
local glob = filter.pattern.glob
if ignore_case then glob = glob:lower() end
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string.lower() works only for Latin characters. Using vim.fn.tolower() is more robust.

rename = 'willRename',
}

H.lsp_will_fs_do = function(action, params)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have so many questions (mostly due to LSP complexity, not only due to the PR itself) ...

  1. It seems that it is possible to make requests/notifications in bulk for every three operation kinds. I'd say it is worth taking advantage of that: it is less talking back-and-forth between the client and the server, less computation based on the client capabilities, etc.
  2. There is a lot of code duplication between two functions. Either moving that to a separate function is better. Or even probably just using a single function that has something like if method:sub(1, 3) == 'did' then ... else ... end.
  3. I think H.lsp_{will,did}_fs_do_method is a bit too much. Maybe supplying a method directly when calling a function is more explicit.

So all in all, my first instinct is that adding all LSP related stuff (done in bulk) in the H.fs_actions_apply() (so that it is "willXxx requests", "action", "didXxx notifications") should be less complex and more concise. Yes, it will probably require an duplicating check about if the actions is predicted to be successful, but can be worked around later. This way it can also reuse computed parameters.

local full_method = 'workspace/' .. method .. 'Files'
local clients = vim.lsp.get_clients({ method = full_method })

-- TODO(TheLeoP): configurable timeout
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is huge deal. The plan is to add something like boolean config.options.lsp, so that it can be disabled if there is a slow server. But not sure, maybe a table config.lsp = { enable = true, ... } is better. Just don't like another table-like option.

@echasnovski
Copy link
Copy Markdown
Member

Are these tests specially time sensitive? I can't think of another reason of why they would be failing

I don't think so. Probably, some logic has been broken. Didn't read too much into the code.

  • the code should make some considerations to support older Neovim versions (vim.glob.to_lpeg has a bug prior to 0.11 that requires manually sorting items inside brackets as a workaround, client:request_sync/client:notify are not methods prior to 0.11, etc)

Restricting this functionality to only Neovim<0.11 is fine.

  • I though about checking the user permissions on a given directory to further test if the file operations will succeed, but the file permissions semantics for these kind of operations seem to be different in Linux and Windows (and, as far as I can tell, libuv does not offer an abstraction on top of it. Maybe fs_stat related function would work on Windows with the same semantics, I haven't tested it yet)

I am okay with these checks for willXxx requests be on the "best effort" basis. It is the fault of the LSP specifications to have designed such a questionable (i.e. I don't understand why it is like this) approach :)


I'll also take a look into how to better handle the "close on lost focus" in 'mini.files'. It will probably have to be "don't close if inside non-normal buffer in a floating window" type of exception.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants