lsp-plugin
stableLSP (Language Server Protocol) message building and parsing helpers for constructing JSON-RPC requests, responses, notifications, diagnostics, completions, and hover results.
use plugin lsp-plugin::{create_request, create_response, create_notification, …} Functions (19)
- create_request Build a JSON-RPC request string
- create_response Build a JSON-RPC response string
- create_notification Build a JSON-RPC notification string
- create_error_response Build a JSON-RPC error response string
- encode_lsp_message Wrap JSON body with Content-Length header
- decode_lsp_header Parse Content-Length from a raw header
- position Create an LSP Position object
- range Create an LSP Range object
- diagnostic Create an LSP Diagnostic object
- completion_item Create an LSP CompletionItem object
- hover Create an LSP Hover result object
- document_symbol Create an LSP DocumentSymbol object
- text_edit Create an LSP TextEdit object
- parse_message Parse a JSON string into a table
- severity_error Returns diagnostic severity 1 (Error)
- severity_warning Returns diagnostic severity 2 (Warning)
- severity_info Returns diagnostic severity 3 (Information)
- severity_hint Returns diagnostic severity 4 (Hint)
- publish_diagnostics Build a publishDiagnostics notification
Overview
lsp-plugin is a toolkit for speaking the Language Server Protocol from Zolo. It
has no connection or server state of its own: every function is a pure builder or
parser that turns Zolo values into the JSON-RPC and LSP shapes a client expects,
or turns a raw wire message back into a Zolo table. Requests, responses,
notifications, and error responses come out as ready-to-send JSON strings, while
the structural helpers (position, range, diagnostic, completion_item,
hover, document_symbol, text_edit) produce the nested tables those messages
carry as their params/result payloads.
The mental model is layered: build a payload table with the structural helpers,
wrap it in a JSON-RPC envelope with create_request / create_response /
create_notification, then frame it for the wire with encode_lsp_message. On
the way in you reverse the flow — decode_lsp_header reads the Content-Length
and parse_message decodes the JSON body into a table. The severity_*
constants and publish_diagnostics shortcut cover the most common diagnostics
path. Reach for this plugin when implementing a language server, an LSP client,
or any tooling that exchanges JSON-RPC messages.
Common patterns
Build a hover response payload and frame it for the wire in one pass:
use plugin lsp-plugin::{hover, range, create_response, encode_lsp_message}
let r = range(10, 4, 10, 9)
let h = hover("**let** — declares an immutable binding", r)
let wire = encode_lsp_message(create_response(1, h))
print(wire)
Collect diagnostics for a file and publish them as a single notification:
use plugin lsp-plugin::{range, diagnostic, severity_error, severity_warning, publish_diagnostics, encode_lsp_message}
let diags = [
diagnostic(range(2, 0, 2, 5), "Undefined variable 'x'", severity_error()),
diagnostic(range(7, 4, 7, 9), "Unused binding", severity_warning()),
]
let notif = publish_diagnostics("file:///main.zolo", diags)
print(encode_lsp_message(notif))
Decode an incoming wire message: read the header, then parse the JSON body:
use plugin lsp-plugin::{decode_lsp_header, parse_message}
let raw = "Content-Length: 52\r\n\r\n"
let header = decode_lsp_header(raw)
print("expecting {header["content_length"]} bytes")
let msg = parse_message('{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}')
print("method: {msg["method"]}")
Build a JSON-RPC request string
Builds a JSON-RPC 2.0 request string with the given id, method, and optional params table. Use this when sending requests that expect a response from the client or server.
use plugin lsp-plugin::{create_request}
let req = create_request(1, "textDocument/hover", #{"textDocument": #{"uri": "file:///main.zolo"}, "position": #{"line": 10, "character": 5}})
print(req)
The id accepts any value, and the structural helpers compose into the params:
use plugin lsp-plugin::{create_request, position, encode_lsp_message}
let pos = position(3, 12)
let req = create_request("req-7", "textDocument/definition", #{"position": pos})
print(encode_lsp_message(req))
Build a JSON-RPC response string
Builds a JSON-RPC 2.0 success response string with the given id and result table.
use plugin lsp-plugin::{create_response, hover}
let h = hover("**string** — built-in type")
let resp = create_response(1, h)
print(resp)
Build a JSON-RPC notification string
Builds a JSON-RPC 2.0 notification string (no id field). Notifications do not expect a response.
use plugin lsp-plugin::{create_notification}
let notif = create_notification("textDocument/didOpen", #{"textDocument": #{"uri": "file:///main.zolo", "languageId": "zolo", "version": 1, "text": ""}})
print(notif)
Build a JSON-RPC error response string
Builds a JSON-RPC 2.0 error response with the given request id, error code integer, and human-readable message.
use plugin lsp-plugin::{create_error_response}
let err = create_error_response(1, -32601, "Method not found")
print(err)
Wrap JSON body with Content-Length header
Wraps a JSON string with the LSP wire-format header Content-Length: N\r\n\r\n. Use this before writing messages to stdout or a socket.
use plugin lsp-plugin::{create_notification, encode_lsp_message}
let body = create_notification("initialized", #{})
let wire = encode_lsp_message(body)
print(wire)
Parse Content-Length from a raw header
Parses the Content-Length value from a raw LSP header string. Returns a table with a content_length integer field.
use plugin lsp-plugin::{decode_lsp_header}
let header = "Content-Length: 128\r\n\r\n"
let parsed = decode_lsp_header(header)
print("Body length: {parsed["content_length"]}")
Create an LSP Position object
Creates an LSP Position object with zero-based line and character fields.
use plugin lsp-plugin::{position}
let pos = position(10, 5)
Create an LSP Range object
Creates an LSP Range object with start and end position fields.
use plugin lsp-plugin::{range}
let r = range(10, 0, 10, 20)
A range is just two positions, so it slots directly into a text_edit:
use plugin lsp-plugin::{range, text_edit}
let edit = text_edit(range(0, 0, 0, 3), "let")
print(edit["newText"])
Create an LSP Diagnostic object
Creates an LSP Diagnostic object. Use severity_error(), severity_warning(), etc. to get the correct severity integer.
use plugin lsp-plugin::{range, diagnostic, severity_error}
let r = range(5, 0, 5, 10)
let diag = diagnostic(r, "Undefined variable 'x'", severity_error())
Create an LSP CompletionItem object
Creates an LSP CompletionItem object. kind is an LSP completion kind integer (e.g. 1=Text, 2=Method, 3=Function, 6=Variable). detail and insert_text are optional.
use plugin lsp-plugin::{completion_item}
let item = completion_item("print", 3, "fn print(value: any)", "print($1)")
The trailing two arguments are optional — omit them for a bare label:
use plugin lsp-plugin::{completion_item, create_response}
let items = [
completion_item("let", 14),
completion_item("match", 14),
]
let resp = create_response(5, items)
print(resp)
Create an LSP Hover result object
Creates an LSP Hover result with markdown contents. The optional range highlights the hovered token in the editor.
use plugin lsp-plugin::{hover, range, create_response}
let r = range(2, 4, 2, 9)
let h = hover("**let** — declares an immutable binding", r)
let resp = create_response(42, h)
Create an LSP DocumentSymbol object
Creates an LSP DocumentSymbol object. kind is an LSP symbol kind integer (e.g. 12=Function, 13=Variable). Both range and selection_range are LSP Range tables.
use plugin lsp-plugin::{range, document_symbol}
let full = range(0, 0, 10, 1)
let name_range = range(0, 3, 0, 8)
let sym = document_symbol("myFn", 12, full, name_range)
Create an LSP TextEdit object
Creates an LSP TextEdit object describing a text replacement at range with new_text.
use plugin lsp-plugin::{range, text_edit}
let r = range(3, 0, 3, 5)
let edit = text_edit(r, "hello")
Parse a JSON string into a table
Parses a raw JSON string into a Zolo table. Use this to decode incoming LSP messages received over stdin or a socket.
use plugin lsp-plugin::{parse_message}
let msg = parse_message('{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}')
print("Method: {msg["method"]}")
Nested objects decode into nested tables you can dig into by key:
use plugin lsp-plugin::{parse_message}
let msg = parse_message('{"method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///a.zolo"}}}')
print("opened: {msg["params"]["textDocument"]["uri"]}")
Returns diagnostic severity 1 (Error)
Returns the LSP diagnostic severity constant for Error (value 1).
use plugin lsp-plugin::{severity_error}
print(severity_error())
Returns diagnostic severity 2 (Warning)
Returns the LSP diagnostic severity constant for Warning (value 2).
use plugin lsp-plugin::{severity_warning}
print(severity_warning())
Returns diagnostic severity 3 (Information)
Returns the LSP diagnostic severity constant for Information (value 3).
use plugin lsp-plugin::{severity_info}
print(severity_info())
Returns diagnostic severity 4 (Hint)
Returns the LSP diagnostic severity constant for Hint (value 4).
use plugin lsp-plugin::{severity_hint}
print(severity_hint())
Build a publishDiagnostics notification
Builds a textDocument/publishDiagnostics notification JSON string containing the given uri and array of diagnostic tables.
use plugin lsp-plugin::{range, diagnostic, severity_warning, publish_diagnostics, encode_lsp_message}
let r = range(0, 0, 0, 5)
let diag = diagnostic(r, "Unused variable", severity_warning())
let notif = publish_diagnostics("file:///main.zolo", [diag])
let wire = encode_lsp_message(notif)
print(wire)