From be7440b5c365303865ebf7a3c437039e96c170c1 Mon Sep 17 00:00:00 2001 From: xs Date: Tue, 3 Nov 2026 19:49:53 +0800 Subject: vimrc: rust-analyzer gd/completion on standalone .rs files Root rust-analyzer at the owning crate only if the file is one of its targets; otherwise list the file in detachedFiles (init options for the first file, workspace config for later ones) so std resolves. Co-Authored-By: Claude Opus 5.5 --- core/vimrc | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/core/vimrc b/core/vimrc index f62115c..a2b40c4 100644 --- a/core/vimrc +++ b/core/vimrc @@ -153,20 +153,68 @@ endif let g:gitgutter_sign_column_always = 0 +" rust-analyzer root: nearest Cargo.toml dir only if this file is a target of that crate +" (src/ examples/ tests/ benches/ build.rs, or its path appears in the manifest); else the +" file's own dir, so rust-analyzer loads it standalone (std resolves) instead of detached-in-a-foreign-project +" RustCrate(): crate dir if this buffer is a target of its nearest Cargo.toml, else '' +function! RustCrate() abort + let l:p = expand('%:p') + let l:d = lsp#utils#find_nearest_parent_file_directory(l:p, 'Cargo.toml') + if !empty(l:d) + let l:d = substitute(fnamemodify(l:d, ':p'), '/$', '', '') + let l:rel = stridx(l:p, l:d . '/') == 0 ? l:p[len(l:d) + 1:] : '' + let l:target = l:rel =~# '^\(src\|examples\|tests\|benches\)/' || l:rel ==# 'build.rs' + if !l:target && !empty(l:rel) + let l:target = stridx(join(readfile(l:d . '/Cargo.toml'), "\n"), '"' . l:rel . '"') >= 0 + endif + if l:target + return l:d + endif + endif + return '' +endfunction +function! RustRoot() abort + let l:c = RustCrate() + return empty(l:c) ? expand('%:p:h') : l:c +endfunction +" Standalone .rs files (no owning crate) must be listed in rust-analyzer's detachedFiles, +" else it loads nothing for them (no gd/completion). The list is shared by reference with +" the init options below (covers the first file) and pushed as config for later files. +let g:ra_detached = [] +function! RustDetach() abort + if !exists('g:lsp_loaded') + return + endif + let l:p = expand('%:p') + if empty(l:p) || !empty(RustCrate()) || index(g:ra_detached, l:p) >= 0 + return + endif + call add(g:ra_detached, l:p) + if lsp#get_server_status('rust-analyzer') ==# 'running' + call lsp#update_workspace_config('rust-analyzer', {'rust-analyzer': {'detachedFiles': copy(g:ra_detached)}}) + endif +endfunction +augroup rust_detached + au! + au BufEnter *.rs call RustDetach() +augroup END + " rust-analyzer via the rustup proxy shim (portable across toolchains/machines) if executable(expand('~/.cargo/bin/rust-analyzer')) au User lsp_setup call lsp#register_server({ \ 'name': 'rust-analyzer', \ 'cmd': {server_info -> [expand('~/.cargo/bin/rust-analyzer')]}, \ 'allowlist': ['rust'], - \ 'root_uri': {server_info -> lsp#utils#path_to_uri(lsp#utils#find_nearest_parent_file_directory(lsp#utils#get_buffer_path(), 'Cargo.toml'))}, + \ 'root_uri': {server_info -> lsp#utils#path_to_uri(RustRoot())}, \ 'initialization_options': { \ 'completion': { \ 'autoimport': { 'enable': v:true }, \ 'postfix': { 'enable': v:true }, \ }, \ 'checkOnSave': v:true, + \ 'detachedFiles': g:ra_detached, \ }, + \ 'workspace_config': {'rust-analyzer': {'detachedFiles': g:ra_detached}}, \ }) endif if executable(expand('~/.local/bin/ruff')) -- cgit