LSP
TLDR: tix lsp provides IDE features over the Language Server Protocol. Run it, point your editor at it.
Running
tix lsp
Communicates over stdin/stdout. Stubs are loaded from tix.toml (auto-discovered from the workspace root) and editor settings.
Features
| Feature | What it does |
|---|---|
| Hover | Shows inferred type and doc comments |
| Completion | Attrset field access (.), function args, expected-type fields in nested values (list elements, nested attrsets), identifiers, inherit targets |
| Signature Help | Parameter names and types when calling functions; highlights the active parameter for curried calls |
| Go to Definition | Jump to let bindings, lambda params, imports, cross-file field definitions (including callPackage-style patterns and transitive barrel re-exports), any path literal (including directory→default.nix resolution), and NixOS/Home Manager config option definitions via @source annotations |
| Go to Type Definition | Jump to the .tix stub file where a type alias is declared, or to the original source of a config field via @source. Works on any name or expression whose inferred type is a named alias (e.g. Derivation, Lib). Only available for stubs loaded from disk. |
| Find References | All uses of a name in the file, plus cross-file usages (x.name in files that import this file) |
| Rename | Refactor bindings and their references; cross-file rename updates x.field select expressions in open files that import the renamed file |
| Inlay Hints | Inline type annotations after binding names |
| Document Symbols | Outline of let bindings and lambda params |
| Workspace Symbols | Search for symbols across all open files |
| Document Links | Clickable import and callPackage paths |
| Semantic Tokens | Syntax highlighting based on name kind |
| Selection Range | Smart expand/shrink selection |
| Document Highlight | Highlight all uses of the name under cursor |
| Code Actions | Quick fixes: add missing field, add type annotation, remove unused binding |
| Formatting | Runs nixfmt |
| Diagnostics | Type errors, missing fields, import resolution errors — each with a stable error code |
Diagnostics
When diagnostics are enabled ("diagnostics": { "enable": true }), tix reports:
- Type errors (ERROR): type mismatches (E001), invalid operators (E003), invalid attrset merges (E004)
- Missing fields (ERROR): accessing a field that doesn’t exist on a closed attrset (E002)
- Unresolved names (WARNING): references to names that can’t be resolved (E005)
- Import errors (WARNING):
import ./missing.nixwhere the target file doesn’t exist (E007), angle bracket imports like<nixpkgs>(E012), or files that haven’t been analyzed (E013) - Inference aborted (WARNING): when type inference is aborted due to memory pressure (E008)
- Unknown type (configurable): bindings whose type is
?(E014) — default severity: hint
Every diagnostic has a stable error code (e.g. E001) that links to documentation. In VS Code, click the code in the Problems panel to open the docs page.
Import errors appear at the import expression so you can see which import failed and why. The CLI (tix) shows the same diagnostics with error codes in Rust-style format: error[E001]: message.
Code Actions
Code actions (quick fixes / refactorings) are offered based on diagnostics and cursor position:
-
Add missing field (quick fix): when you access a field that doesn’t exist on a closed attrset (e.g.
x.barwherex = { foo = 1; }), offers to insertbar = throw "TODO";into the attrset definition. Only works when the attrset definition is visible in the same file. -
Add type annotation (refactor): when the cursor is on a let-binding or rec-attrset field that has an inferred type, offers to insert a
/** type: name :: <type> */doc comment above the binding. Skipped if an annotation already exists. -
Remove unused binding (quick fix): when a let-binding has no references in the file, offers to remove the entire
name = value;line. Names starting with_are excluded (conventional “unused” prefix in Nix).
CLI flags
--log-level
Controls the log level for tix crates (default: info). Useful for debugging background analysis, import resolution, or inference behavior. The RUST_LOG environment variable takes precedence if set.
tix lsp --log-level debug # see per-file background analysis, import details
tix lsp --log-level warn # quieter, only warnings and errors
tix lsp --log-level trace # maximum verbosity
--mem-limit
The LSP sets an RSS (resident memory) limit at startup to prevent runaway inference from consuming all system memory. The default is 80% of system RAM (detected via sysconf; falls back to 3200 MiB if detection fails). A hard RLIMIT_AS backstop is set to 2.5× the RSS limit to accommodate virtual address space overhead.
Override with the --mem-limit flag (value in MiB, sets the RSS limit directly) or the TIX_MEM_LIMIT environment variable:
tix lsp --mem-limit 8192 # 8 GiB RSS limit
tix lsp --mem-limit 0 # no limit
TIX_MEM_LIMIT=8192 tix lsp # 8 GiB (env var, lower priority than --mem-limit)
When process RSS exceeds the limit, inference bails out early — returning partial results instead of crashing. Background analysis of project files is also paused when RSS is high.
Editor setup
VS Code
Install the Nix IDE extension, then configure it to use tix lsp.
Minimal setup — add to your .vscode/settings.json (workspace) or user settings:
{
"nix.enableLanguageServer": true,
"nix.serverPath": ["tix", "lsp"]
}
With extra stubs and initialization options:
{
"nix.enableLanguageServer": true,
"nix.serverPath": ["tix", "lsp"],
"nix.serverSettings": {
"stubs": ["./my-stubs"],
"inlayHints": { "enable": true },
"diagnostics": { "enable": true, "unknownType": "hint" }
}
}
Neovim (nvim-lspconfig)
vim.api.nvim_create_autocmd("FileType", {
pattern = "nix",
callback = function()
vim.lsp.start({
name = "tix",
cmd = { "tix", "lsp" },
})
end,
})
Initialization options
The LSP accepts configuration via initializationOptions. How you pass these depends on your editor — in VS Code they go under nix.serverSettings, in Neovim they go in the init_options field of vim.lsp.start():
{
"stubs": ["/path/to/extra/stubs"],
"inlayHints": { "enable": true },
"diagnostics": { "enable": true }
}