Class: SyntaxTree::LanguageServer

Inherits:
Object
  • Object
show all
Defined in:
lib/syntax_tree/language_server.rb

Overview

Syntax Tree additionally ships with a language server conforming to the language server protocol. It can be invoked through the CLI by running:

stree lsp

Defined Under Namespace

Modules: Request Classes: InlayHints

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input: $stdin, output: $stdout, print_width: DEFAULT_PRINT_WIDTH, ignore_files: []) ⇒ LanguageServer

Returns a new instance of LanguageServer.



217
218
219
220
221
222
223
224
225
226
227
# File 'lib/syntax_tree/language_server.rb', line 217

def initialize(
  input: $stdin,
  output: $stdout,
  print_width: DEFAULT_PRINT_WIDTH,
  ignore_files: []
)
  @input = input.binmode
  @output = output.binmode
  @print_width = print_width
  @ignore_files = ignore_files
end

Instance Attribute Details

#inputObject (readonly)

Returns the value of attribute input.



215
216
217
# File 'lib/syntax_tree/language_server.rb', line 215

def input
  @input
end

#outputObject (readonly)

Returns the value of attribute output.



215
216
217
# File 'lib/syntax_tree/language_server.rb', line 215

def output
  @output
end

Returns the value of attribute print_width.



215
216
217
# File 'lib/syntax_tree/language_server.rb', line 215

def print_width
  @print_width
end

Instance Method Details

#runObject

rubocop:disable Layout/LineLength



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/syntax_tree/language_server.rb', line 230

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)
      filepath = uri.split("///").last
      ignore = @ignore_files.any? do |glob|
        File.fnmatch(glob, filepath)
      end
      contents = store[uri]
      write(id: request[:id], result: contents && !ignore ? 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