TL;DR: Synapse ships an LSP companion (
syn lsp) so any editor that speaks LSP gets entity-aware completion, hover, go-to- definition and dangling-reference diagnostics on every.mdfile in a workspace. Build with thelspfeature, point your editor at thesyn lspbinary, done.
1. Build with the LSP feature
cargo install synapse --features "mcp lsp"
# or, from this repo:
cargo build --release --features "mcp lsp"
# binary lands at target/release/syn
Confirm the subcommand:
syn lsp --help
# Run the LSP companion on stdio (slug/type/tag completions, hover,
# go-to-definition, dangling-ref diagnostics): SYN-130.
The server reads .synapse/index.db from the workspace root; runs
syn reindex once if completions look stale.
2. What you get
- Completion in
.mdfiles:- Inside
[[<cursor>: every workspace entity slug. - After
type: <cursor>: the six entity types (project, person, entity, api, task, note). - Inside
tags: [<cursor>: every distinct tag in the workspace.
- Inside
- Hover over
[[slug]]: entity title + first paragraph of the body. - Go-to-definition on
[[slug]]: jumps to the entity’s markdown file. - Diagnostics:
[[slug]]with no matching entity is flagged as a Warning,source: "synapse".
3. Helix
~/.config/helix/languages.toml:
[language-server.syn-lsp]
command = "syn"
args = ["lsp"]
[[language]]
name = "markdown"
language-servers = ["syn-lsp"]
roots = [".synapse"]
Helix auto-discovers the workspace root by walking up to .synapse/.
4. Neovim (nvim-lspconfig)
init.lua:
local lspconfig = require("lspconfig")
local configs = require("lspconfig.configs")
if not configs.syn_lsp then
configs.syn_lsp = {
default_config = {
cmd = { "syn", "lsp" },
filetypes = { "markdown" },
root_dir = lspconfig.util.root_pattern(".synapse"),
settings = {},
},
}
end
lspconfig.syn_lsp.setup({})
5. VS Code (and Cursor)
VS Code does not auto-discover non-marketplace LSPs, so it needs the
thinnest possible extension. Drop this into a folder and code --install-extension .:
// package.json
{
"name": "synapse",
"version": "0.0.1",
"engines": { "vscode": "^1.94.0" },
"activationEvents": ["onLanguage:markdown"],
"main": "./extension.js",
"contributes": {
"languages": [{ "id": "markdown", "extensions": [".md"] }]
}
}
// extension.js
const { LanguageClient, TransportKind } = require("vscode-languageclient/node");
let client;
function activate() {
client = new LanguageClient(
"synLsp",
"Synapse LSP",
{
command: "syn",
args: ["lsp"],
transport: TransportKind.stdio,
},
{
documentSelector: [{ scheme: "file", language: "markdown" }],
workspaceFolder: undefined,
},
);
client.start();
}
function deactivate() { return client && client.stop(); }
module.exports = { activate, deactivate };
Cursor inherits the same extension API; the config above works identically.
6. Zed
~/.config/zed/settings.json:
{
"lsp": {
"syn-lsp": {
"binary": { "path": "syn", "arguments": ["lsp"] }
}
},
"languages": {
"Markdown": { "language_servers": ["syn-lsp"] }
}
}
7. Troubleshooting
No completions. Run syn reindex in the workspace. The LSP
server reads from .synapse/index.db; if it’s missing or empty
nothing comes back. Also verify your editor is actually launching
the server: most editors expose an “LSP log” command that shows
the stdio handshake.
[[slug]] warning is wrong. The diagnostic source is synapse
and it fires when no entity with that slug exists in the index. The
fix is either (a) create the entity (syn project add foo, etc.) or
(b) update the link.
Hover shows nothing. Hover is only implemented for [[slug]]
references. Hovering on free text returns nothing by design.
Go-to-definition opens the wrong file. Each slug must be unique across the workspace: the index returns the first match. If you have two entities with the same slug in different types, rename one.
8. References
- ADR-018: why we picked
tower-lsp-serverover the inactivetower-lsp. - SYN-130, acceptance criteria for the v0.4 LSP companion.
- LSP 3.18 spec: https://microsoft.github.io/language-server-protocol/.