Another LSP server for Markdown note-taking.
On macOS or Linux, run this command:
curl -sSfL https://raw.githubusercontent.com/RaquerLabs/xsmd/main/install.sh | shOn Windows, run this command:
iwr https://raw.githubusercontent.com/RaquerLabs/xsmd/main/install.ps1 | iexThe LSP server looks for an xsmd.toml file at the root of your project.
You can configure the following options in it:
# Enable verbose debug logs printed to xsmd.log
debug = false
# Folders to ignore during autocomplete. Paths must start from the project root directory.
# For example, "/journal" will ignore everything in "/journal/*"
ignore = []You can provide the same settings per editor through the LSP initializationOptions payload.
These settings override xsmd.toml:
{ "debug": true, "ignore": ["/journal"] }In Neovim, you pass them with init_options:
local lsp_config = {
name = "xsmd",
cmd = { "xsmd" },
filetypes = { "markdown" },
init_options = { debug = false, ignore = {} },
}
vim.lsp.start(lsp_config)When debug = true, the server serves a live trace of every LSP message at
http://127.0.0.1:8666. The UI shows each message with its direction
(client → server or server → client), kind, and payload, and pairs each
request with its response (including the round-trip duration). The port is
only bound while debug mode is on; with debug = false nothing listens.
You can filter by method, direction, or kind, pause auto-scroll, collapse payloads, and clear the history.
Make sure that Neovim launches a single xsmd process and shares it across all open Markdown buffers.
Configure the server with a dynamic root_dir. Specify name = "xsmd". Disable single_file_support:
local lsp_config = {
name = "xsmd",
cmd = { "xsmd" },
filetypes = { "markdown" },
-- Dynamically resolve the workspace root per-buffer
root_dir = function(filepath)
return vim.fs.root(filepath, { "xsmd.toml", ".git" })
end,
-- Prevent spawning a process per file/buffer if no root is detected
single_file_support = false,
-- ... on_attach, capabilities, settings
}
vim.lsp.start(lsp_config)The server provides a list of commands for debug:
xsmd.dumpState: Outputs a list of all current indexed document keys toxsmd.log. In Neovim, you can run this command with::XsmdDump
- Workspace crawling: Scans your vault on boot and locates the project root with the anchor file
xsmd.toml. - Workspace file watching: Registers filesystem watchers for Markdown files (
**/*.md,**/*.markdown). The watchers keep the in-memory database in sync when files change externally. - Go to definition:
- The server resolves links that start with
/(for example,[Link](/docs/file.md)) relative to the workspace root. - The server resolves links that do not start with
/(for example,[Link](../file.md)) relative to the folder of the current file.
- The server resolves links that start with
- Find references
- Folding:
# Headings,## Subheadings- Nested lists (
-or*)
- Autocomplete:
- Caches the primary
# H1 Titleof every note in the directory. It excludes the notes that do not have a# H1 Titleheader. - Typing
[autocompletes with note names. It adds the folder-relative[Title Text](../path/to/note.md)snippet. - Typing
(inside a link (for example,[Label]() autocompletes with paths. It also adds the folder-relative snippet.
- Caches the primary
- Rename: Moves files and updates all reference links across the workspace.
- Anchor completion: Complete
#headinganchors. The anchors are in-file headings for[](#and target-file headings for links like[x](file.md#. The feature uses a per-document heading index.
The server communicates with Neovim over JSON-RPC. The transport is standard input/output (stdin/stdout).
┌───────────┐ JSON-RPC (stdio) ┌─────────────┐
│ IDE │ ────────────────────────> │ Go Core │
│ (Buffers) │ <──────────────────────── │ (LSP Server)│
└───────────┘ └─────────────┘
│
┌──────────────┴──────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ In-Memory │ │ Goldmark │
│ State Index │ │ AST Parser │
└──────────────┘ └──────────────┘
- Architecture Guide
- visual dependencies
- modules map
- concurrency locks
- Execution Flows
- boot-time crawl loops
- real-time diagnostics triggers
- character coordinate parsing
- Development & Contributing
- compiling locally
- formatting files
- running tests
- git contributions
Compile:
mise run buildLaunch the LSP server:
./dist/xsmdList the indexed workspace files, except the configured directories:
./dist/xsmd listList the workspace files as JSON (path, title, has_h1), sorted by path:
./dist/xsmd list --jsonPrint the version:
./dist/xsmd --versionInstall globally:
mise run installmise run test- Fork the repo. Make your changes in the Go code.
- Format your files with
mise run format. - Run the tests with
mise run test. Make sure that all unit tests pass. - Send a PR.