These docs may be ahead of the current release (v0.10.1). See the changelog.
stetv0.10.1
Reference

Languages

What stet gives every language, and which get diagnostics and code intelligence.

Language support is layered. Named file associations select syntax, icons, and LSP profiles independently. Every recognized file gets syntax highlighting, a file-type icon, and structural folding; language servers add diagnostics and code intelligence for the profiles the table marks. You can change any layer through your config.

LanguageHighlightingFoldingDiagnosticsCode intelligence
TypeScript & JavaScriptyesindenttypes + lintyes
Rustyesindenttypes + cargoyes
Pythonyesindenttypes + lintyes
Goyesindenttypes + vetyes
JSON & JSONCyesindentschema + linthover, outline
YAMLyesindentschemahover, outline, definition
CSSyesindentlint
GraphQLyesindentlint
Markdownyesby heading
200+ othersyesindentvia config

Syntax highlighting

Shiki bundles grammars for more than 200 languages. stet picks the grammar from the file's name, including exact names such as Dockerfile, and loads it on demand. The diff, full-file view, search results, hover cards, and reference previews share the same highlighter and theme. An unrecognized file type falls back to plain text.

Config dotfiles carry no extension to pick a grammar from, so they resolve by name: .npmrc highlights as INI and .prettierrc as JSONC, so a comment in it reads as a comment. Their extension variants, such as .prettierrc.json and .prettierrc.yaml, already resolve on the extension.

Folding

Press z to fold the block at the caret: code folds by indentation in every language, markdown folds by heading section. See reading files and diffs.

File icons

Recognized file types use restrained colored Nerd Font icons; structural and generic icons remain muted. Recognizable brands keep their identity, while unbranded formats reuse stet's palette. The same named icon and theme color appears in every file-list surface. Matching follows exact filename, glob, extension, and dotfile specificity; --no-icons keeps every layout stable while hiding the glyphs if your terminal font lacks them.

Diagnostics

Servers overlap deliberately: every server that claims a file runs, and their findings merge per file, each tagged with its own source label. A linter that competes with an always-on default activates only where the repo opted into it, the way an editor extension would. Where a language has competing type checkers, stet runs the one the repo opted into and only that one, so the same code is never type-checked twice. Both rules are written as data in the same grammar your config uses: a server's when says which repos it runs in, and a firstOf group picks exactly one of several candidates. A language profile can reuse those defaults or override a candidate with an unconditional { "server": "name" } or its own when.

TypeScript & JavaScript

.ts, .tsx, .js, .jsx, and the .mts/.cts/.mjs/.cjs variants run through three servers: typescript-language-server reports type errors (label tsc), oxlint reports lint findings (label oxc), and Biome joins them (label biome) when the repo has a biome.json or biome.jsonc.

Rust

.rs files get rust-analyzer's own analysis (label rust-analyzer) plus the findings cargo check produces through it (label rustc). stet downloads the server automatically the first time a Rust file needs it.

Python

.py and .pyi files get a type checker plus ruff's lint findings (label Ruff). Python has more than one type checker, so stet runs the one your project runs: ty (label ty) in a repo that opted into it, basedpyright (label basedpyright) everywhere else, never both. So the warnings you see are the ones your CI gates on.

A repo counts as a ty repo when it has a ty.toml or .ty.toml, a [tool.ty] table in pyproject.toml, or ty among its declared dependencies ([project] dependencies, a dependency group, an optional-dependencies extra, [tool.uv] dev-dependencies, or a Poetry dependency table). That rule is not private machinery: it is ty's when gate, and the one-checker rule is a firstOf group, both written in the exact grammar your config uses, so you can reproduce, re-gate, or replace the whole arrangement. Bare names retain their server's default gate; use the object form when you want to force a checker regardless of repository signals:

~/.config/stet/config.jsonc
{
  "languages": {
    "python": { "servers": [{ "server": "basedpyright" }, "ruff"] },
  },
}

ty answers hover, go to definition, references, call hierarchy, and the symbol outline. It has no go-to-implementation yet, so in a ty repo Shift+I says implementations are unsupported for the file type rather than claiming there are none.

Servers download automatically the first time a Python file needs them, basedpyright from npm, ty and ruff as sha256-verified GitHub release binaries.

Go

.go files get gopls, Go's official language server: type and build errors plus the go vet analyzers it runs by default (label compiler, and the analyzer's name for a vet finding). go.mod and go.work get their module diagnostics through the same server.

gopls ships no prebuilt binaries and stet never builds it, so unlike the other built-in servers stet never downloads it. Install it yourself with go install golang.org/x/tools/gopls@latest; stet finds it on your PATH. Without it, Go files show as unavailable rather than falsely clean.

JSON & JSONC

vscode-json-language-server validates well-known files against their schemas: package.json, tsconfig.json, tsconfig.*.json, and jsconfig.json out of the box, plus any file you associate through the schemas config. Biome adds its lint where configured, and the same server answers hover and the symbol outline. The built-in associations point at SchemaStore URLs fetched over the network, so an offline repo falls back to syntax checking.

YAML

yaml-language-server validates .yaml and .yml against schemas it discovers through SchemaStore (GitHub workflows, and so on), and answers go to definition, hover, and the symbol outline. It fetches those schemas over the network, so an offline repo falls back to syntax checking.

CSS & GraphQL

Biome covers both, only in repos with a biome config. Without one these files show as unavailable rather than falsely clean.

Code intelligence

Go to definition, find references, implementations, call hierarchy, hover, and the symbol outline work for the TypeScript & JavaScript family, for Rust, for Python, and for Go, through the same servers that produce their diagnostics (a ty repo has everything but go to implementation, which ty does not yet answer). JSON and YAML get hover and the symbol outline (YAML also go to definition); ruff adds hover on Python. See code intelligence for the keys and flows.

How servers are found

The default discovery strategy checks the repo's own node_modules/.bin, then your PATH. For provisionable built-in servers only, stet then checks for an existing pinned copy in its LSP cache. Python discovery first checks the active $VIRTUAL_ENV/bin, then the repo's .venv/bin, before using the same node_modules/.bin and PATH lookup. Opting a user-defined server into Python discovery changes only those local lookup locations; it does not enable cache use or provisioning. User-defined servers are never provisioned. The cache is $XDG_CACHE_HOME/stet/lsp when XDG_CACHE_HOME is set, and ~/.cache/stet/lsp otherwise. If a provisionable built-in remains missing, stet downloads it into that cache: packages distributed through npm (this path needs npm on your PATH), or a SHA-256-verified GitHub release binary for the servers it doesn't (rust-analyzer, ruff, ty). Set "diagnostics": { "download": false } in your config to turn downloads off, or pass --no-lsp-download (or set STET_NO_LSP_DOWNLOAD) for one run.

gopls is the exception among built-ins: it ships no prebuilt binaries and stet never builds from source, so stet never downloads it. Install it yourself (go install golang.org/x/tools/gopls@latest) and stet finds it on your PATH; without it, Go files show as unavailable rather than falsely clean.

A download that cannot complete (offline, air-gapped, locked-down CI) times out, and its files show as unavailable instead of waiting forever.

Installing dependencies while stet is open

Agents install dependencies constantly, and a language server that never hears about it keeps reporting imports it can no longer resolve. stet tells each server about the on-disk changes it asked to hear about, as they happen, including inside gitignored directories like .venv/.

In Python, that means running uv add fastapi while stet is open clears the Import "fastapi" could not be resolved errors on its own: basedpyright has no file watcher of its own, so this is the only way it learns a package arrived. You do not need to restart, and you do not need to press r.

Servers that watch the filesystem themselves, like rust-analyzer and tsserver, keep doing so and pick up cargo add or npm install through their own watchers.

If a server does get stuck, R restarts the language servers for the repo. r only re-reads what they already believe, which is why it cannot clear a stale cache on its own. R is also the fallback where filesystem watching is unavailable (some sandboxes and containers): installs go unnoticed there, so press R after installing.

Add your own languages

Custom support connects a named server, language profile, and file association. The registries stay separate so several associations can reuse one LSP profile and several profiles can reuse one server:

~/.config/stet/config.jsonc
{
  "diagnostics": {
    "servers": {
      "lua": { "command": ["lua-language-server"] },
    },
  },
  "languages": {
    "lua": { "languageId": "lua", "servers": ["lua"] },
  },
  "files": {
    "lua": { "extensions": ["lua"], "syntax": "lua", "language": "lua" },
  },
}

Server conditions and firstOf groups let the same profile select a different server by repository. stet resolves user-configured commands locally and never runs a command declared by the inspected repository. See configuration for matching, icons, gates, server options, and overriding or disabling built-ins.

On this page