Contents

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:

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

When Something Looks Wrong