Neovim language services, completion and formatting

On this page

Highlighting, completion, definition lookup and test execution come from different components. Identify the responsible component before reinstalling tools.

What opening a file triggers

ComponentResponsibilityLimit
File type detectionIdentify Rust, Lua and other typesDoes not install tools
Treesitter parserSyntax structure for highlights and selectionsNot project type checking
LSP client/serverDefinitions, references, diagnostics, renameDoes not run all tests
Completion interfacePresent LSP, snippet and other candidatesDoes not supply all semantics
FormatterRearrange text by rulesDoes not prove behaviour
DAP client/adapterBreakpoints, variables and execution controlStill needs SDKs, targets and symbols

Language extras connect some of these layers. Optional plugin settings apply only when the plugin is installed; runtimes and project dependencies remain separate. LazyVim extras

Start with one language

For Rust, make cargo check work outside the editor before enabling Rust through :LazyExtras. Resolve toolchain, dependency and compiler failures there first.

Open a Rust file in the Cargo project:

:set filetype?
:pwd
:lua print(vim.inspect(vim.lsp.get_clients({ bufnr = 0 })))

Expect rust and an attached language client. :pwd is the editor working directory, not necessarily the LSP root; inspect the client's configuration as well. Neovim LSP

A workspace server may use an outer root. A file copied outside its project can lack module and dependency context.

Verify individual language features

Try definitions, references, hover and rename on a known local function. Direct calls help separate API behaviour from key mappings:

:lua vim.lsp.buf.definition()
:lua vim.lsp.buf.references()
:lua vim.lsp.buf.hover()

No result can mean an unsupported target, unresolved dependency or incomplete indexing rather than no server. Establish a known working local target first.

After rename, inspect affected buffers, save intended changes and review Git diff. A successful request is not the final correctness check.

Choose who formats

LazyVim commonly uses Conform. :ConformInfo reports configured formatters, availability and logs. To make changes explicit while learning, put this in lua/config/options.lua:

vim.g.autoformat = false

Trigger formatting manually and review differences. Respect project files such as .editorconfig, .clang-format and rustfmt.toml.

This override in lua/plugins/formatting.lua selects StyLua for Lua:

return {
  {
    "stevearc/conform.nvim",
    opts = function(_, opts)
      opts.formatters_by_ft = opts.formatters_by_ft or {}
      opts.formatters_by_ft.lua = { "stylua" }
    end,
  },
}

It selects a program but does not install it. Avoid adding another save autocmd that also invokes LSP formatting, which can duplicate work or apply conflicting rules.

Diagnostics, tests and debugging

Static diagnostics can find syntax and type problems while missing incorrect business boundaries. The project exercise demonstrates code that compiles but fails a requirement.

Debugging additionally needs a launchable target, working directory, arguments and adapter. Make the same target run from the command line first. Rust, Go, C# and Java use different backends; nvim-dap alone does not provide all of them.

A diagnostic sequence

  1. Check :set filetype?.
  2. Verify project context, configuration and dependencies.
  3. Inspect executable resolution, for example :lua print(vim.fn.exepath("rust-analyzer")); plugins may also specify their own path.
  4. Check client attachment and startup logs.
  5. Verify the specific server capability and whether failure is file-specific.
  6. Inspect formatter and debugger tools independently rather than repeatedly reinstalling LSP.

Change one condition at a time. Reproduce the result with logs and a small project before applying the fix to the normal configuration.