Another LSP server for Markdown note-taking.
If you're on MacOS or Linux:
curl -sSfL https://raw.githubusercontent.com/RaquerLabs/xsmd/main/install.sh | shIf you're on windows:
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 = []The same settings can be provided per-editor through the LSP
initializationOptions payload, which override xsmd.toml:
{ "debug": true, "ignore": ["/journal"] }In Neovim this is passed via init_options:
local lsp_config = {
name = "xsmd",
cmd = { "xsmd" },
filetypes = { "markdown" },
init_options = { debug = false, ignore = {} },
}
vim.lsp.start(lsp_config)To ensure Neovim launches a single xsmd process and correctly shares/reuses it across all open Markdown buffers, configure the server with a dynamically resolved root_dir, specify name = "xsmd", and 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 with::XsmdDump
- Workspace Crawling:
Scans your vault on boot,
locates the project root via the anchor file
xsmd.toml. - Workspace File Watching:
Dynamically registers filesystem watchers for Markdown files (
**/*.md,**/*.markdown) to automatically keep the in-memory database in sync when files are changed externally. - Go to Definition:
- Links starting with
/(e.g.,[Link](/docs/file.md)) are resolved relative to the workspace root. - Links not starting with
/(e.g.,[Link](../file.md)) are resolved relative to the current file's folder.
- Links starting with
- Find References
- Folding:
# Headings,## Subheadings- nested lists (
-or*)
- Autocomplete:
- Caches the primary
# H1 Titleof every note in the directory. Filtering out files that don't have# H1 Titleheaders. - Typing
[autocompletes with note names, adding the folder-relative[Title Text](../path/to/note.md)snippet. - Typing
(inside a link (e.g.,[Label]() autocompletes with paths, also adds the folder-relative snippet.
- Caches the primary
- Rename: Moves files and automatically updates all reference links across the workspace.
- Anchor completion: complete
#headinganchors — in-file headings for[](#, and target-file headings for links like[x](file.md#— backed by a per-document heading index.
The server communicates with Neovim using standard input/output (stdin/stdout) over JSON-RPC.
┌───────────┐ JSON-RPC (stdio) ┌─────────────┐
│ IDE │ ────────────────────────> │ Go Core │
│ (Buffers) │ <──────────────────────── │ (LSP Server)│
└───────────┘ └─────────────┘
│
┌──────────────┴──────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ In-Memory │ │ Goldmark │
│ State Index │ │ AST Parser │
└──────────────┘ └──────────────┘
- Architecture Guide
- visual dependencies
- modules map
- thread-safety concurrency locks
- Execution Flows
- boot-time crawl indexing 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 indexed workspace files (ignoring configured directories):
./dist/xsmd listList workspace files as JSON (path, title, has_h1), sorted by path:
./dist/xsmd list --jsonPrint the version:
./dist/xsmd --versionTo install it globally:
mise run installmise run test- Fork the repo and make your adjustments in the Go code.
- Format your files using
mise run format. - Assert that all unit tests pass with
mise run test. - Send a PR