Add file watching in the server as a fallback for editors that don't support it
We ask editors to watch Ruby files for us here, which we use to update the codebase index upon modifications.
However, the registration may fail in two ways: some editors don't support dynamic feature registration and some don't support file watching. In those cases, only the declarations from the original indexing will be available since we will never receive the modification events.
We can use the listen gem to provide a fallback and listen to modifications from the server when file watching is not available.
This is suboptimal from a performance standpoint since having the editor watch files means that it can broadcast the modification events to all parties interested in handling those - as opposed to having each server watch files on their own. However, it's better than never updating the index.
Questions
File watching is available for VS Code. Is it also available for other popular editors such as NeoVim, Emacs, Sublime?
Implementation suggestion
My suggestion is adding an else statement where we register for file watching. In the else branch, we would:
- Require the listen gem
- Use listen to register the callbacks for when files are modified
We should evaluate if there's any way to share the current handler for modifications, with the one using listen.
Neovim supports file watching, but doesn't support dynamic registration:
workspace = {
applyEdit = true,
configuration = true,
didChangeWatchedFiles = {
dynamicRegistration = false,
relativePatternSupport = true
},
semanticTokens = {
refreshSupport = true
},
symbol = {
dynamicRegistration = false,
hierarchicalWorkspaceSymbolSupport = true,
symbolKind = {
valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 }
}
},
workspaceEdit = {
resourceOperations = { "rename", "create", "delete" }
},
workspaceFolders = true
}
Is it possible to register it standard way? Would it make sense to update indexes on didSave in case when file watching is not possible?
My understanding is that there are only two types of registration: static and dynamic. The static registrations are basically the response of the initialize request, which is composed of the server capabilities - indicating what the server supports and needs.
Dynamic registrations happen as notifications sent by the server to the client to enable some functionality.
Based on the specification, there does not seem to be a way to ask for file watching using static registrations.
Would it make sense to update indexes on didSave in case when file watching is not possible?
We do want to index files even before they are saved (see https://github.com/Shopify/ruby-lsp/issues/1908). That certainly helps, but unfortunately it's not enough because files can be modified outside of the editor context, which means we'd fail to track those modifications.
Consider the case of switching git branches. When you switch, several files may be added, changed or deleted because of the state differences. With file watching, the LSP receives notifications about all of those modifications and we can update the index to reflect it. Same story if you run something like RuboCop on the terminal, the only way to know about those is through file watching. But didSave or didChange notifications don't capture this type of thing, which means the index would go stale.
Thanks for clarification.
I've double checked if neovim supports file watching and it indeed does, but it's disabled by default. When I enabled it, it works as expected.
@bjarosze : How did you enable file watching in neovim? Via :set noautoread / vim.opt.autoread = true, or via a plugin?
If it requires changing some configuration, that would be a great contribution for our docs.
I only tested it briefly, I not use it, so can't tell how well it works.
The setting:
capabilities.workspace.didChangeWatchedFiles.dynamicRegistration = true
Whole
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.workspace.didChangeWatchedFiles.dynamicRegistration = true
require("lspconfig").ruby_lsp.setup({
capabilities = capabilities,
init_options = {
formatter = 'auto',
enabledFeatures = {
"codeActions",
"diagnostics",
"documentHighlights",
"documentLink",
"documentSymbols",
"foldingRanges",
"formatting",
"hover",
"inlayHint",
"selectionRanges",
"completion",
"codeLens",
"definition",
"workspaceSymbol",
"signatureHelp"
}
},
})
I use my version of ruby-lsp that updates on save: here is diff https://github.com/bjarosze/ruby-lsp/commit/b64a73e3719da27c542aff948d798376dd6fc6ee#diff-839ccc66503b74e2992f46cfc787178a068f33d77c9c90e4a0da4b91ca06219e
changes in lib/ruby_lsp/server.rb
For future readers, the below line from the above post fixed a duplicate indexing issue that I had for a long time:
capabilities.workspace.didChangeWatchedFiles.dynamicRegistration = true
@jacobdaddario is it worth documenting it in editors?
I was just thinking about that. I'm at work, so I can't do it right now. I can do it later though.
In general, Vim is always a choose your own adventure so there's an expectation of the programmer to do it themselves. That said, when it comes to Lazy, there's that whole set of presets. I think it'd be great for an ideal configuration to be added to the Ruby Lazy Extra. That'd be a fantastic experience rather than folks having to do it themselves.
EDIT: Never mind, I still get multiple results after reindexing occurs.
capabilities.workspace.didChangeWatchedFiles.dynamicRegistration = true
This is the default for Neovim 0.10, as far as I'm aware.
I'm also seeing the duplicate indexing issue. This didn't used to happen for me earlier in 2024 (sadly can't remember what versions I was on).
I have since tried older Neovim version as well as using older ruby-lsp versions; they all had the same issues though, oddly.
I've had better success setting this in Neovim:
:set backupcopy=yes