Skip to content

lsp-plugin

stable

LSP (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, …}
19 functions Utilities
/ filter jk navigate Esc clear
Functions (19)
  1. create_request Build a JSON-RPC request string
  2. create_response Build a JSON-RPC response string
  3. create_notification Build a JSON-RPC notification string
  4. create_error_response Build a JSON-RPC error response string
  5. encode_lsp_message Wrap JSON body with Content-Length header
  6. decode_lsp_header Parse Content-Length from a raw header
  7. position Create an LSP Position object
  8. range Create an LSP Range object
  9. diagnostic Create an LSP Diagnostic object
  10. completion_item Create an LSP CompletionItem object
  11. hover Create an LSP Hover result object
  12. document_symbol Create an LSP DocumentSymbol object
  13. text_edit Create an LSP TextEdit object
  14. parse_message Parse a JSON string into a table
  15. severity_error Returns diagnostic severity 1 (Error)
  16. severity_warning Returns diagnostic severity 2 (Warning)
  17. severity_info Returns diagnostic severity 3 (Information)
  18. severity_hint Returns diagnostic severity 4 (Hint)
  19. 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)
enespt-br