srf-lsp/README.md
Emil Lerch 16bc787738
All checks were successful
Generic zig build / build (push) Successful in 55s
nix flake + ci
2026-09-01 16:23:31 -07:00

173 lines
5.2 KiB
Markdown

# srf-lsp
Language Server Protocol implementation for [SRF (Simple Record Format)](https://git.lerch.org/lobo/srf), written in Zig.
Provides real-time parse error diagnostics using the SRF library's parser directly.
## Features
- Parse error diagnostics on open and edit, using the SRF library's parser
directly: missing or duplicate `#!srfv1`, bad type hints, unparseable values,
data after `#!eof`, and so on.
- All errors from a parse are reported, not just the first.
- A type-consistency warning when one key holds different value kinds in
different places in the same document, for example `close_price:num:100.00` in
most records and `close_price::200.00` in one. `k::32` is valid SRF and often
deliberate, so the check is not "this value looks numeric" but "this key is
typed differently elsewhere in this file", which the document itself evidences.
Kinds are compared by the `srf.Value` variant a hint produces, so an empty
hint, `string` and a byte count are all one kind. A uniformly mistyped document
has no minority and so gets no warning.
- Length-prefix checking for the cases srf does not cover. A numeric type hint
declares an exact byte count. srf reports a wrong one in compact format, but in
long format it keeps the declared bytes and discards the rest of the line
without a word, and a count running past the end of the file fails with no
location at all. Both are detected here and reported on the offending bytes.
- Hover, covering every construct in the format:
- `#!srfv1` and each directive, explained.
- `#!expires`, `#!created` and `#!modified` render their Unix timestamp as a
UTC date, because nobody can read `1772589213`.
- Keys and values show the parsed interpretation: numbers, booleans, byte
counts for strings, and for `binary` values the decoded content when it is
text (`decoding to 5 bytes: "hello"`) or a note when it is not.
- Type hints are explained, including a numeric hint's length-prefix meaning.
- A string value that also parses as a number gets a one-line note that a
consumer coercing it to a numeric field needs `strings_to_numbers`.
- Position encoding is negotiated during `initialize` (`utf-8`, `utf-16` or
`utf-32`), so columns line up even on lines containing multi-byte characters.
- Full-document sync, which matches SRF's single-pass parser.
- stdio transport.
Value interpretation defers to the srf library rather than reimplementing its
rules, so hover and diagnostics cannot disagree about the same text.
Not implemented: completion, document symbols, formatting, goto definition,
semantic tokens, and pull diagnostics (`textDocument/diagnostic`). Requests for
them are answered with `MethodNotFound` rather than left unanswered, and none of
them are advertised as server capabilities.
## Requirements
Zig 0.16.0. Pinned in `.mise.toml`, along with the other tooling.
## Setup
```sh
mise install
```
## Build & Test
```sh
zig build
zig build test
```
The binary is at `zig-out/bin/srf-lsp`.
## Install
The Neovim config below expects `srf-lsp` on `PATH`.
### With nix
This repository is a flake. To add it to a `buildEnv`-style home profile:
```nix
inputs = {
srf-lsp.url = "git+https://git.lerch.org/lobo/srf-lsp.git";
# Optional: reuse your own nixpkgs instead of instantiating another one.
srf-lsp.inputs.nixpkgs.follows = "nixpkgs";
};
```
then add `srf-lsp.packages.${system}.default` to your package list. Or run it
directly:
```sh
nix run git+https://git.lerch.org/lobo/srf-lsp.git
```
`nix flake update srf-lsp` picks up a new version. The build runs the full test
suite in the sandbox, so a release that fails its tests will not install.
Bumping the `srf` dependency changes `build.zig.zon`, which invalidates the
`zigDeps` hash in `nix/package.nix`. To refresh it: set the hash to
`lib.fakeHash`, build, and paste the hash nix reports.
### Without nix
```sh
zig build && install -m755 zig-out/bin/srf-lsp ~/.local/bin/srf-lsp
```
## Neovim Integration
Neovim 0.11 or newer. Add to your config:
```lua
vim.filetype.add({
extension = {
srf = "srf",
},
})
vim.lsp.config("srf_lsp", {
cmd = { "srf-lsp" },
filetypes = { "srf" },
root_markers = { ".git", "/" }, -- single file server
})
vim.lsp.enable("srf_lsp")
```
Check it attached with `:checkhealth vim.lsp` on a `.srf` buffer. Server logs go
to stderr, which Neovim captures in `:LspLog`.
## Combined Setup (with srf-tree-sitter)
For both syntax highlighting and error detection:
```lua
vim.filetype.add({
extension = {
srf = "srf",
},
})
-- Diagnostics
vim.lsp.config("srf_lsp", {
cmd = { "srf-lsp" },
filetypes = { "srf" },
root_markers = { ".git", "/" },
})
vim.lsp.enable("srf_lsp")
-- Syntax highlighting
vim.api.nvim_create_autocmd("User", {
pattern = "TSUpdate",
callback = function()
require("nvim-treesitter.parsers").srf = {
install_info = {
url = "https://github.com/elerch/srf-tree-sitter",
branch = "master",
queries = "queries",
},
}
end,
})
vim.api.nvim_create_autocmd("FileType", {
pattern = "srf",
callback = function(args)
pcall(vim.treesitter.start, args.buf)
end,
})
```
Then install the tree-sitter parser (`:TSInstall srf` no-ops if it is already
present, so use `:TSUpdate srf` to pick up a newer grammar):
```vim
:TSUpdate srf
```