Skip to content

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:

LSP: cpp ✓ · no compile database  python ↻2  rust ✗
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:

  1. pyright (pyright-langserver) is tried first. It is an npm package, so it needs Node.js: npm i -g pyright.
  2. 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:

<user data>/plugins/org.mte.lsp/jdtls-workspaces/<project id>/

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 clangd
where cmake
where ninja
where pyright-langserver
where pylsp

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:

  1. 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;
  2. your PATH, for the server's command and each known alias (clangd-20, clangd-19, …);
  3. the install locations in the server's definition for your operating system;
  4. 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.json in 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.txt at the project root;
  • a .clangd configuration 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:

cmake -S . -B .mte-compile-commands -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
  • .mte-compile-commands/ is one of the directories MTE scans, so the resulting compile_commands.json is 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 as QT6_DIR for a Qt project). If cmake can'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}"
}
  • language is OctoEdit's own id for the language — the key the settings page, the status segment and the connection are grouped by. command/aliases are the executable names tried on PATH.
  • extensions is the primary file-to-language mapping (see above). Include the dot.
  • args is 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 in didOpen; it defaults to language. Use it when the LSP specification's name differs from ours (bash → shellscript, objc → objective-c).
  • languageIds (optional) overrides languageId per 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 says no <first marker>; it never stops the server.
  • searchDirs is the Windows install-location list; searchDirsMac and searchDirsLinux are 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.
  • args may 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 at compile_commands.json.
  • priority (optional, default 0) 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

<user data>/plugins/org.mte.lsp/lsp-servers/

Every *.json in it is loaded after the bundled set, and:

  • a file whose language and command match 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).

A minimal user definition: <user data>/…/lsp-servers/zig.json
{
    "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 .editorconfig that declares root = 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/searchDirs search above — useful when the server isn't on PATH or you want a specific build.
  • Add Extra arguments, appended after the definition's own — for example --log=verbose for 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.json clangd 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.