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