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

rubocop:disable Layout/LineLength
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