Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Noyalib logo

noyalib-lsp

Language Server Protocol implementation for noyalib — YAML formatting, validation, and hover information delivered to any LSP-aware editor over stdio JSON-RPC.

Build Crates.io Docs.rs lib.rs OpenSSF Scorecard OpenSSF Best Practices


Contents


Install

cargo install noyalib-lsp

Releases ship the crate archive with a CycloneDX SBOM and sigstore bundles. Pre-built binary tarballs and distro packages are not published yet; cargo install is the supported path.

MSRV: Rust 1.89.0, the floor shared across the noyalib family and the maintainer’s other Rust crates (noyalib ADR-0014). The toolchain-driven floor is lower: criterion 0.8 (the benchmark dev-dependency) declares rust-version = 1.86, so cargo check --all-targets and the bench suite fail on 1.85 (criterion@0.8.2 requires rustc 1.86), though cargo check --lib still builds. We publish the number we verify.


Requirements

  • Rust 1.89.0 or newer to build from source: rust-version in the manifest, enforced by the msrv-core CI job on every push.
  • Any tier-1 platform. CI runs the tests on Linux, macOS, and Windows with the stable, beta, and nightly toolchains; stable is the gate, beta and nightly are early warning.
  • The matching core. This crate pins noyalib at the identical =0.0.X and releases in lockstep with it; Cargo resolves that pin for you.
  • An LSP client (any editor in docs/editor-setup.md); the server speaks stdio with Content-Length framing.

Quick Start

# 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

LSP methodWhat it does
initialize / initialized / shutdown / exitFull LSP lifecycle handshake.
textDocument/didOpen / didChange / didCloseFull-text document sync (TextDocumentSyncKind = 1).
textDocument/publishDiagnosticsParse-error diagnostics emitted on every open + change. Line / column locations.
textDocument/formattingFull-document TextEdit[] from the CST formatter. Empty array when the document is already canonical.
textDocument/hoverMarkdown 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 extension lives in editors/vscode. Once the listing is live it installs from the Marketplace:

ext install sebastienrousseau.noyalib

CI also packages a .vsix on every push, so you can download it from a workflow run (or build it with npm install && npm run package in that directory) and use “Extensions: Install from VSIX…”. Either way it starts the noyalib-lsp binary on your PATH; point at another one with:

{
  "noyalib.path": "/usr/local/bin/noyalib-lsp",
  "[yaml]": {
    "editor.defaultFormatter": "sebastienrousseau.noyalib",
    "editor.formatOnSave": true
  }
}

Zed

~/.config/zed/settings.json:

{
  "languages": {
    "YAML": {
      "language_servers": ["noyalib-lsp"],
      "format_on_save": "on"
    }
  },
  "lsp": {
    "noyalib-lsp": {
      "binary": { "path": "noyalib-lsp" }
    }
  }
}

Neovim (via nvim-lspconfig)

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:

[[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:

{
  "clients": {
    "noyalib-lsp": {
      "enabled": true,
      "command": ["noyalib-lsp"],
      "selector": "source.yaml"
    }
  }
}

Examples

Editor-driving demos under examples/:

ScriptWhat it shows
handshake.shOne-shot initialize / initialized / shutdown / exit round-trip. Smoke test for protocol compliance.
format-on-save.shdidOpen → textDocument/formatting. Returns the TextEdit[] an editor would apply on save.
hover-cursor.shdidOpen → 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.

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

The four entry points, identical across every repo in the family:


Conformance

Every push runs the official yaml-test-suite through this server’s diagnostics, from the same vendored suite and the same core commit as the noyalib core: 406 of 406 (valid cases produce no diagnostic, invalid cases produce one at the failing position). A two-document configuration that uses most of YAML at once (tests/fixtures/ultra-complex/) opens with no diagnostics. Details and the family table: noyalib.com/conformance.

License

Dual-licensed under Apache 2.0 or MIT, at your option.