Class: Spoom::LSP::Client

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/spoom/sorbet/lsp.rb

Instance Method Summary collapse

Constructor Details

#initialize(sorbet_bin, *sorbet_args, path: ".") ⇒ Client

Returns a new instance of Client.



17
18
19
20
21
22
23
24
# File 'lib/spoom/sorbet/lsp.rb', line 17

def initialize(sorbet_bin, *sorbet_args, 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

Instance Method Details

#closeObject



229
230
231
232
233
234
235
# File 'lib/spoom/sorbet/lsp.rb', line 229

def close
  send(Request.new(next_id, "shutdown", {}))
  @in.close
  @out.close
  @err.close
  @open = false
end

#definitions(uri, line, column) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/spoom/sorbet/lsp.rb', line 131

def definitions(uri, line, column)
  json = send(Request.new(
    next_id,
    "textDocument/definition",
    {
      "textDocument" => {
        "uri" => uri,
      },
      "position" => {
        "line" => line,
        "character" => column,
      },
    },
  ))

  return [] unless json && json["result"]

  json["result"].map { |loc| Location.from_json(loc) }
end

#document_symbols(uri) ⇒ Object



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/spoom/sorbet/lsp.rb', line 212

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

#hover(uri, line, column) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/spoom/sorbet/lsp.rb', line 89

def hover(uri, line, column)
  json = send(Request.new(
    next_id,
    "textDocument/hover",
    {
      "textDocument" => {
        "uri" => uri,
      },
      "position" => {
        "line" => line,
        "character" => column,
      },
    },
  ))

  return nil unless json && json["result"]

  Hover.from_json(json["result"])
end

#next_idObject



27
28
29
# File 'lib/spoom/sorbet/lsp.rb', line 27

def next_id
  @id += 1
end

#open(workspace_path) ⇒ Object

Raises:



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/spoom/sorbet/lsp.rb', line 72

def open(workspace_path)
  raise Error::AlreadyOpen, "Error: CLI already opened" if @open

  send(Request.new(
    next_id,
    "initialize",
    {
      "rootPath" => workspace_path,
      "rootUri" => "file://#{workspace_path}",
      "capabilities" => {},
    },
  ))
  send(Notification.new("initialized", {}))
  @open = true
end

#readObject



54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/spoom/sorbet/lsp.rb', line 54

def read
  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"

  json
end

#read_rawObject

Raises:



43
44
45
46
47
48
49
50
51
# File 'lib/spoom/sorbet/lsp.rb', line 43

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

#references(uri, line, column, include_decl = true) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/spoom/sorbet/lsp.rb', line 173

def references(uri, line, column, include_decl = true)
  json = send(Request.new(
    next_id,
    "textDocument/references",
    {
      "textDocument" => {
        "uri" => uri,
      },
      "position" => {
        "line" => line,
        "character" => column,
      },
      "context" => {
        "includeDeclaration" => include_decl,
      },
    },
  ))

  return [] unless json && json["result"]

  json["result"].map { |loc| Location.from_json(loc) }
end

#send(message) ⇒ Object



37
38
39
40
# File 'lib/spoom/sorbet/lsp.rb', line 37

def send(message)
  send_raw(message.to_json)
  read if message.is_a?(Request)
end

#send_raw(json_string) ⇒ Object



32
33
34
# File 'lib/spoom/sorbet/lsp.rb', line 32

def send_raw(json_string)
  @in.puts("Content-Length:#{json_string.length}\r\n\r\n#{json_string}")
end

#signatures(uri, line, column) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/spoom/sorbet/lsp.rb', line 110

def signatures(uri, line, column)
  json = send(Request.new(
    next_id,
    "textDocument/signatureHelp",
    {
      "textDocument" => {
        "uri" => uri,
      },
      "position" => {
        "line" => line,
        "character" => column,
      },
    },
  ))

  return [] unless json && json["result"] && json["result"]["signatures"]

  json["result"]["signatures"].map { |loc| SignatureHelp.from_json(loc) }
end

#symbols(query) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/spoom/sorbet/lsp.rb', line 197

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

#type_definitions(uri, line, column) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/spoom/sorbet/lsp.rb', line 152

def type_definitions(uri, line, column)
  json = send(Request.new(
    next_id,
    "textDocument/typeDefinition",
    {
      "textDocument" => {
        "uri" => uri,
      },
      "position" => {
        "line" => line,
        "character" => column,
      },
    },
  ))

  return [] unless json && json["result"]

  json["result"].map { |loc| Location.from_json(loc) }
end