<p align="center">
<img src="https://cloudcdn.pro/noyalib/v1/logos/noyalib.svg" alt="Noyalib logo" width="128" />
</p>
<h1 align="center">noyalib-lsp</h1>
<p align="center">
<strong>Language Server Protocol implementation for noyalib —
YAML formatting, validation, and hover information delivered
to any LSP-aware editor over stdio JSON-RPC.</strong>
</p>
<p align="center">
<a href="https://github.com/sebastienrousseau/noyalib/actions"><img src="https://img.shields.io/github/actions/workflow/status/sebastienrousseau/noyalib/ci.yml?style=for-the-badge&logo=github" alt="Build" /></a>
<a href="https://crates.io/crates/noyalib-lsp"><img src="https://img.shields.io/crates/v/noyalib-lsp.svg?style=for-the-badge&color=fc8d62&logo=rust" alt="Crates.io" /></a>
<a href="https://docs.rs/noyalib-lsp"><img src="https://img.shields.io/badge/docs.rs-noyalib--lsp-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" alt="Docs.rs" /></a>
<a href="https://lib.rs/crates/noyalib-lsp"><img src="https://img.shields.io/badge/lib.rs-noyalib-orange.svg?style=for-the-badge" alt="lib.rs" /></a>
<a href="https://api.securityscorecards.dev/projects/github.com/sebastienrousseau/noyalib"><img src="https://api.securityscorecards.dev/projects/github.com/sebastienrousseau/noyalib/badge" alt="OpenSSF Scorecard" /></a>
</p>
---
## Contents
- [Install](#install) — Cargo, distro packages
- [Quick Start](#quick-start) — smoke test
- [Why this approach?](#why-this-approach) — design rationale
- [Surface](#surface) — LSP methods supported
- [Editor configuration](#editor-configuration) — VS Code, Zed, Neovim, Helix, Sublime
- [Examples](#examples) — JSON-RPC scripts
- [Performance](#performance) — formatter cost
- [When not to use noyalib-lsp](#when-not-to-use-noyalib-lsp)
- [Documentation](#documentation)
- [License](#license)
---
## Install
```bash
cargo install noyalib-lsp
```
Pre-built binaries for every target the workspace ships are
attached to each GitHub Release; each is signed with cosign
keyless. See the
[install matrix in the workspace README](https://github.com/sebastienrousseau/noyalib#install)
for distro-package paths — `noyalib-lsp` is bundled into the
top-level `noyalib` package on every channel (Homebrew, AUR,
Scoop, Nix, GHCR).
**MSRV: Rust 1.85.0.** The transitive LSP transport stack
(`litemap`, `uuid`) requires recent stables; the noyalib core
library itself stays at 1.75.
---
## Quick Start
```bash
# Smoke test — drives a one-shot LSP handshake over stdio.
noyalib-lsp --version
# As a child process spawned by your editor (the typical path).
# See "Editor configuration" below for VS Code / Zed / Neovim /
# Helix / Sublime examples.
```
---
## Why this approach?
The market has two YAML language servers (`yaml-language-server`,
`taplo`-style hybrids) and both make tradeoffs noyalib-lsp avoids:
- **Byte-faithful formatting.** `textDocument/formatting` runs
through noyalib's lossless CST. An already-canonical document
produces an empty `TextEdit[]` — your editor doesn't churn
whitespace on save. Comments stay where they were; indent
width follows the file's dominant style; only quoting and
inter-key whitespace normalise.
- **Real diagnostics.** Parse errors flow through
`textDocument/publishDiagnostics` with line / column
locations the editor's gutter can highlight directly. No
best-effort parsers, no recovery hand-waving.
- **Schema-aware hover.** When a JSON Schema is attached, hover
surfaces the resolved field type. Schema descriptions land
in the hover card in a follow-up.
- **Stdio transport.** Standard `Content-Length`-framed
JSON-RPC 2.0. Works with every LSP-compliant client; no
client-specific protocol extensions.
- **Pure-Rust, zero `unsafe`.** Same `#![forbid(unsafe_code)]`
guarantee as the noyalib core library.
The whole thing is ~5 KLOC of Rust; the heavy lifting is in the
`noyalib` library, the LSP wrapper just bridges JSON-RPC to the
library's CST + parser surface.
---
## Surface
| `initialize` / `initialized` / `shutdown` / `exit` | Full LSP lifecycle handshake. |
| `textDocument/didOpen` / `didChange` / `didClose` | Full-text document sync (`TextDocumentSyncKind = 1`). |
| `textDocument/publishDiagnostics` | Parse-error diagnostics emitted on every open + change. Line / column locations. |
| `textDocument/formatting` | Full-document `TextEdit[]` from the CST formatter. Empty array when the document is already canonical. |
| `textDocument/hover` | Markdown card with cursor position + document type. Schema-driven descriptions tracked for follow-up. |
Server capabilities response includes `textDocumentSync = 1`,
`documentFormattingProvider = true`, `hoverProvider = true`.
Future capabilities (`rangeFormatting`, `documentSymbols`,
`codeActions`) are gated behind the same lossless-CST surface
in the library.
---
## Editor configuration
### Visual Studio Code
The bundled experience ships through the
[noyalib VS Code extension](https://marketplace.visualstudio.com/items?itemName=sebastienrousseau.noyalib);
no manual config needed. To point at a system-installed binary
instead of the bundled one:
```json
{
"noyalib.path": "/usr/local/bin/noyalib-lsp",
"[yaml]": {
"editor.defaultFormatter": "sebastienrousseau.noyalib",
"editor.formatOnSave": true
}
}
```
### Zed
`~/.config/zed/settings.json`:
```json
{
"languages": {
"YAML": {
"language_servers": ["noyalib-lsp"],
"format_on_save": "on"
}
},
"lsp": {
"noyalib-lsp": {
"binary": { "path": "noyalib-lsp" }
}
}
}
```
### Neovim (via `nvim-lspconfig`)
```lua
require("lspconfig.configs").noyalib = {
default_config = {
cmd = { "noyalib-lsp" },
filetypes = { "yaml" },
root_dir = require("lspconfig.util").find_git_ancestor,
},
}
require("lspconfig").noyalib.setup {
on_attach = function(_, bufnr)
vim.api.nvim_create_autocmd("BufWritePre", {
buffer = bufnr,
callback = function() vim.lsp.buf.format() end,
})
end,
}
```
### Helix
`~/.config/helix/languages.toml`:
```toml
[[language]]
name = "yaml"
language-servers = ["noyalib-lsp"]
auto-format = true
[language-server.noyalib-lsp]
command = "noyalib-lsp"
```
### Sublime Text (via `LSP` package)
`~/.config/sublime-text/Packages/User/LSP.sublime-settings`:
```json
{
"clients": {
"noyalib-lsp": {
"enabled": true,
"command": ["noyalib-lsp"],
"selector": "source.yaml"
}
}
}
```
---
## Examples
Editor-driving demos under
[`crates/noyalib-lsp/examples/`](examples/):
| [`handshake.sh`](examples/handshake.sh) | One-shot `initialize` / `initialized` / `shutdown` / `exit` round-trip. Smoke test for protocol compliance. |
| [`format-on-save.sh`](examples/format-on-save.sh) | `didOpen` → `textDocument/formatting`. Returns the `TextEdit[]` an editor would apply on save. |
| [`hover-cursor.sh`](examples/hover-cursor.sh) | `didOpen` → `textDocument/hover` at a specific `(line, column)`. |
Each script pipes a sequence of `Content-Length`-framed JSON-RPC
messages into `noyalib-lsp` over stdio and prints the response
stream. POSIX-shell only — no `jq`, no `node` dependencies.
```bash
chmod +x crates/noyalib-lsp/examples/*.sh
crates/noyalib-lsp/examples/handshake.sh
```
---
## Performance
`textDocument/formatting` for a 1 MiB YAML document on Apple
M-series ≈ 12 ms (the same wall-clock as `noyafmt --write`
since both share the CST). For per-keystroke formatting under
human-perceivable latency, the server returns an empty
`TextEdit[]` when the document is already canonical, so the
editor avoids a round-trip on every `didChange` after a save.
`textDocument/publishDiagnostics` runs on every `didChange`;
parse cost on a freshly-edited buffer is dominated by the byte
range that changed, not the buffer size.
---
## When not to use noyalib-lsp
- **You want OpenAPI / AsyncAPI / Kubernetes-CRD hover docs out
of the box.** That comes from the JSON Schema attached to
the buffer. noyalib-lsp doesn't ship a schema registry; you
point it at a schema explicitly via the
`yaml.schemas` (style) settings exposed by your editor.
- **You need WebDAV-style multi-document operations
(`workspace/applyEdit` for cross-file refactor).** Not
implemented yet; tracked as a v0.1.x capability extension.
---
## Documentation
- **Engineering policies** (MSRV, SemVer, security, performance, concurrency, platform support, feature flags):
[`doc/POLICIES.md`](https://github.com/sebastienrousseau/noyalib/blob/main/doc/POLICIES.md)
- **Security policy**:
[`SECURITY.md`](https://github.com/sebastienrousseau/noyalib/blob/main/SECURITY.md)
- **API reference**: <https://docs.rs/noyalib-lsp>
- **Editor setup (VS Code, Neovim, Emacs, Helix, Zed, Sublime)**:
[`doc/editor-setup.md`](https://github.com/sebastienrousseau/noyalib/blob/main/crates/noyalib-lsp/doc/editor-setup.md)
- **Protocol coverage (which LSP methods are implemented)**:
[`doc/protocol-coverage.md`](https://github.com/sebastienrousseau/noyalib/blob/main/crates/noyalib-lsp/doc/protocol-coverage.md)
- **Workspace README**:
<https://github.com/sebastienrousseau/noyalib#readme>
- **LSP specification**:
<https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/>
---
## License
Dual-licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)
or [MIT](https://opensource.org/licenses/MIT), at your option.