Language Servers
Zasper uses the Language Server Protocol for what an editor cannot work out on its own: errors as you type, completion, hover, go to definition, find references, rename, quick fixes, symbols and formatting. Files get all of them; notebook cells get most, as In a Notebook says.
Zasper ships no language servers. It starts one you already have, and tells you when you have none.
Step by step: Get Completions and Hovers in a Notebook.
What You Need
| Language | Server | Install |
|---|---|---|
| Python | basedpyright, pyright or pylsp | pip install basedpyright |
| Go | gopls | go install golang.org/x/tools/gopls@latest |
| TypeScript, JavaScript | typescript-language-server | npm install -g typescript-language-server typescript |
| Rust | rust-analyzer | rustup component add rust-analyzer |
| C, C++ | clangd | xcode-select --install on macOS, sudo apt install clangd on Linux |
| R | languageserver | R -e 'install.packages("languageserver")' |
| Julia | LanguageServer.jl | julia -e 'using Pkg; Pkg.add("LanguageServer")' |
| Java | jdtls | brew install jdtls |
| Scala | metals | cs install metals |
| Kotlin | kotlin-language-server | brew install kotlin-language-server |
| C# | csharp-ls | dotnet tool install --global csharp-ls |
| Shell | bash-language-server | npm install -g bash-language-server |
| SQL | sqls | go install github.com/sqls-server/sqls@latest |
| Haskell | haskell-language-server | ghcup install hls |
For Python, the first of the three that is found is used, in that order. basedpyright and pyright are type checkers; pylsp is jedi with a set of plugins, so the type-checking setting below does not apply to it.
Zasper looks on your PATH first, then in the project’s .venv/bin, venv/bin and
node_modules/.bin (.venv\Scripts and venv\Scripts on Windows), then in $GOPATH/bin,
~/go/bin, ~/.cargo/bin, ~/.local/bin, ~/.juliaup/bin and Homebrew. Zasper started from a
desktop launcher or as a service has a thinner PATH than your shell, and without that list gopls
in ~/go/bin would look uninstalled.
In a folder you have not trusted, only basedpyright, pyright and clangd start, because they read code
without running it. They are looked for outside the folder: its .venv, venv and node_modules are
skipped. Every other server waits until you trust the folder. Trusted
Folders has the details.
The Status Bar
The item at the right of the status bar names the server for the file or notebook in front, with a dot for its state: starting, ready, failed or stopped. Its menu restarts the server, shows what it wrote to standard error (Show log), stops it and, for Python, names the interpreter the server reads imports with. When nothing is installed it says No server, and the menu gives the command that installs one.
The counts beside it are the problems in the project. Pressing them opens the Problems panel under the editor.
Problems
An error or a warning is a squiggle under the code, a mark in the gutter, a row in the Problems panel
and a number in the status bar. In a notebook, the row names the cell, such as
analysis.ipynb cell 3, 2:5. Pressing a row opens the file, or the cell, with the cursor on the
problem.
Some things a server reports are not faults: an unused import, a branch that can never run. Those are drawn as faded text, with no squiggle and nothing in the gutter, as other editors draw them. They are listed in the Problems panel but not counted in the status bar.
Settings
Under Settings → Language servers:
- Use language servers: off starts nothing at all.
- Type checking, for basedpyright and pyright:
off(the default),basic,standardorstrict. A change applies at once, without a restart. - A command per language, for when Zasper finds the wrong server or none. Write the command as
you would type it in a shell, such as
ruff server.
A project’s own configuration wins over the Type checking setting: a pyrightconfig.json, or
[tool.pyright] or [tool.basedpyright] in pyproject.toml, is read by the server itself.
Settings → Editor → Inlay hints draws the server’s parameter names and inferred types into the line, without writing them to the file. It is off by default, because the hints move the text of every line they appear on.
Which Type-Checking Mode
off still reports the mistakes worth stopping for: undefined names, imports that cannot be found,
and an import that has type stubs but is not installed. That is the same set Pylance keeps with its
defaults. It does not report type errors, such as an argument of the wrong type, or an attribute a
value does not have. basic and the stricter modes do.
The stricter modes also report correct code that uses libraries without type information. The
checker reads the library’s source, infers a type it cannot narrow, and faults an attribute that is
there when the code runs. scikit-learn is the common example: under standard,
load_iris(as_frame=True).frame is reported although it works. Choose basic or standard for code
you maintain and have types for, and silence a single line with # type: ignore[attr-defined].
In a Notebook
A notebook’s cells are given to the server as one document, so a name defined in one cell is known in the next.
Not every language’s notebook gets one. Python, R, Julia, Go, TypeScript and JavaScript, Rust, shell and SQL notebooks do. C, C++, Java, Scala, Kotlin, C# and Haskell notebooks do not: their kernels take cells that are snippets rather than a source file, which those servers mark as wrong throughout. Their files still get the server.
IPython’s own syntax is hidden from the server. Line magics, ! shell escapes and obj? are
replaced before the server sees them, line for line, so the server never reports them as errors and
every line keeps its number. %%time and its relatives hide only their first line; %%bash and other
cell magics hide the whole cell.
Imports resolve against the kernel’s Python. The server is told which interpreter the notebook’s
kernel runs, so a notebook on a conda or uv environment resolves its imports with no configuration. A
plain .py file has no kernel, so it is read with Settings → Python interpreter. Left on
Automatic, that is the project’s .venv or venv, or the python on your PATH when there is
neither. The same interpreter runs the file with Run Python File in Terminal.
Completion comes from both the server and the kernel. The kernel knows what df is right now,
because it ran the cell that made it; the server knows the source, including cells that have not run.
While you type, suggestions come from the server alone. After a ., on Tab and on Ctrl-Space, both
are asked and merged, and each suggestion is tagged kernel or source. The kernel is kept out of
typing on purpose: it answers one request at a time, so asking it on every keystroke would queue
behind a running cell.
Shift+Tab shows the kernel’s documentation for the name at the cursor, as Jupyter does. Hovering a name asks the server first, and the kernel when the server has nothing and the kernel is idle.
Documentation
Hover shows the server’s documentation for a name, in files and in cells. A suggestion in the completion list shows documentation only when the server sends it with the list: Zasper does not ask the server for one suggestion’s details. basedpyright, pyright and pylsp send documentation only when asked, so a Python suggestion shows only its name and an icon for its kind. To read a Python name’s documentation, hover it, or press Shift+Tab in a cell.
Formatting
Format Document formats a file, and Format Cell and Format Notebook format a notebook; all three are in the command palette. Settings → Editor → Format on save formats a file whenever it is saved; a file with no server is saved as it is. A notebook is formatted a cell at a time, so an edit never reaches across cells, and IPython’s lines are put back untouched.
basedpyright and pyright do not format, and Zasper says so when you ask. To format Python, set a
server that does under Settings → Language servers, such as ruff server.
Not There Yet
- One server per language per window. Two notebooks on different environments share a server, which uses the interpreter of the notebook that attached last.
- Rename, go to definition and signature help from a notebook cell. They work in files.
- Semantic highlighting, call hierarchy and code lens.
When Something Looks Wrong
Import "pandas" could not be resolved: the server is reading a different Python than your kernel. The status bar item’s menu names the interpreter it uses. No interpreter means the notebook has no kernel attached yet.- Squiggles under correct library calls: see Which Type-Checking Mode. A library without type information is the usual cause.
- Faded code with no squiggle: an unused import or a branch that never runs. Nothing is wrong with the line.
- The server keeps failing: Show log in its menu has what it wrote to standard error.
- Nothing at all: check that Use language servers is on, and that a server for the language is installed.