turbo-editors/turbo-corepublic Fork 0
v0.9.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

talk-to-a-language-server.md · 79 lines · 4.0 KBmarkdown Blame HistoryRaw
🛟 Updated. 28d5985 k33g 20h ago1# How to talk to a language server
2
3This guide shows how to point an editor built on turbo-core at a language server, so that completion, hover and go-to-definition work. It assumes you have an editor and a server that speaks LSP over standard input and output.
4
5## Steps
6
7### 1. Describe the server in your profile
8
9```go
10Server: profile.Server{
11 Command: "rust-analyzer",
12 Args: nil,
13 InstallHint: "rustup component add rust-analyzer",
14 Dirs: []string{cargoBinDir()},
15},
16```
17
18| Field | What it is for |
19| --- | --- |
20| `Command` | the executable's name, looked up on PATH |
21| `Args` | what it is started with — gopls wants `serve`, rust-analyzer wants nothing |
22| `InstallHint` | one command the user can copy, shown on the status bar when the server is missing |
23| `Dirs` | extra directories to search when PATH has nothing |
24
25`Dirs` matters more than it looks. Every language's own installer puts binaries somewhere that is very often not on PATH — `GOPATH/bin`, `~/.cargo/bin` — and "completion silently does nothing" is what the user sees when the editor cannot find a server they believe they installed.
26
27### 2. Say where the project starts
28
29```go
30RootMarkers: []string{"Cargo.toml"},
31```
32
33The server is started in the nearest directory at or above the file that holds one of these. A server given the wrong root loads the wrong code and then answers nothing at all, with no error — which is the most confusing way completion can fail.
34
35### 3. Start it from your command
36
37```go
38editor.StartLanguageServer(ctx, app.ProjectRoot(p, files))
39defer editor.Language().Stop(context.Background())
40```
41
42Start it **after** opening the files, not before. The editor announces the documents that are already open on the first turn of its event loop in which the server is ready, which is what makes this order safe.
43
44## Variants
45
46### A file is created inside the editor
47
48Nothing to do. A window that begins Untitled has no path, so the server hears nothing about it — until the first save, which announces the document rather than merely reporting a write. Completion, hover and diagnostics work in that window from the moment it has a name, with no restart. A Save As under a genuinely new name also closes the old document on the server, so no ghost stays open there.
49
50### The server needs configuration
51
52turbo-core answers `workspace/configuration` with an empty object. If your server insists on settings before it will work, that answer is in `lsp.Client.handleRequest` and is not yet configurable — say so to your users rather than letting completion fail silently.
53
54### There is no server for your language
55
56Leave `Server` at its zero value. `app.Language` is a no-op when nothing is connected, so nothing else in the editor has to check: colouring, editing, themes, the file tree and terminal windows all work exactly the same.
57
58### The command exists but does not run
59
60This is real and it is worth handling. `rustup` installs a *shim* called `rust-analyzer` whether or not the component is there, and the shim fails only when it is run. Finding the file is not the same as being able to use it; if you check for a server in an installer script, run it rather than stat it.
61
62## Verifying it end to end
63
64The test worth writing is the one that types text the server has never seen on disk:
65
661. Write a file that stops short of the thing being completed.
672. Open it, **then** start the server.
683. Take one turn of the event loop with `editor.Tick()`.
694. Type the rest into the buffer with `editor.Handle(...)`.
705. Ask for a completion.
71
72A fixture that already contains the text proves nothing: the server answers from disk for anything it has not been told is open, so such a test passes whether or not the editor said a word.
73
74Expect to have to ask more than once. rust-analyzer answers an empty list until it has finished loading the workspace, and says so with a `$/progress` notification this client does not read.
75
76## See also
77
78- Every field: [profile reference](../reference/profile.md)
79- What the editor exposes for driving it: [app reference](../reference/app.md)