LSP (Language Server) plugin¶
Code intelligence powered by the Language Server Protocol — error squiggles, navigation, hover, completion, signature help, formatting, find references, rename, and go-to-symbol — for any language that has a language server. The editor talks to the server over LSP; the plugin is the client. Which servers exist is data-driven (one small JSON per language), so C++/clangd below is just one of several bundled definitions.
The editor does not bundle or download servers
A language server is a separate program you install once. MTE detects an installed server and points it at your project; it never ships one. See Getting a language server.
Current features¶
| Feature | How |
|---|---|
| Diagnostics | Errors/warnings from the server appear as squiggly underlines (red = error, amber = warning, blue = info, grey = hint), updated as you type. |
| Go to Definition | Put the caret on a symbol and press F12, or use Search ▸ Code Navigation ▸ Go to Definition. Jumps to the definition, opening another file if needed. |
| Find References | Caret on a symbol → Shift+F12 (or Search ▸ Code Navigation ▸ Find References). Results open in a bottom References panel, grouped by file with a source-line preview; double-click to jump. |
| Hover | Rest the pointer over a symbol; a popup shows its type/signature and documentation (rendered from the server's Markdown). |
| Completion | Ctrl+Space requests completions from the server and folds them into the editor's completion popup (ahead of the built-in keyword/buffer-word suggestions), filtered as you type. |
| Signature help | Typing a call's ( or a , shows a call tip with the function signature and the active parameter highlighted; ) dismisses it. |
| Format Document | Alt+Shift+F (⌃⌥F on macOS; or Edit ▸ Format Document) reformats the whole document via the server, in a single undo step. The server's own configuration governs the style (for clangd, a .clang-format file). |
| Find References (listed above) | — |
| Rename Symbol | Caret on a symbol → Shift+F6 (or Edit ▸ Rename Symbol). Enter a new name; every usage is updated. When the rename spans several files you are asked to confirm first — each file is opened, changed, and left modified for you to review and save. |
| Go to Symbol (in file) | Ctrl+Shift+O opens the Command Palette listing every symbol in the current file (functions, classes, methods…); type to filter, Enter jumps. Each row shows the symbol kind and its container. |
| Go to Symbol in Workspace | Ctrl+T searches symbols across the whole project as you type (the server matches the query); Enter opens the file at the symbol. |
The go-to-symbol pickers are the Command Palette's symbol modes — see Command palette.
The commands grey out until a server is running
Go to Definition, Find References, Format Document and Rename Symbol are only enabled when a language server for the current file's language is Ready — and when that server reported it supports the request in its handshake. On a file with no configured server, or while one is starting, restarting or has given up, the menu entries are greyed rather than doing nothing when pressed. The status indicator below tells you which it is. Restart Language Servers is never greyed: it is the way out of a server that has given up.
Cross-file references and rename need the server's index
Find References and Rename only reach other files once the server has indexed
the project. For C/C++ that means a compile_commands.json (see below) and
clangd's background index; without it they operate on the current file only.
Rename is per-file undo for now
A multi-file rename is applied one file at a time, so each file is its own undo step (the confirmation dialog says so). Review the modified tabs before saving.
In the editor's context menu¶
Right-click a symbol in the text and the menu offers, below the editing commands, Go to Definition, Find References, Rename Symbol and Format Document. Each appears only when the same command in the main menu would work right now — a language server for the file is ready and supports it, and, for Rename and Format, the document is not read-only. On a plain-text file, in the margin left of the text, or while the server is starting, none of them appear.
The entries act on the symbol you right-clicked, not on the one under the caret; the caret and the selection stay where they were. Rename proposes the selection as the old name when you right-clicked inside it, and the word under the pointer otherwise. Format Document always formats the whole document. A menu opened from the keyboard acts at the caret.
Where the platform shows keyboard shortcuts in context menus (Windows), each entry shows its shortcut — the one in effect, including your own changes in the Shortcut mapper. On macOS, context menus show no shortcuts.
How your edits reach the server¶
Typing does not send a message per keystroke. Edits are coalesced for a fraction of a second and then sent as one change — and as a single edited range rather than the whole file, whenever the server says it can take one (most can). A large file therefore costs a few dozen bytes per burst of typing instead of a copy of itself.
Anything that asks the server about the text — hover, go-to-definition, find references, rename, format, completion, signature help — sends the pending edit first, so an answer never refers to a version you have already moved past. Saving a file does the same.
Starting a server does not block the editor either: the process is launched in
the background and the status segment shows … until it has finished its
handshake. A server binary that hangs on startup costs you nothing but that
language.
A ceiling on how many problems are drawn
At most 2000 squiggles are drawn per document. Past that the file is unreadable anyway, and each extra marker costs the editor real time — so when a file produces more, the first 2000 are shown and the status bar says Showing the first 2000 of N problems in this file, rather than the count quietly appearing smaller than it is. It is usually a sign that the server is looking at the file wrongly: a missing compilation database, or a generated file that is not meant to compile on its own.
The server status indicator¶
The status bar shows a compact LSP: segment (left of the other plugin
segments) summarising every language server relevant to your open files:
| Glyph | Meaning |
|---|---|
… |
Starting — the server was launched and is handshaking. |
✓ |
Ready — serving requests. |
↻n |
Restarting — the server stopped unexpectedly; automatic restart attempt n is scheduled. |
✗ |
No server installed for this language. Install one (see below), then Restart Language Servers. |
⚠ |
Gave up — the server kept crashing and automatic restarts stopped. Only a manual restart revives it. |
A language may carry a short qualifier after the glyph — the server is running, and something about your project still limits what it can answer. Only one is shown at a time, the most serious first:
| Qualifier | Meaning |
|---|---|
· server not responding |
Several requests in a row went unanswered. The server is alive but stuck; Show Language Server Log, then Restart Language Servers. The qualifier disappears the moment the server answers anything. |
· no compile database |
The C/C++ server is running but can only see the file you have open — see C/C++ projects need compile_commands.json. |
· no Cargo.toml, · no go.mod, · no tsconfig.json, … |
The project has none of the files this server needs to understand it as a project. It still works on the open file; cross-file answers will be incomplete until the file exists. The exact name is the one the server most wants — see What each server needs from your project. |
The segment appears when the first document with a configured language opens, tracks only languages you actually have documents open for — closing a language's last file removes its entry (reopening one brings it back instantly) — and hides entirely when no such document remains.
Search ▸ Code Navigation ▸ Show Language Server Log shows the most recent output of every running server. That is where a server explains itself: a missing toolchain, an unreadable configuration file, a project it refused to load. The window is a normal resizable one — each language is a heading naming the server that is running for it, the text can be selected, Copy puts the whole log on the clipboard, and the size you give the window is remembered.
If a server crashes¶
A language server that exits unexpectedly is restarted automatically with
an increasing delay (1 s, 2 s, 4 s… capped at 30 s). While this happens the
indicator shows ↻n and a status-bar notification reports the first crash.
After a successful restart your open documents are re-announced to the server
and diagnostics come back on their own; stale squiggles from the dead server
are cleared immediately. Editing is never blocked.
After 5 failed attempts in a row the plugin gives up: the indicator shows
✗, a notification points you at the settings, and the language stays dark
until you restart it manually. A server that ran fine for at least a minute
gets a fresh set of attempts when it later crashes.
Manual restart — either of:
- Search ▸ Code Navigation ▸ Restart Language Servers (no default shortcut; also available from the Command Palette), or
- the Re-scan / restart servers button in Preferences ▸ Language Server.
To see why a server is unhappy before restarting it, use Search ▸ Code Navigation ▸ Show Language Server Log.
Both tear every server down, re-run the discovery with the current settings, and reset the failure budget — use them after installing a missing server or fixing a broken one.
Getting a language server¶
Install the server for your language and make sure it is on your PATH (or set
an explicit path in the settings). If none is found,
the feature simply does nothing for that language — the
status indicator shows ✗ and the log records a
one-time hint, but nothing interrupts you.
MTE ships server definitions (not the servers) for many languages, so most just need the server installed — no JSON editing. Bundled definitions cover C/C++, Objective-C, Python, Rust, Go, TypeScript, JavaScript, C#, Java, Kotlin, Swift, HTML, CSS/SCSS/Less, JSON, YAML and shell scripts.
| Language | Server | Where to get it |
|---|---|---|
| C/C++ | clangd | winget install LLVM.LLVM (Windows), the "C++ Clang tools" component in the Visual Studio installer, brew install llvm (macOS), or your distro's clang-tools-extra package. |
| Python | pyright or pylsp | npm i -g pyright (needs Node.js), or pip install python-lsp-server (needs only Python). |
| TypeScript/JS | typescript-language-server | npm i -g typescript-language-server typescript. |
| Rust | rust-analyzer | rustup component add rust-analyzer. |
| Go | gopls | go install golang.org/x/tools/gopls@latest. |
| C# | csharp-ls or OmniSharp | dotnet tool install -g csharp-ls (a single dotnet tool, tried first), or download an OmniSharp release. |
| Java | jdtls (Eclipse JDT Language Server) | brew install jdtls (macOS), or unpack a release and put its bin on your PATH. |
Verify in a terminal, e.g. clangd --version or where clangd / which clangd.
For a language MTE doesn't ship a definition for, add one as described in
Adding a language.
Python: pyright or pylsp¶
Python has two bundled definitions, and OctoEdit runs whichever is actually installed:
- pyright (
pyright-langserver) is tried first. It is an npm package, so it needs Node.js:npm i -g pyright. - pylsp (
python-lsp-server) is used when pyright is not found. It is a pip package and needs only Python:pip install python-lsp-server.
If both are installed, pyright wins. There is one settings row for Python (one
enable flag, one path override), and it applies to whichever server is used —
setting an explicit path to a pylsp executable runs pylsp with its launch
arguments, not pyright's. Show Language Server Log names the server that is
actually running.
Several servers for one language
This is a general mechanism, not a Python special case: a language may have
any number of bundled definitions, ordered by a priority field, and the
first one whose executable is found on your machine is the one that starts.
C# works the same way — csharp-ls first, OmniSharp second — because
the two take different launch arguments and only one of them can be right.
Java: the server's own workspace¶
jdtls refuses to start without a writable workspace directory of its own
(-data). OctoEdit supplies one automatically, per project, under its
plugin data folder:
You never have to create or name it. Deleting the folder is safe — jdtls rebuilds it, at the cost of one re-index.
Setting up on Windows¶
A fresh Windows machine usually has none of clangd, cmake, ninja,
node or python on its PATH. This section says where each one comes from
and how to check it. You do not normally have to edit PATH yourself —
OctoEdit also searches the
ecosystem directories listed below.
| You want | Install | Where it lands |
|---|---|---|
| clangd (C/C++) | winget install LLVM.LLVM |
C:\Program Files\LLVM\bin\clangd.exe |
| cmake and ninja | The "C++ CMake tools for Windows" component in the Visual Studio Installer | …\Microsoft Visual Studio\2022\<edition>\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin and …\CMake\Ninja — not on your PATH, but reachable through the Visual Studio developer environment (see below). A standalone winget install Kitware.CMake / winget install Ninja-build.Ninja puts them on PATH instead. |
| pyright (Python) | winget install OpenJS.NodeJS.LTS, then npm i -g pyright |
%APPDATA%\npm\pyright-langserver.cmd |
| pylsp (Python, no Node needed) | Python from the Microsoft Store or python.org, then pip install python-lsp-server |
%LOCALAPPDATA%\Packages\PythonSoftwareFoundation.Python.3.*_*\LocalCache\local-packages\Python3*\Scripts\pylsp.exe (Store) or %APPDATA%\Python\Python3*\Scripts\pylsp.exe |
Checking what is visible. In a normal Command Prompt or PowerShell:
where searches only your PATH. A tool it cannot find may still be found by
OctoEdit, because the search also covers the ecosystem directories above — the
Python script directories in particular are never on PATH. If a language stays
✗ after installing its server, use Search ▸ Code Navigation ▸ Restart
Language Servers, which re-runs the whole search.
Compile-database generation runs inside the Visual Studio developer
environment. When OctoEdit runs CMake for you (below), it first locates Visual
Studio — through VSINSTALLDIR, else vswhere.exe — and wraps the call in that
installation's VsDevCmd.bat -arch=x64. That is what puts cl, the Windows SDK
and Visual Studio's own cmake/ninja on the path for that one command, so
the editor does not need them on your PATH at all. Without Visual Studio
(a clang-only or MinGW machine) the call is made plainly in the editor's own
environment, and the log says so; you then need cmake and ninja reachable
yourself, and a compiler CMake can identify.
Regenerating on demand. Search ▸ Code Navigation ▸ Generate compile
database… runs the same generation at any time — including when a database
already exists (it is reconfigured in place) and when you previously answered
Don't ask for this project. The entry is always available — when it cannot
generate, it says why on the status bar (no C/C++ document, no project folder, no
CMakeLists.txt in the project root, no cmake, or no generator here that can
export one) instead of being greyed out for a reason you cannot see.
Where the "don't ask" answer is stored. Don't ask for this project is kept
per project root in %APPDATA%\7-bits\OctoEdit.ini, under
plugins/org.mte.lsp/noCompileDbPrompt/<hash> (the project path is hashed
because a path is not a legal settings key). Running Generate compile
database… clears it for that project.
Where OctoEdit looks for a server¶
You should not have to configure anything. After installing a server, opening a folder and a source file is enough. In order, the editor tries:
- an explicit path you set in the settings — authoritative: if it does not resolve, the language stays dark rather than silently falling back to something else;
- your
PATH, for the server's command and each known alias (clangd-20,clangd-19, …); - the install locations in the server's definition for your operating system;
- a built-in list of ecosystem directories.
| Ecosystem directories searched | |
|---|---|
| Windows | the npm global prefix and %APPDATA%\npm, %LOCALAPPDATA%\Microsoft\WinGet\Links, %USERPROFILE%\.cargo\bin, %USERPROFILE%\go\bin and %LOCALAPPDATA%\go\bin, %USERPROFILE%\.dotnet\tools, %ProgramFiles%\LLVM\bin, %ProgramFiles%\nodejs, and Python's per-user script directories: %APPDATA%\Python\Python3*\Scripts, %LOCALAPPDATA%\Packages\PythonSoftwareFoundation.Python.3.*_*\LocalCache\local-packages\Python3*\Scripts (Microsoft Store Python) and %LOCALAPPDATA%\Programs\Python\Python3*\Scripts |
| macOS | /opt/homebrew/bin and /opt/homebrew/opt/llvm/bin (Homebrew's clangd lives in the second — it is never linked into the first), /usr/local/bin, /usr/local/opt/llvm/bin, ~/.cargo/bin, ~/go/bin, ~/.local/bin, ~/.npm-global/bin, ~/.dotnet/tools, ~/Library/Python/3.*/bin |
| Linux | /usr/local/bin, /usr/lib/llvm*/bin, Linuxbrew, and the same ~/.cargo/bin, ~/go/bin, ~/.local/bin, ~/.npm-global/bin, ~/.dotnet/tools |
The * entries are real wildcards: the version directory Python creates
(Python313, 3.13, or the Store's …Python.3.13_qbz5n2kfra8p0) is matched
against what is actually on disk, newest first. That is where
pip install python-lsp-server puts pylsp, and it is why installing it needs
no PATH edit.
macOS: launching from Finder vs a terminal
An application started from Finder or the Dock does not inherit the
PATH from your shell profile — macOS gives it a minimal one
(/usr/bin:/bin:/usr/sbin:/sbin). Homebrew, Cargo, Go and npm would all be
invisible. OctoEdit therefore asks your login shell what its PATH is
and searches that too, and hands the resulting environment to the server it
starts — so a server installed by any of those tools is found whichever way
you launch the editor.
The server is started once per language and shut down with the editor.
C/C++ projects need compile_commands.json¶
clangd only understands your code accurately when it knows the compile flags,
from a compilation database (compile_commands.json). Without one, clangd
falls back to guessing and cross-file features (find references, rename) stay
limited to the open file. clangd is still started either way.
A C/C++ project counts as configured when it has any of:
- a
compile_commands.jsonin the project root or in one of the build directories OctoEdit scans (build/,Build/,out/,out/build/,cmake-build-debug/,cmake-build-release/,.mte-compile-commands/); - a
compile_flags.txtat the project root; - a
.clangdconfiguration file at the project root.
clangd reads the last two by itself, so nothing is reported for them.
When none of those is present — for every C/C++ project, whatever build
system it uses — the status segment shows · no compile database and a
status-bar message says so once. Nothing blocks; no dialog interrupts your
typing.
Letting OctoEdit generate it (CMake projects)¶
If the project root has a CMakeLists.txt and a CMake that would actually
produce a database, you are offered this once per project:
- Generate — runs CMake in the background, configure-only: nothing is
compiled and your own build directories are untouched. The result goes into
<project>/.mte-compile-commands/. On success clangd is restarted against the new database; on failure the last lines of the CMake log are shown, together with the exact command that was tried so you can repeat it yourself. - How to do it manually — shows that same command without running it.
- Don't ask for this project — remembered per project root.
Both the "how to do it manually" window and the failure window show the exact command line in a selectable code box, above a single Copy button whose tooltip says what it takes: the command on its own, or the command together with the tail of the CMake log when there is one. The failure window is resizable and remembers the size you give it — each of these windows remembers its own size, so enlarging the server log does not make a two-line instruction window open equally large.
You are not limited to that one offer: Search ▸ Code Navigation ▸ Generate compile database… starts the same generation whenever you want — after adding files, after changing build options, or after having answered Don't ask for this project (which it clears). An existing database is reconfigured in place. The entry is always available; when it cannot generate, it says why on the status bar.
Your presets come first. If the project has a CMakePresets.json (or a
CMakeUserPresets.json), OctoEdit configures with one of your presets rather
than inventing a command line — cmake --preset <name> -B <project>/.mte-compile-commands
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON. That way the generator, the toolset and the
preset's environment block (a QT6_DIR, an SDK path) are the ones the project
already relies on, and CMake applies them itself. Presets that are hidden, that
a condition excludes on your operating system, or that configure with a
generator which cannot export are skipped; a name containing debug is
preferred. Your own build directory is never touched — the editor always
redirects the output with -B.
On Windows, CMake is run inside a Visual Studio developer environment. An
editor started from Explorer or the Start menu has no cl on its PATH, and
CMake would then quietly settle for whatever other compiler it finds — including
the clang.exe that ships inside a Visual Studio installation, which promptly
fails on MSVC-specific settings. OctoEdit therefore locates Visual Studio with
vswhere and wraps the call in VsDevCmd.bat -arch=x64. If no Visual Studio is
installed (a MinGW or clang-only machine) the call is made plainly and the log
says so.
A previous failed attempt is cleaned up before a retry: its CMakeCache.txt
would otherwise pin the compiler that failed. A directory that already contains
a working compile_commands.json is reconfigured in place, never deleted.
Why the offer sometimes does not appear
CMAKE_EXPORT_COMPILE_COMMANDS is honoured only by the Ninja and
Makefile generators; the Visual Studio generator ignores it and writes
no database. So on Windows, for a project with no usable preset, the offer
appears only when ninja is on your PATH. When a project's only
applicable presets are Visual Studio ones and there is no Ninja either,
OctoEdit says which preset stood in the way instead of offering to run
something that could not work.
Generating it yourself (CMake)¶
CMAKE_EXPORT_COMPILE_COMMANDS is honoured only by the Ninja and Makefile
generators — the Visual Studio generator ignores it (it produces no
compile_commands.json even with the flag set). So generate the database with
Ninja, into a dedicated directory, separate from the one you normally
build in: CMake refuses to reconfigure an existing build directory with a
different generator, so a project you usually build with Visual Studio needs its
own Ninja directory.
This is a configure-only step (no full build needed). From the project root:
.mte-compile-commands/is one of the directories MTE scans, so the resultingcompile_commands.jsonis found automatically. Any scanned directory works too (build/,out/,cmake-build-*, or the project root) — just keep it separate from a directory configured with another generator.- Run it from an environment where the project actually configures: the right
compiler on
PATH(e.g. an x64 Native Tools Command Prompt on Windows) and any variables your project needs (such asQT6_DIRfor a Qt project). Ifcmakecan't find your compiler or SDK, the database ends up empty or missing include paths and clangd won't index well. - Reopen the folder afterwards. The log should no longer say "no compile_commands.json"; give clangd a moment to build its background index before references/rename span files.
Other build systems: Bear (bear -- make), compiledb, Meson, Bazel and others
can all produce a compile_commands.json that the plugin will find.
Which server serves which file¶
The file name decides. Each server definition lists the extensions it
serves, and the first definition claiming a file's extension is the one that
starts. Matching ignores case, and only the last dot counts — Deploy.Prod.SH
is a shell script.
This is deliberately independent of syntax colouring. Turning colouring off, or opening a file bigger than the colouring size limit, no longer costs you the language server as well.
The colorizer id is still a fallback
If no definition claims the extension, OctoEdit falls back to the language id the colouring engine assigned. That keeps a hand-written definition working when it names a language but lists no extensions.
The name the server is told is a second, separate thing: LSP has its own
vocabulary of language ids, and several of them do not match OctoEdit's. A
.sh file is announced as shellscript, a .scss as scss, a .jsx as
javascriptreact, a .mm as objective-cpp. Servers that behave differently
per language — the CSS server, the TypeScript server — need this to be right.
| Extension | Served by | Announced as |
|---|---|---|
.cpp .cxx .cc .h .hpp .hh .hxx .ipp .inl |
clangd | cpp |
.c |
clangd | c |
.m / .mm |
clangd | objective-c / objective-cpp |
.sh .bash .zsh .ksh |
bash-language-server | shellscript |
.css / .scss / .less |
vscode-css-language-server | css / scss / less |
.ts .mts .cts / .tsx |
typescript-language-server | typescript / typescriptreact |
.js .mjs .cjs / .jsx |
typescript-language-server | javascript / javascriptreact |
.json / .jsonc .json5 |
vscode-json-language-server | json / jsonc |
.h is claimed by the C/C++ definition rather than the Objective-C one — both
run clangd, which decides for itself what a header contains.
What each server needs from your project¶
Several servers only work properly when a particular file sits at the project root. When it is missing, the status indicator says so instead of pretending everything is fine:
| Language | Wants | Without it |
|---|---|---|
| Rust | Cargo.toml |
rust-analyzer will not load the crate at all. |
| Go | go.mod |
gopls cannot resolve the module. |
| TypeScript | tsconfig.json (or package.json) |
project-wide answers are unavailable. |
| JavaScript | jsconfig.json (or package.json) |
as above. |
| Python (pyright) | pyproject.toml / pyrightconfig.json / setup.py |
pyright still works; it just uses defaults for import roots and strictness. |
| Java | pom.xml / build.gradle |
jdtls cannot build a classpath. |
| C/C++ | compile_commands.json |
see the section above. |
Adding a language¶
Servers are described by JSON files the plugin loads at startup from
<editor>/lsp-servers/*.json, plus your own folder.
Each entry maps a language to its command plus discovery hints:
{
"language": "cpp",
"languageId": "cpp",
"languageIds": { ".c": "c" },
"command": "clangd",
"aliases": ["clangd-20", "clangd-19"],
"args": ["--background-index", "--log=verbose"],
"extensions": [".cpp", ".h", ".hpp", ".c"],
"projectMarkers": [],
"searchDirs": ["${LLVM}/bin", "${ProgramFiles}/LLVM/bin"],
"searchDirsMac": ["/opt/homebrew/opt/llvm/bin"],
"searchDirsLinux": ["/usr/lib/llvm-20/bin"],
"docsAnchor": "cpp-clangd",
"compileCommandsArg": "--compile-commands-dir=${dir}"
}
languageis OctoEdit's own id for the language — the key the settings page, the status segment and the connection are grouped by.command/aliasesare the executable names tried onPATH.extensionsis the primary file-to-language mapping (see above). Include the dot.argsis one array element per argument.["--background-index", "--log=verbose"]passes two arguments;["--background-index --log=verbose"]passes one argument that happens to contain a space, which every server rejects — and because it then exits immediately, the result looks like a crash loop rather than a typo. OctoEdit warns about that shape in the diagnostics log when it loads your file.languageId(optional) is the id sent to the server indidOpen; it defaults tolanguage. Use it when the LSP specification's name differs from ours (bash→shellscript,objc→objective-c).languageIds(optional) overrideslanguageIdper extension, for one server that serves several spec languages.projectMarkers(optional) are the file names the server wants at the project root. When none of them is there the status segment saysno <first marker>; it never stops the server.searchDirsis the Windows install-location list;searchDirsMacandsearchDirsLinuxare its per-platform counterparts. All three expand${VAR}, and the POSIX lists also expand a leading~. An entry whose variables are not set is skipped rather than turned into a nonsense path.argsmay use two placeholders:${dataDir}, the plugin's writable data folder, and${rootHash}, a short stable id for the current project. That is how jdtls gets-data <dir>. A placeholder that cannot be resolved is left in the argument verbatim rather than expanded to nothing.compileCommandsArg(optional) is appended, with${dir}replaced by the located database directory — that is how clangd is pointed atcompile_commands.json.priority(optional, default0) orders several definitions of the same language: the lowest number is tried first, and the first definition whose executable is found is the one that starts. That is how Python gets pyright (priority: 0) with pylsp (priority: 10) as the fallback.
Your own server definitions¶
The bundled definitions live inside the installation directory, which is not yours to edit (Program Files on Windows, a signed application bundle on macOS). Put your own next to them instead: Preferences ▸ Language Server ▸ Open user server definitions folder creates and opens
Every *.json in it is loaded after the bundled set, and:
- a file whose
languageandcommandmatch a bundled definition replaces it — that is how you change clangd's arguments or add a search directory; - anything else is added — a second server for a language we ship, or a language OctoEdit ships nothing for at all.
A file that is not valid JSON, or that lacks language or command, is skipped
and reported in the diagnostics log rather than silently costing you a language.
Definitions are read once, at startup: restart the editor after editing them (the Re-scan / restart servers button re-runs the search, not the load).
{
"language": "zig",
"command": "zls",
"extensions": [".zig", ".zon"],
"projectMarkers": ["build.zig"],
"searchDirs": ["${LOCALAPPDATA}/zls"],
"searchDirsMac": ["/opt/homebrew/bin", "~/.local/bin"],
"searchDirsLinux": ["~/.local/bin"]
}
Which folder the server is given¶
A language server needs to know where the project is: rust-analyzer will not
load a crate without it, gopls needs the module root, pyright reads
pyrightconfig.json from it, and clangd looks for its database there.
- With a folder open (File ▸ Open Folder), files inside it belong to that folder.
- For a file outside the open folder, or with no folder open at all,
OctoEdit walks up from the file looking for
compile_commands.json,compile_flags.txt,.clangd,CMakeLists.txt,Cargo.toml,go.mod,pyrightconfig.json,pyproject.toml,setup.py,tsconfig.json,package.json,pom.xml,build.gradle, a.sln,.git, and finally an.editorconfigthat declaresroot = true. The file's own directory is the fallback, so a server is never left without a project. - Opening the folder after the file works. Servers that understand workspace-folder changes are told about the new folder; the rest are restarted, and your open documents are re-announced to them automatically. No manual restart is needed.
Troubleshooting¶
| Symptom | What to check |
|---|---|
The LSP: segment never appears |
No server definition claims this file's extension. Check which server serves which file, or add a definition. |
· server not responding |
The server has stopped answering. Show Language Server Log, then Restart Language Servers. |
✗ for a language you installed a server for |
Confirm the executable is really there: where clangd (Windows) / which clangd (macOS, Linux). Then Search ▸ Code Navigation ▸ Restart Language Servers, which re-runs the whole search. |
⚠ |
The server started and then kept crashing. Show Language Server Log usually names the reason; fix it, then Restart Language Servers. |
· no compile database on a C/C++ project |
See above. Find References and Rename will only see the open file until it is fixed. |
| Completion returns nothing on one C++ file | That file is probably missing from compile_commands.json (a header not included by any translation unit, or a file added since the database was generated). Regenerate it. |
| Everything works from a terminal launch but not from Finder | Report it — the login-shell PATH lookup is meant to make the two identical. Show Language Server Log and the Diagnostics log record what was searched. |
| A server needs a project file you don't have | typescript-language-server needs the typescript package, gopls a go.mod, rust-analyzer a Cargo.toml, pyright a pyrightconfig.json/pyproject.toml for project-wide answers. |
Language server settings¶
Preferences ▸ Language Server lists every language a server definition exists for. Per language you can:
- Enable / disable the server (a disabled language never starts one).
- Set an explicit executable path (with Browse…). This override wins
over the
PATH/searchDirssearch above — useful when the server isn't onPATHor you want a specific build. - Add Extra arguments, appended after the definition's own — for example
--log=verbosefor clangd. The line is split the way a command line is, so put quotes around a value that contains spaces.
The path field is a path, not a command line
Typing C:\tools\clangd.exe --log=verbose into Executable path asks
OctoEdit to find a file whose name ends in .exe --log=verbose, which
does not exist — the language then reports no server at all. Put the
executable in the path field and its arguments in Extra arguments. If
it happens anyway, the diagnostics log says so in as many words.
Changes take effect when you press OK / Apply: if any enable flag or path actually changed, the servers are restarted with the new settings automatically (an untouched page never disturbs the running servers). The Re-scan / restart servers button additionally forces a teardown-and- rediscover with the current settings — useful after installing a server the editor previously could not find, and it also resets the crash-restart budget.
Open user server definitions folder creates and opens the folder described in Your own server definitions.
Limitations¶
- Code colouring inside hover popups is not applied (the text is formatted — bold, headings, monospace code — but not syntax-highlighted).
- Accuracy depends on the server: for C/C++, without
compile_commands.jsonclangd falls back to heuristics and results may be incomplete. - OctoEdit never installs or downloads a server. It finds one you installed and starts it; when there is none, the feature stays dark and the status segment says so.
- One server process per language, shared by every open project of that language. A second project is added as a workspace folder when the server supports it; otherwise it is served under the first project's root.
- Kotlin, Swift and Objective-C get code intelligence but no syntax colouring — the editor has no colourer for them yet. The two are independent now, so the server works regardless.
- Server definitions are read once at startup; editing them (yours or the bundled ones) needs an editor restart.