spoom 1.0.1 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,196 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'open3'
5
+ require 'json'
6
+
7
+ require_relative 'lsp/base'
8
+ require_relative 'lsp/structures'
9
+ require_relative 'lsp/errors'
10
+
11
+ module Spoom
12
+ module LSP
13
+ class Client
14
+ def initialize(sorbet_cmd, *sorbet_args, path: ".")
15
+ @id = 0
16
+ Bundler.with_clean_env do
17
+ opts = {}
18
+ opts[:chdir] = path
19
+ @in, @out, @err, @status = Open3.popen3([sorbet_cmd, *sorbet_args].join(" "), opts)
20
+ end
21
+ end
22
+
23
+ def next_id
24
+ @id += 1
25
+ end
26
+
27
+ def send_raw(json_string)
28
+ @in.puts("Content-Length:#{json_string.length}\r\n\r\n#{json_string}")
29
+ end
30
+
31
+ def send(message)
32
+ send_raw(message.to_json)
33
+ read if message.is_a?(Request)
34
+ end
35
+
36
+ def read_raw
37
+ header = @out.gets
38
+
39
+ # Sorbet returned an error and forgot to answer
40
+ raise Error::BadHeaders, "bad response headers" unless header&.match?(/Content-Length: /)
41
+
42
+ len = header.slice(::Range.new(16, nil)).to_i
43
+ @out.read(len + 2) # +2 'cause of the final \r\n
44
+ end
45
+
46
+ def read
47
+ json = JSON.parse(read_raw)
48
+
49
+ # Handle error in the LSP protocol
50
+ raise ResponseError.from_json(json['error']) if json['error']
51
+
52
+ # Handle typechecking errors
53
+ raise Error::Diagnostics.from_json(json['params']) if json['method'] == "textDocument/publishDiagnostics"
54
+
55
+ json
56
+ end
57
+
58
+ # LSP requests
59
+
60
+ def open(workspace_path)
61
+ raise Error::AlreadyOpen, "Error: CLI already opened" if @open
62
+ send(Request.new(
63
+ next_id,
64
+ 'initialize',
65
+ {
66
+ 'rootPath' => workspace_path,
67
+ 'rootUri' => "file://#{workspace_path}",
68
+ 'capabilities' => {},
69
+ },
70
+ ))
71
+ send(Notification.new('initialized', {}))
72
+ @open = true
73
+ end
74
+
75
+ def hover(uri, line, column)
76
+ json = send(Request.new(
77
+ next_id,
78
+ 'textDocument/hover',
79
+ {
80
+ 'textDocument' => {
81
+ 'uri' => uri,
82
+ },
83
+ 'position' => {
84
+ 'line' => line,
85
+ 'character' => column,
86
+ },
87
+ }
88
+ ))
89
+ return nil unless json['result']
90
+ Hover.from_json(json['result'])
91
+ end
92
+
93
+ def signatures(uri, line, column)
94
+ json = send(Request.new(
95
+ next_id,
96
+ 'textDocument/signatureHelp',
97
+ {
98
+ 'textDocument' => {
99
+ 'uri' => uri,
100
+ },
101
+ 'position' => {
102
+ 'line' => line,
103
+ 'character' => column,
104
+ },
105
+ }
106
+ ))
107
+ json['result']['signatures'].map { |loc| SignatureHelp.from_json(loc) }
108
+ end
109
+
110
+ def definitions(uri, line, column)
111
+ json = send(Request.new(
112
+ next_id,
113
+ 'textDocument/definition',
114
+ {
115
+ 'textDocument' => {
116
+ 'uri' => uri,
117
+ },
118
+ 'position' => {
119
+ 'line' => line,
120
+ 'character' => column,
121
+ },
122
+ }
123
+ ))
124
+ json['result'].map { |loc| Location.from_json(loc) }
125
+ end
126
+
127
+ def type_definitions(uri, line, column)
128
+ json = send(Request.new(
129
+ next_id,
130
+ 'textDocument/typeDefinition',
131
+ {
132
+ 'textDocument' => {
133
+ 'uri' => uri,
134
+ },
135
+ 'position' => {
136
+ 'line' => line,
137
+ 'character' => column,
138
+ },
139
+ }
140
+ ))
141
+ json['result'].map { |loc| Location.from_json(loc) }
142
+ end
143
+
144
+ def references(uri, line, column, include_decl = true)
145
+ json = send(Request.new(
146
+ next_id,
147
+ 'textDocument/references',
148
+ {
149
+ 'textDocument' => {
150
+ 'uri' => uri,
151
+ },
152
+ 'position' => {
153
+ 'line' => line,
154
+ 'character' => column,
155
+ },
156
+ 'context' => {
157
+ 'includeDeclaration' => include_decl,
158
+ },
159
+ }
160
+ ))
161
+ json['result'].map { |loc| Location.from_json(loc) }
162
+ end
163
+
164
+ def symbols(query)
165
+ json = send(Request.new(
166
+ next_id,
167
+ 'workspace/symbol',
168
+ {
169
+ 'query' => query,
170
+ }
171
+ ))
172
+ json['result'].map { |loc| DocumentSymbol.from_json(loc) }
173
+ end
174
+
175
+ def document_symbols(uri)
176
+ json = send(Request.new(
177
+ next_id,
178
+ 'textDocument/documentSymbol',
179
+ {
180
+ 'textDocument' => {
181
+ 'uri' => uri,
182
+ },
183
+ }
184
+ ))
185
+ json['result'].map { |loc| DocumentSymbol.from_json(loc) }
186
+ end
187
+
188
+ def close
189
+ send(Request.new(next_id, "shutdown", nil))
190
+ @in.close
191
+ @out.close
192
+ @err.close
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,58 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module LSP
6
+ # Base messaging
7
+ # We don't use T::Struct for those so we can subclass them
8
+
9
+ # A general message as defined by JSON-RPC.
10
+ #
11
+ # The language server protocol always uses `"2.0"` as the `jsonrpc` version.
12
+ class Message
13
+ attr_reader :jsonrpc
14
+
15
+ def initialize
16
+ @jsonrpc = '2.0'
17
+ end
18
+
19
+ def as_json
20
+ instance_variables.each_with_object({}) do |var, obj|
21
+ val = instance_variable_get(var)
22
+ obj[var.to_s.delete('@')] = val if val
23
+ end
24
+ end
25
+
26
+ def to_json(*args)
27
+ as_json.to_json(*args)
28
+ end
29
+ end
30
+
31
+ # A request message to describe a request between the client and the server.
32
+ #
33
+ # Every processed request must send a response back to the sender of the request.
34
+ class Request < Message
35
+ attr_reader :id, :method, :params
36
+
37
+ def initialize(id, method, params)
38
+ super()
39
+ @id = id
40
+ @method = method
41
+ @params = params
42
+ end
43
+ end
44
+
45
+ # A notification message.
46
+ #
47
+ # A processed notification message must not send a response back. They work like events.
48
+ class Notification < Message
49
+ attr_reader :method, :params
50
+
51
+ def initialize(method, params)
52
+ super()
53
+ @method = method
54
+ @params = params
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,45 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module LSP
6
+ class Error < StandardError
7
+ class AlreadyOpen < Error; end
8
+ class BadHeaders < Error; end
9
+
10
+ class Diagnostics < Error
11
+ attr_reader :uri, :diagnostics
12
+
13
+ def self.from_json(json)
14
+ Diagnostics.new(
15
+ json['uri'],
16
+ json['diagnostics'].map { |d| Diagnostic.from_json(d) }
17
+ )
18
+ end
19
+
20
+ def initialize(uri, diagnostics)
21
+ @uri = uri
22
+ @diagnostics = diagnostics
23
+ end
24
+ end
25
+ end
26
+
27
+ class ResponseError < Error
28
+ attr_reader :code, :message, :data
29
+
30
+ def self.from_json(json)
31
+ ResponseError.new(
32
+ json['code'],
33
+ json['message'],
34
+ json['data']
35
+ )
36
+ end
37
+
38
+ def initialize(code, message, data)
39
+ @code = code
40
+ @message = message
41
+ @data = data
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,312 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../../printer"
5
+
6
+ module Spoom
7
+ module LSP
8
+ module PrintableSymbol
9
+ extend T::Sig
10
+ extend T::Helpers
11
+
12
+ interface!
13
+
14
+ sig { abstract.params(printer: SymbolPrinter).void }
15
+ def accept_printer(printer); end
16
+ end
17
+
18
+ class Hover < T::Struct
19
+ extend T::Sig
20
+ include PrintableSymbol
21
+
22
+ const :contents, String
23
+ const :range, T.nilable(Range)
24
+
25
+ def self.from_json(json)
26
+ Hover.new(
27
+ contents: json['contents']['value'],
28
+ range: json['range'] ? Range.from_json(json['range']) : nil
29
+ )
30
+ end
31
+
32
+ sig { override.params(printer: SymbolPrinter).void }
33
+ def accept_printer(printer)
34
+ printer.print("#{contents}\n")
35
+ printer.print_object(range) if range
36
+ end
37
+
38
+ def to_s
39
+ "#{contents} (#{range})."
40
+ end
41
+ end
42
+
43
+ class Position < T::Struct
44
+ extend T::Sig
45
+ include PrintableSymbol
46
+
47
+ const :line, Integer
48
+ const :char, Integer
49
+
50
+ def self.from_json(json)
51
+ Position.new(
52
+ line: json['line'].to_i,
53
+ char: json['character'].to_i
54
+ )
55
+ end
56
+
57
+ sig { override.params(printer: SymbolPrinter).void }
58
+ def accept_printer(printer)
59
+ printer.print_colored("#{line}:#{char}", :light_black)
60
+ end
61
+
62
+ def to_s
63
+ "#{line}:#{char}"
64
+ end
65
+ end
66
+
67
+ class Range < T::Struct
68
+ extend T::Sig
69
+ include PrintableSymbol
70
+
71
+ const :start, Position
72
+ const :end, Position
73
+
74
+ def self.from_json(json)
75
+ Range.new(
76
+ start: Position.from_json(json['start']),
77
+ end: Position.from_json(json['end'])
78
+ )
79
+ end
80
+
81
+ sig { override.params(printer: SymbolPrinter).void }
82
+ def accept_printer(printer)
83
+ printer.print_object(start)
84
+ printer.print_colored("-", :light_black)
85
+ printer.print_object(self.end)
86
+ end
87
+
88
+ def to_s
89
+ "#{start}-#{self.end}"
90
+ end
91
+ end
92
+
93
+ class Location < T::Struct
94
+ extend T::Sig
95
+ include PrintableSymbol
96
+
97
+ const :uri, String
98
+ const :range, LSP::Range
99
+
100
+ def self.from_json(json)
101
+ Location.new(
102
+ uri: json['uri'],
103
+ range: Range.from_json(json['range'])
104
+ )
105
+ end
106
+
107
+ sig { override.params(printer: SymbolPrinter).void }
108
+ def accept_printer(printer)
109
+ printer.print_colored("#{printer.clean_uri(uri)}:", :light_black)
110
+ printer.print_object(range)
111
+ end
112
+
113
+ def to_s
114
+ "#{uri}:#{range}"
115
+ end
116
+ end
117
+
118
+ class SignatureHelp < T::Struct
119
+ extend T::Sig
120
+ include PrintableSymbol
121
+
122
+ const :label, T.nilable(String)
123
+ const :doc, Object # TODO
124
+ const :params, T::Array[T.untyped] # TODO
125
+
126
+ def self.from_json(json)
127
+ SignatureHelp.new(
128
+ label: json['label'],
129
+ doc: json['documentation'],
130
+ params: json['parameters'],
131
+ )
132
+ end
133
+
134
+ sig { override.params(printer: SymbolPrinter).void }
135
+ def accept_printer(printer)
136
+ printer.print(label)
137
+ printer.print("(")
138
+ printer.print(params.map { |l| "#{l['label']}: #{l['documentation']}" }.join(", "))
139
+ printer.print(")")
140
+ end
141
+
142
+ def to_s
143
+ "#{label}(#{params})."
144
+ end
145
+ end
146
+
147
+ class Diagnostic < T::Struct
148
+ extend T::Sig
149
+ include PrintableSymbol
150
+
151
+ const :range, LSP::Range
152
+ const :code, Integer
153
+ const :message, String
154
+ const :informations, Object
155
+
156
+ def self.from_json(json)
157
+ Diagnostic.new(
158
+ range: Range.from_json(json['range']),
159
+ code: json['code'].to_i,
160
+ message: json['message'],
161
+ informations: json['relatedInformation']
162
+ )
163
+ end
164
+
165
+ sig { override.params(printer: SymbolPrinter).void }
166
+ def accept_printer(printer)
167
+ printer.print(to_s)
168
+ end
169
+
170
+ def to_s
171
+ "Error: #{message} (#{code})."
172
+ end
173
+ end
174
+
175
+ class DocumentSymbol < T::Struct
176
+ extend T::Sig
177
+ include PrintableSymbol
178
+
179
+ const :name, String
180
+ const :detail, T.nilable(String)
181
+ const :kind, Integer
182
+ const :location, T.nilable(Location)
183
+ const :range, T.nilable(Range)
184
+ const :children, T::Array[DocumentSymbol]
185
+
186
+ def self.from_json(json)
187
+ DocumentSymbol.new(
188
+ name: json['name'],
189
+ detail: json['detail'],
190
+ kind: json['kind'],
191
+ location: json['location'] ? Location.from_json(json['location']) : nil,
192
+ range: json['range'] ? Range.from_json(json['range']) : nil,
193
+ children: json['children'] ? json['children'].map { |symbol| DocumentSymbol.from_json(symbol) } : [],
194
+ )
195
+ end
196
+
197
+ sig { override.params(printer: SymbolPrinter).void }
198
+ def accept_printer(printer)
199
+ h = serialize.hash
200
+ return if printer.seen.include?(h)
201
+ printer.seen.add(h)
202
+
203
+ printer.printt
204
+ printer.print(kind_string)
205
+ printer.print(' ')
206
+ printer.print_colored(name, :blue, :bold)
207
+ printer.print_colored(' (', :light_black)
208
+ if range
209
+ printer.print_object(range)
210
+ elsif location
211
+ printer.print_object(location)
212
+ end
213
+ printer.print_colored(')', :light_black)
214
+ printer.printn
215
+ unless children.empty?
216
+ printer.indent
217
+ printer.print_objects(children)
218
+ printer.dedent
219
+ end
220
+ # TODO: also display details?
221
+ end
222
+
223
+ def to_s
224
+ "#{name} (#{range})"
225
+ end
226
+
227
+ def kind_string
228
+ return "<unknown:#{kind}>" unless SYMBOL_KINDS.key?(kind)
229
+ SYMBOL_KINDS[kind]
230
+ end
231
+
232
+ SYMBOL_KINDS = {
233
+ 1 => "file",
234
+ 2 => "module",
235
+ 3 => "namespace",
236
+ 4 => "package",
237
+ 5 => "class",
238
+ 6 => "def",
239
+ 7 => "property",
240
+ 8 => "field",
241
+ 9 => "constructor",
242
+ 10 => "enum",
243
+ 11 => "interface",
244
+ 12 => "function",
245
+ 13 => "variable",
246
+ 14 => "const",
247
+ 15 => "string",
248
+ 16 => "number",
249
+ 17 => "boolean",
250
+ 18 => "array",
251
+ 19 => "object",
252
+ 20 => "key",
253
+ 21 => "null",
254
+ 22 => "enum_member",
255
+ 23 => "struct",
256
+ 24 => "event",
257
+ 25 => "operator",
258
+ 26 => "type_parameter",
259
+ }
260
+ end
261
+
262
+ class SymbolPrinter < Printer
263
+ extend T::Sig
264
+
265
+ attr_accessor :seen, :prefix
266
+
267
+ sig do
268
+ params(
269
+ out: T.any(IO, StringIO),
270
+ colors: T::Boolean,
271
+ indent_level: Integer,
272
+ prefix: T.nilable(String)
273
+ ).void
274
+ end
275
+ def initialize(out: $stdout, colors: true, indent_level: 0, prefix: nil)
276
+ super(out: out, colors: colors, indent_level: indent_level)
277
+ @seen = Set.new
278
+ @out = out
279
+ @colors = colors
280
+ @indent_level = indent_level
281
+ @prefix = prefix
282
+ end
283
+
284
+ sig { params(object: T.nilable(PrintableSymbol)).void }
285
+ def print_object(object)
286
+ return unless object
287
+ object.accept_printer(self)
288
+ end
289
+
290
+ sig { params(objects: T::Array[PrintableSymbol]).void }
291
+ def print_objects(objects)
292
+ objects.each { |object| print_object(object) }
293
+ end
294
+
295
+ sig { params(uri: String).returns(String) }
296
+ def clean_uri(uri)
297
+ return uri unless prefix
298
+ uri.delete_prefix(prefix)
299
+ end
300
+
301
+ sig { params(objects: T::Array[PrintableSymbol]).void }
302
+ def print_list(objects)
303
+ objects.each do |object|
304
+ printt
305
+ print "* "
306
+ print_object(object)
307
+ printn
308
+ end
309
+ end
310
+ end
311
+ end
312
+ end