class SyntaxTree::LanguageServer
stree lsp
language server protocol. It can be invoked through the CLI by running:
Syntax Tree additionally ships with a language server conforming to the
def capabilities
def capabilities { documentFormattingProvider: true, inlayHintProvider: { resolveProvider: false }, textDocumentSync: { change: 1, openClose: true } } end
def format(source, extension)
def format(source, extension) text = SyntaxTree::HANDLERS[".#{extension}"].format(source, print_width) [ { range: { start: { line: 0, character: 0 }, end: { line: source.lines.size + 1, character: 0 } }, newText: text } ] rescue Parser::ParseError # If there is a parse error, then we're not going to return any formatting # changes for this source. nil end
def initialize(
def initialize( input: $stdin, output: $stdout, print_width: DEFAULT_PRINT_WIDTH ) @input = input.binmode @output = output.binmode @print_width = print_width end
def inlay_hints(source)
def inlay_hints(source) visitor = InlayHints.new SyntaxTree.parse(source).accept(visitor) visitor.hints rescue Parser::ParseError # If there is a parse error, then we're not going to return any inlay # hints for this source. [] end
def log(message)
def log(message) write(method: "window/logMessage", params: { type: 4, message: message }) end
def run
def run store = Hash.new do |hash, uri| filepath = CGI.unescape(URI.parse(uri).path) File.exist?(filepath) ? (hash[uri] = File.read(filepath)) : nil end while (headers = input.gets("\r\n\r\n")) source = input.read(headers[/Content-Length: (\d+)/i, 1].to_i) request = JSON.parse(source, symbolize_names: true) # stree-ignore case request when Request[method: "initialize", id: :any] store.clear write(id: request[:id], result: { capabilities: capabilities }) when Request[method: "initialized"] # ignored when Request[method: "shutdown"] # tolerate missing ID to be a good citizen store.clear write(id: request[:id], result: {}) return when Request[method: "textDocument/didChange", params: { textDocument: { uri: :any }, contentChanges: [{ text: :any }] }] store[request.dig(:params, :textDocument, :uri)] = request.dig(:params, :contentChanges, 0, :text) when Request[method: "textDocument/didOpen", params: { textDocument: { uri: :any, text: :any } }] store[request.dig(:params, :textDocument, :uri)] = request.dig(:params, :textDocument, :text) when Request[method: "textDocument/didClose", params: { textDocument: { uri: :any } }] store.delete(request.dig(:params, :textDocument, :uri)) when Request[method: "textDocument/formatting", id: :any, params: { textDocument: { uri: :any } }] uri = request.dig(:params, :textDocument, :uri) contents = store[uri] write(id: request[:id], result: contents ? format(contents, uri.split(".").last) : nil) when Request[method: "textDocument/inlayHint", id: :any, params: { textDocument: { uri: :any } }] uri = request.dig(:params, :textDocument, :uri) contents = store[uri] write(id: request[:id], result: contents ? inlay_hints(contents) : nil) when Request[method: "syntaxTree/visualizing", id: :any, params: { textDocument: { uri: :any } }] uri = request.dig(:params, :textDocument, :uri) write(id: request[:id], result: PP.pp(SyntaxTree.parse(store[uri]), +"")) when Request[method: %r{\$/.+}] # ignored when Request[method: "textDocument/documentColor", params: { textDocument: { uri: :any } }] # ignored else raise ArgumentError, "Unhandled: #{request}" end end end
def write(value)
def write(value) response = value.merge(jsonrpc: "2.0").to_json output.print("Content-Length: #{response.bytesize}\r\n\r\n#{response}") output.flush end