lib/spoom/sorbet/lsp.rb in spoom-1.1.8 vs lib/spoom/sorbet/lsp.rb in spoom-1.1.9

- old
+ new

@@ -1,6 +1,6 @@ -# typed: true +# typed: strict # frozen_string_literal: true require 'open3' require 'json' @@ -9,41 +9,56 @@ require_relative 'lsp/errors' module Spoom module LSP class Client + extend T::Sig + + sig { params(sorbet_bin: String, sorbet_args: String, path: String).void } def initialize(sorbet_bin, *sorbet_args, path: ".") - @id = 0 - @in, @out, @err, @status = T.unsafe(Open3).popen3(sorbet_bin, *sorbet_args, chdir: path) + @id = T.let(0, Integer) + @open = T.let(false, T::Boolean) + io_in, io_out, io_err, _status = T.unsafe(Open3).popen3(sorbet_bin, *sorbet_args, chdir: path) + @in = T.let(io_in, IO) + @out = T.let(io_out, IO) + @err = T.let(io_err, IO) end + sig { returns(Integer) } def next_id @id += 1 end + sig { params(json_string: String).void } def send_raw(json_string) @in.puts("Content-Length:#{json_string.length}\r\n\r\n#{json_string}") end + sig { params(message: Message).returns(T.nilable(T::Hash[T.untyped, T.untyped])) } def send(message) send_raw(message.to_json) read if message.is_a?(Request) end + sig { returns(T.nilable(String)) } def read_raw header = @out.gets # Sorbet returned an error and forgot to answer raise Error::BadHeaders, "bad response headers" unless header&.match?(/Content-Length: /) len = header.slice(::Range.new(16, nil)).to_i @out.read(len + 2) # +2 'cause of the final \r\n end + sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) } def read - json = JSON.parse(read_raw) + raw_string = read_raw + return nil unless raw_string + json = JSON.parse(raw_string) + # Handle error in the LSP protocol raise ResponseError.from_json(json['error']) if json['error'] # Handle typechecking errors raise Error::Diagnostics.from_json(json['params']) if json['method'] == "textDocument/publishDiagnostics" @@ -51,10 +66,11 @@ json end # LSP requests + sig { params(workspace_path: String).void } def open(workspace_path) raise Error::AlreadyOpen, "Error: CLI already opened" if @open send(Request.new( next_id, 'initialize', @@ -66,10 +82,11 @@ )) send(Notification.new('initialized', {})) @open = true end + sig { params(uri: String, line: Integer, column: Integer).returns(T.nilable(Hover)) } def hover(uri, line, column) json = send(Request.new( next_id, 'textDocument/hover', { @@ -80,14 +97,16 @@ 'line' => line, 'character' => column, }, } )) - return nil unless json['result'] + + return nil unless json && json['result'] Hover.from_json(json['result']) end + sig { params(uri: String, line: Integer, column: Integer).returns(T::Array[SignatureHelp]) } def signatures(uri, line, column) json = send(Request.new( next_id, 'textDocument/signatureHelp', { @@ -98,13 +117,16 @@ 'line' => line, 'character' => column, }, } )) + + return [] unless json && json['result'] && json['result']['signatures'] json['result']['signatures'].map { |loc| SignatureHelp.from_json(loc) } end + sig { params(uri: String, line: Integer, column: Integer).returns(T::Array[Location]) } def definitions(uri, line, column) json = send(Request.new( next_id, 'textDocument/definition', { @@ -115,13 +137,16 @@ 'line' => line, 'character' => column, }, } )) + + return [] unless json && json['result'] json['result'].map { |loc| Location.from_json(loc) } end + sig { params(uri: String, line: Integer, column: Integer).returns(T::Array[Location]) } def type_definitions(uri, line, column) json = send(Request.new( next_id, 'textDocument/typeDefinition', { @@ -132,13 +157,16 @@ 'line' => line, 'character' => column, }, } )) + + return [] unless json && json['result'] json['result'].map { |loc| Location.from_json(loc) } end + sig { params(uri: String, line: Integer, column: Integer, include_decl: T::Boolean).returns(T::Array[Location]) } def references(uri, line, column, include_decl = true) json = send(Request.new( next_id, 'textDocument/references', { @@ -152,41 +180,51 @@ 'context' => { 'includeDeclaration' => include_decl, }, } )) + + return [] unless json && json['result'] json['result'].map { |loc| Location.from_json(loc) } end + sig { params(query: String).returns(T::Array[DocumentSymbol]) } def symbols(query) json = send(Request.new( next_id, 'workspace/symbol', { 'query' => query, } )) + + return [] unless json && json['result'] json['result'].map { |loc| DocumentSymbol.from_json(loc) } end + sig { params(uri: String).returns(T::Array[DocumentSymbol]) } def document_symbols(uri) json = send(Request.new( next_id, 'textDocument/documentSymbol', { 'textDocument' => { 'uri' => uri, }, } )) + + return [] unless json && json['result'] json['result'].map { |loc| DocumentSymbol.from_json(loc) } end + sig { void } def close - send(Request.new(next_id, "shutdown", nil)) + send(Request.new(next_id, "shutdown", {})) @in.close @out.close @err.close + @open = false end end end end