Files
cell/editors/vscode/lsp/lsp.ce
2026-02-09 18:53:13 -06:00

210 lines
5.5 KiB
Plaintext

// ƿit Language Server Protocol (LSP) main loop.
// Communicates via JSON-RPC over stdin/stdout.
var fd = use('fd')
var json_mod = use('json')
var protocol = use('protocol')
var analysis_make = use('analysis')
var completions = use('completions')
var hover_mod = use('hover')
var symbols = use('symbols')
// Get tokenize_mod and parse_mod from the environment.
// These are the same functions the compiler uses internally.
var tokenize_mod = use('tokenize')
var parse_mod = use('parse')
// Create analysis module bound to tokenize/parse
var analysis = analysis_make(tokenize_mod, parse_mod)
// Document store: URI -> {text, version, ast, tokens, errors}
var docs = {}
// Log to stderr for debugging (does not interfere with protocol).
var log = function(msg) {
fd.write(2, `[pit-lsp] ${msg}\n`)
}
// Publish diagnostics for a document.
var publish_diagnostics = function(uri, doc) {
var diags = analysis.diagnostics(doc)
protocol.notify("textDocument/publishDiagnostics", {
uri: uri,
diagnostics: diags
})
}
// Parse a document and publish diagnostics.
var parse_and_notify = function(uri, src, version) {
var doc = analysis.update(docs, uri, {src: src, version: version})
publish_diagnostics(uri, doc)
}
// Handle initialize request.
var handle_initialize = function(id, params) {
protocol.respond(id, {
capabilities: {
textDocumentSync: {
openClose: true,
change: 1,
save: {includeText: true}
},
completionProvider: {
triggerCharacters: [".", "$"]
},
hoverProvider: true,
definitionProvider: true,
documentSymbolProvider: true
},
serverInfo: {
name: "pit-lsp",
version: "0.1.0"
}
})
}
// Handle textDocument/didOpen notification.
var handle_did_open = function(params) {
var td = params.textDocument
parse_and_notify(td.uri, td.text, td.version)
}
// Handle textDocument/didChange notification (full text sync).
var handle_did_change = function(params) {
var td = params.textDocument
var changes = params.contentChanges
if (length(changes) > 0) {
parse_and_notify(td.uri, changes[0].text, td.version)
}
}
// Handle textDocument/didClose notification.
var handle_did_close = function(params) {
var uri = params.textDocument.uri
analysis.remove(docs, uri)
// Clear diagnostics
protocol.notify("textDocument/publishDiagnostics", {
uri: uri,
diagnostics: []
})
}
// Handle textDocument/didSave notification.
var handle_did_save = function(params) {
var td = params.textDocument
if (params.text != null) {
parse_and_notify(td.uri, params.text, td.version)
}
}
// Handle textDocument/completion request.
var handle_completion = function(id, params) {
var uri = params.textDocument.uri
var pos = params.position
var doc = docs[uri]
var items = []
if (doc != null) {
items = completions.complete(doc, pos.line, pos.character)
}
protocol.respond(id, items)
}
// Handle textDocument/hover request.
var handle_hover = function(id, params) {
var uri = params.textDocument.uri
var pos = params.position
var doc = docs[uri]
var result = null
if (doc != null) {
result = hover_mod.hover(doc, pos.line, pos.character, analysis.token_at)
}
protocol.respond(id, result)
}
// Handle textDocument/definition request.
var handle_definition = function(id, params) {
var uri = params.textDocument.uri
var pos = params.position
var doc = docs[uri]
var result = null
if (doc != null) {
result = symbols.definition(doc, pos.line, pos.character, analysis.token_at)
}
protocol.respond(id, result)
}
// Handle textDocument/documentSymbol request.
var handle_document_symbol = function(id, params) {
var uri = params.textDocument.uri
var doc = docs[uri]
var result = []
if (doc != null) {
result = symbols.document_symbols(doc)
}
protocol.respond(id, result)
}
// Dispatch a single message. Wrapped in a function for disruption handling.
var dispatch_message = function(msg) {
var method = msg.method
if (method == "initialize") {
handle_initialize(msg.id, msg.params)
} else if (method == "initialized") {
// no-op
} else if (method == "textDocument/didOpen") {
handle_did_open(msg.params)
} else if (method == "textDocument/didChange") {
handle_did_change(msg.params)
} else if (method == "textDocument/didClose") {
handle_did_close(msg.params)
} else if (method == "textDocument/didSave") {
handle_did_save(msg.params)
} else if (method == "textDocument/completion") {
handle_completion(msg.id, msg.params)
} else if (method == "textDocument/hover") {
handle_hover(msg.id, msg.params)
} else if (method == "textDocument/definition") {
handle_definition(msg.id, msg.params)
} else if (method == "textDocument/documentSymbol") {
handle_document_symbol(msg.id, msg.params)
} else if (method == "shutdown") {
protocol.respond(msg.id, null)
return "shutdown"
} else if (method == "exit") {
return "exit"
} else {
if (msg.id != null) {
protocol.respond_error(msg.id, -32601, `Method not found: ${method}`)
}
}
return null
} disruption {
log(`error handling ${msg.method}`)
if (msg.id != null) {
protocol.respond_error(msg.id, -32603, `Internal error handling ${msg.method}`)
}
return null
}
// Main loop.
log("starting")
var running = true
var msg = null
var result = null
while (running) {
msg = protocol.read_message()
if (msg == null) {
running = false
break
}
result = dispatch_message(msg)
if (result == "exit") {
running = false
}
}
log("stopped")