typeprof 0.21.11 → 0.30.1

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -31
  3. data/bin/typeprof +5 -0
  4. data/doc/doc.ja.md +134 -0
  5. data/doc/doc.md +136 -0
  6. data/lib/typeprof/cli/cli.rb +178 -0
  7. data/lib/typeprof/cli.rb +3 -133
  8. data/lib/typeprof/code_range.rb +112 -0
  9. data/lib/typeprof/core/ast/base.rb +263 -0
  10. data/lib/typeprof/core/ast/call.rb +259 -0
  11. data/lib/typeprof/core/ast/const.rb +126 -0
  12. data/lib/typeprof/core/ast/control.rb +433 -0
  13. data/lib/typeprof/core/ast/meta.rb +150 -0
  14. data/lib/typeprof/core/ast/method.rb +339 -0
  15. data/lib/typeprof/core/ast/misc.rb +263 -0
  16. data/lib/typeprof/core/ast/module.rb +123 -0
  17. data/lib/typeprof/core/ast/pattern.rb +140 -0
  18. data/lib/typeprof/core/ast/sig_decl.rb +471 -0
  19. data/lib/typeprof/core/ast/sig_type.rb +663 -0
  20. data/lib/typeprof/core/ast/value.rb +319 -0
  21. data/lib/typeprof/core/ast/variable.rb +315 -0
  22. data/lib/typeprof/core/ast.rb +472 -0
  23. data/lib/typeprof/core/builtin.rb +146 -0
  24. data/lib/typeprof/core/env/method.rb +137 -0
  25. data/lib/typeprof/core/env/method_entity.rb +55 -0
  26. data/lib/typeprof/core/env/module_entity.rb +408 -0
  27. data/lib/typeprof/core/env/static_read.rb +155 -0
  28. data/lib/typeprof/core/env/type_alias_entity.rb +27 -0
  29. data/lib/typeprof/core/env/value_entity.rb +32 -0
  30. data/lib/typeprof/core/env.rb +366 -0
  31. data/lib/typeprof/core/graph/box.rb +998 -0
  32. data/lib/typeprof/core/graph/change_set.rb +224 -0
  33. data/lib/typeprof/core/graph/filter.rb +155 -0
  34. data/lib/typeprof/core/graph/vertex.rb +225 -0
  35. data/lib/typeprof/core/service.rb +514 -0
  36. data/lib/typeprof/core/type.rb +352 -0
  37. data/lib/typeprof/core/util.rb +81 -0
  38. data/lib/typeprof/core.rb +31 -0
  39. data/lib/typeprof/diagnostic.rb +35 -0
  40. data/lib/typeprof/lsp/messages.rb +415 -0
  41. data/lib/typeprof/lsp/server.rb +203 -0
  42. data/lib/typeprof/lsp/text.rb +69 -0
  43. data/lib/typeprof/lsp/util.rb +51 -0
  44. data/lib/typeprof/lsp.rb +4 -907
  45. data/lib/typeprof/version.rb +1 -1
  46. data/lib/typeprof.rb +4 -18
  47. data/typeprof.gemspec +5 -7
  48. metadata +47 -33
  49. data/.github/dependabot.yml +0 -6
  50. data/.github/workflows/main.yml +0 -39
  51. data/.gitignore +0 -9
  52. data/Gemfile +0 -17
  53. data/Gemfile.lock +0 -41
  54. data/Rakefile +0 -10
  55. data/exe/typeprof +0 -10
  56. data/lib/typeprof/analyzer.rb +0 -2598
  57. data/lib/typeprof/arguments.rb +0 -414
  58. data/lib/typeprof/block.rb +0 -176
  59. data/lib/typeprof/builtin.rb +0 -893
  60. data/lib/typeprof/code-range.rb +0 -177
  61. data/lib/typeprof/config.rb +0 -158
  62. data/lib/typeprof/container-type.rb +0 -912
  63. data/lib/typeprof/export.rb +0 -589
  64. data/lib/typeprof/import.rb +0 -852
  65. data/lib/typeprof/insns-def.rb +0 -65
  66. data/lib/typeprof/iseq.rb +0 -864
  67. data/lib/typeprof/method.rb +0 -355
  68. data/lib/typeprof/type.rb +0 -1140
  69. data/lib/typeprof/utils.rb +0 -212
  70. data/tools/coverage.rb +0 -14
  71. data/tools/setup-insns-def.rb +0 -30
  72. data/typeprof-lsp +0 -3
data/lib/typeprof/lsp.rb CHANGED
@@ -1,910 +1,7 @@
1
1
  require "socket"
2
2
  require "json"
3
- require "uri"
4
3
 
5
- module TypeProf
6
- def self.start_lsp_server(config)
7
- if config.lsp_options[:stdio]
8
- $stdin.binmode
9
- $stdout.binmode
10
- reader = LSP::Reader.new($stdin)
11
- writer = LSP::Writer.new($stdout)
12
- # pipe all builtin print output to stderr to avoid conflicting with lsp
13
- $stdout = $stderr
14
- TypeProf::LSP::Server.new(config, reader, writer).run
15
- else
16
- Socket.tcp_server_sockets("localhost", config.lsp_options[:port]) do |servs|
17
- serv = servs[0].local_address
18
- $stdout << JSON.generate({
19
- host: serv.ip_address,
20
- port: serv.ip_port,
21
- pid: $$,
22
- })
23
- $stdout.flush
24
-
25
- $stdout = $stderr
26
-
27
- Socket.accept_loop(servs) do |sock|
28
- sock.set_encoding("UTF-8")
29
- begin
30
- reader = LSP::Reader.new(sock)
31
- writer = LSP::Writer.new(sock)
32
- TypeProf::LSP::Server.new(config, reader, writer).run
33
- ensure
34
- sock.close
35
- end
36
- exit
37
- end
38
- end
39
- end
40
- end
41
-
42
- module LSP
43
- CompletionSession = Struct.new(:results, :row, :start_col_offset)
44
- class CompletionSession
45
- def reusable?(other_row, other_start_col_offset)
46
- other_row == self.row && other_start_col_offset == self.start_col_offset
47
- end
48
- end
49
-
50
- class Text
51
- class AnalysisToken < Utils::CancelToken
52
- def initialize
53
- @timer = Utils::TimerCancelToken.new(1)
54
- @cancelled = false
55
- end
56
-
57
- def cancel
58
- @cancelled = true
59
- end
60
-
61
- def cancelled?
62
- @timer.cancelled? || @cancelled
63
- end
64
- end
65
-
66
- def initialize(server, uri, text, version)
67
- @server = server
68
- @uri = uri
69
- @text = text
70
- @version = version
71
- @sigs = nil
72
-
73
- @last_analysis_cancel_token = nil
74
- @analysis_queue = Queue.new
75
- @analysis_thread = Thread.new do
76
- loop do
77
- work = @analysis_queue.pop
78
- begin
79
- work.call
80
- rescue Exception
81
- puts "Rescued exception:"
82
- puts $!.full_message
83
- puts
84
- end
85
- end
86
- end
87
-
88
- # analyze synchronously to respond the first codeLens request
89
- res, def_table, caller_table = self.analyze(uri, text)
90
- on_text_changed_analysis(res, def_table, caller_table)
91
- end
92
-
93
- attr_reader :text, :version, :sigs, :caller_table
94
- attr_accessor :definition_table
95
-
96
- def lines
97
- @text.lines
98
- end
99
-
100
- def apply_changes(changes, version)
101
- @definition_table = nil
102
- text = @text.empty? ? [] : @text.lines
103
- changes.each do |change|
104
- case change
105
- in {
106
- range: {
107
- start: { line: start_row, character: start_col },
108
- end: { line: end_row , character: end_col }
109
- },
110
- text: change_text,
111
- }
112
- else
113
- raise
114
- end
115
- text << "" if start_row == text.size
116
- text << "" if end_row == text.size
117
- if start_row == end_row
118
- text[start_row][start_col...end_col] = change_text
119
- else
120
- text[start_row][start_col..] = ""
121
- text[end_row][...end_col] = ""
122
- change_text = change_text.lines
123
- case change_text.size
124
- when 0
125
- text[start_row] += text[end_row]
126
- text[start_row + 1 .. end_row] = []
127
- when 1
128
- text[start_row] += change_text.first + text[end_row]
129
- text[start_row + 1 .. end_row] = []
130
- else
131
- text[start_row] += change_text.shift
132
- text[end_row].prepend(change_text.pop)
133
- text[start_row + 1 ... end_row - 1] = change_text
134
- end
135
- end
136
- end
137
- @text = text.join
138
- @version = version
139
-
140
- on_text_changed
141
- end
142
-
143
- def new_code_completion_session(row, start_offset, end_offset)
144
- lines = @text.lines
145
- lines[row][start_offset, end_offset] = ".__typeprof_lsp_completion"
146
- tmp_text = lines.join
147
- res, = analyze(@uri, tmp_text)
148
- if res && res[:completion]
149
- results = res[:completion].keys.map do |name|
150
- {
151
- label: name,
152
- kind: 2, # Method
153
- }
154
- end
155
- return CompletionSession.new(results, row, start_offset)
156
- else
157
- nil
158
- end
159
- end
160
-
161
- def code_complete(loc, trigger_kind)
162
- case loc
163
- in { line: row, character: col }
164
- end
165
- unless row < @text.lines.length && col >= 1 && @text.lines[row][0, col] =~ /\.\w*$/
166
- return nil
167
- end
168
- start_offset = $~.begin(0)
169
- end_offset = $&.size
170
-
171
- case trigger_kind
172
- when LSP::CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS
173
- unless @current_completion_session&.reusable?(row, start_offset)
174
- puts "no reusable completion session but got TRIGGER_FOR_INCOMPLETE_COMPLETIONS"
175
- @current_completion_session = new_code_completion_session(row, start_offset, end_offset)
176
- end
177
- return @current_completion_session.results
178
- else
179
- @current_completion_session = new_code_completion_session(row, start_offset, end_offset)
180
- return @current_completion_session&.results
181
- end
182
- end
183
-
184
- private def locate_arg_index_in_signature_help(node, loc, sig_help)
185
- case node.type
186
- when :FCALL
187
- _mid, args_node = node.children
188
- when :CALL
189
- _recv, _mid, args_node = node.children
190
- end
191
-
192
- idx = 0
193
-
194
- if args_node
195
- arg_nodes = args_node.children.compact
196
-
197
- arg_indexes = {}
198
- hash = arg_nodes.pop if arg_nodes.last&.type == :HASH
199
-
200
- arg_nodes.each_with_index do |node, i|
201
- # Ingore arguments after rest argument
202
- break if node.type == :LIST || node.type == :ARGSCAT
203
-
204
- arg_indexes[i] = ISeq.code_range_from_node(node)
205
- end
206
-
207
- # Handle keyword arguments
208
- if hash
209
- hash.children.last.children.compact.each_slice(2) do |node1, node2|
210
- # key: expression
211
- # ^^^^ ^^^^^^^^^^
212
- # node1 node2
213
- key = node1.children.first
214
- arg_indexes[key] =
215
- CodeRange.new(
216
- CodeLocation.new(node1.first_lineno, node1.first_lineno),
217
- CodeLocation.new(node2.last_lineno, node2.last_lineno),
218
- )
219
- end
220
- end
221
-
222
- if arg_indexes.size >= 1 && arg_indexes.values.last.last < loc
223
- # There is the cursor after the last argument: "foo(111, 222,|)"
224
- idx = arg_indexes.size - 1
225
- prev_cr = arg_indexes.values.last
226
- if prev_cr.last.lineno == loc.lineno
227
- line = @text.lines[prev_cr.last.lineno - 1]
228
- idx += 1 if line[prev_cr.last.column..loc.column].include?(",")
229
- end
230
- else
231
- # There is the cursor within any argument: "foo(111,|222)" or foo(111, 22|2)"
232
- prev_cr = nil
233
- arg_indexes.each do |i, cr|
234
- idx = sig_help.keys.index(i)
235
- if loc < cr.first
236
- break if !prev_cr || prev_cr.last.lineno != loc.lineno
237
- line = @text.lines[prev_cr.last.lineno - 1]
238
- idx -= 1 unless line[prev_cr.last.column..loc.column].include?(",")
239
- break
240
- end
241
- break if loc <= cr.last
242
- prev_cr = cr
243
- end
244
- end
245
- end
246
-
247
- idx
248
- end
249
-
250
- def signature_help(loc, trigger_kind)
251
- loc = CodeLocation.from_lsp(loc)
252
-
253
- res, = analyze(@uri, @text, signature_help_loc: loc)
254
-
255
- if res
256
- res[:signature_help].filter_map do |sig_str, sig_help, node_id|
257
- node = ISeq.find_node_by_id(@text, node_id)
258
- if node && ISeq.code_range_from_node(node).contain_loc?(loc)
259
- idx = locate_arg_index_in_signature_help(node, loc, sig_help)
260
-
261
- {
262
- label: sig_str,
263
- parameters: sig_help.values.map do |r|
264
- {
265
- label: [r.begin, r.end],
266
- }
267
- end,
268
- activeParameter: idx,
269
- }
270
- end
271
- end
272
- else
273
- nil
274
- end
275
- end
276
-
277
- def analyze(uri, text, cancel_token: nil, signature_help_loc: nil)
278
- config = @server.typeprof_config.dup
279
- path = URI(uri).path
280
- config.rb_files = [[path, text]]
281
- config.rbs_files = ["typeprof.rbs"] # XXX
282
- config.verbose = 0
283
- config.max_sec = 1
284
- config.options[:show_errors] = true
285
- config.options[:show_indicator] = false
286
- config.options[:lsp] = true
287
- config.options[:signature_help_loc] = [path, signature_help_loc] if signature_help_loc
288
-
289
- TypeProf.analyze(config, cancel_token)
290
- rescue SyntaxError
291
- end
292
-
293
- def push_analysis_queue(&work)
294
- @analysis_queue.push(work)
295
- end
296
-
297
- def on_text_changed
298
- cancel_token = AnalysisToken.new
299
- @last_analysis_cancel_token&.cancel
300
- @last_analysis_cancel_token = cancel_token
301
-
302
- uri = @uri
303
- text = @text
304
- self.push_analysis_queue do
305
- if cancel_token.cancelled?
306
- next
307
- end
308
- res, def_table, caller_table = self.analyze(uri, text, cancel_token: cancel_token)
309
- unless cancel_token.cancelled?
310
- on_text_changed_analysis(res, def_table, caller_table)
311
- end
312
- end
313
- end
314
-
315
- def on_text_changed_analysis(res, definition_table, caller_table)
316
- @definition_table = definition_table
317
- @caller_table = caller_table
318
- return unless res
319
-
320
- @sigs = []
321
- res[:sigs].each do |file, lineno, sig_str, rbs_code_range, class_kind, class_name|
322
- uri0 = "file://" + file
323
- if @uri == uri0
324
- command = { title: sig_str }
325
- if rbs_code_range
326
- command[:command] = "typeprof.jumpToRBS"
327
- command[:arguments] = [uri0, { line: lineno - 1, character: 0 }, @server.root_uri + "/" + rbs_code_range[0], rbs_code_range[1].to_lsp]
328
- else
329
- command[:command] = "typeprof.createPrototypeRBS"
330
- command[:arguments] = [class_kind, class_name, sig_str]
331
- end
332
- @sigs << {
333
- range: {
334
- start: { line: lineno - 1, character: 0 },
335
- end: { line: lineno - 1, character: 1 },
336
- },
337
- command: command,
338
- }
339
- end
340
- end
341
-
342
- diagnostics = {}
343
- res[:errors]&.each do |(file, code_range), msg|
344
- next unless file and code_range
345
- uri0 = "file://" + file
346
- diagnostics[uri0] ||= []
347
- diagnostics[uri0] << {
348
- range: code_range.to_lsp,
349
- severity: 1,
350
- source: "TypeProf",
351
- message: msg,
352
- }
353
- end
354
-
355
- @server.send_notification('typeprof.enableToggleButton')
356
- @server.send_request("workspace/codeLens/refresh")
357
-
358
- @server.send_notification(
359
- "textDocument/publishDiagnostics",
360
- {
361
- uri: @uri,
362
- version: version,
363
- diagnostics: diagnostics[@uri] || [],
364
- }
365
- )
366
- end
367
- end
368
-
369
- class Message
370
- def initialize(server, json)
371
- @server = server
372
- @id = json[:id]
373
- @method = json[:method]
374
- @params = json[:params]
375
- end
376
-
377
- def run
378
- p [:ignored, @method]
379
- end
380
-
381
- def respond(result)
382
- raise "do not respond to notification" if @id == nil
383
- @server.send_response(id: @id, result: result)
384
- end
385
-
386
- def respond_error(error)
387
- raise "do not respond to notification" if @id == nil
388
- @server.send_response(id: @id, error: error)
389
- end
390
-
391
- Classes = []
392
- def self.inherited(klass)
393
- Classes << klass
394
- end
395
-
396
- Table = Hash.new(Message)
397
- def self.build_table
398
- Classes.each {|klass| Table[klass::METHOD] = klass }
399
- end
400
-
401
- def self.find(method)
402
- Table[method]
403
- end
404
- end
405
-
406
- module ErrorCodes
407
- ParseError = -32700
408
- InvalidRequest = -32600
409
- MethodNotFound = -32601
410
- InvalidParams = -32602
411
- InternalError = -32603
412
- end
413
-
414
- class Message::Initialize < Message
415
- METHOD = "initialize"
416
- def run
417
- @server.root_uri = @params[:rootUri]
418
- pwd = Dir.pwd
419
- @params[:workspaceFolders]&.each do |folder|
420
- folder => { uri:, }
421
- if pwd == URI(uri).path
422
- @server.root_uri = uri
423
- end
424
- end
425
-
426
- respond(
427
- capabilities: {
428
- textDocumentSync: {
429
- openClose: true,
430
- change: 2, # Incremental
431
- },
432
- completionProvider: {
433
- triggerCharacters: ["."],
434
- },
435
- signatureHelpProvider: {
436
- triggerCharacters: ["(", ","],
437
- },
438
- #codeActionProvider: {
439
- # codeActionKinds: ["quickfix", "refactor"],
440
- # resolveProvider: false,
441
- #},
442
- codeLensProvider: {
443
- resolveProvider: true,
444
- },
445
- executeCommandProvider: {
446
- commands: [
447
- "typeprof.createPrototypeRBS",
448
- "typeprof.enableSignature",
449
- "typeprof.disableSignature",
450
- ],
451
- },
452
- definitionProvider: true,
453
- typeDefinitionProvider: true,
454
- referencesProvider: true,
455
- },
456
- serverInfo: {
457
- name: "typeprof",
458
- version: "0.0.0",
459
- },
460
- )
461
-
462
- puts "TypeProf for IDE is started successfully"
463
- end
464
- end
465
-
466
- class Message::Initialized < Message
467
- METHOD = "initialized"
468
- def run
469
- end
470
- end
471
-
472
- class Message::Shutdown < Message
473
- METHOD = "shutdown"
474
- def run
475
- respond(nil)
476
- end
477
- end
478
-
479
- class Message::Exit < Message
480
- METHOD = "exit"
481
- def run
482
- exit
483
- end
484
- end
485
-
486
- module Message::Workspace
487
- end
488
-
489
- class Message::Workspace::DidChangeWatchedFiles < Message
490
- METHOD = "workspace/didChangeWatchedFiles"
491
- def run
492
- #p "workspace/didChangeWatchedFiles"
493
- #pp @params
494
- end
495
- end
496
-
497
- class Message::Workspace::ExecuteCommand < Message
498
- METHOD = "workspace/executeCommand"
499
- def run
500
- case @params[:command]
501
- when "typeprof.enableSignature"
502
- @server.signature_enabled = true
503
- @server.send_request("workspace/codeLens/refresh")
504
- respond(nil)
505
- when "typeprof.disableSignature"
506
- @server.signature_enabled = false
507
- @server.send_request("workspace/codeLens/refresh")
508
- respond(nil)
509
- when "typeprof.createPrototypeRBS"
510
- class_kind, class_name, sig_str = @params[:arguments]
511
- code_range =
512
- CodeRange.new(
513
- CodeLocation.new(1, 0),
514
- CodeLocation.new(1, 0),
515
- )
516
- text = []
517
- text << "#{ class_kind } #{ class_name.join("::") }\n"
518
- text << " #{ sig_str }\n"
519
- text << "end\n\n"
520
- text = text.join
521
- @server.send_request(
522
- "workspace/applyEdit",
523
- edit: {
524
- changes: {
525
- @server.root_uri + "/typeprof.rbs" => [
526
- {
527
- range: code_range.to_lsp,
528
- newText: text,
529
- }
530
- ],
531
- },
532
- },
533
- ) do |res|
534
- code_range =
535
- CodeRange.new(
536
- CodeLocation.new(1, 0),
537
- CodeLocation.new(3, 3), # 3 = "end".size
538
- )
539
- @server.send_request(
540
- "window/showDocument",
541
- uri: @server.root_uri + "/typeprof.rbs",
542
- takeFocus: true,
543
- selection: code_range.to_lsp,
544
- )
545
- end
546
- respond(nil)
547
- else
548
- respond_error(
549
- code: ErrorCodes::InvalidRequest,
550
- message: "Unknown command: #{ @params[:command] }",
551
- )
552
- end
553
- end
554
- end
555
-
556
- module Message::TextDocument
557
- end
558
-
559
- class Message::TextDocument::DidOpen < Message
560
- METHOD = "textDocument/didOpen"
561
- def run
562
- case @params
563
- in { textDocument: { uri:, version:, text: } }
564
- else
565
- raise
566
- end
567
- if uri.start_with?(@server.root_uri)
568
- @server.open_texts[uri] = Text.new(@server, uri, text, version)
569
- end
570
- end
571
- end
572
-
573
- class Message::TextDocument::DidChange < Message
574
- METHOD = "textDocument/didChange"
575
- def run
576
- case @params
577
- in { textDocument: { uri:, version: }, contentChanges: changes }
578
- else
579
- raise
580
- end
581
- @server.open_texts[uri]&.apply_changes(changes, version)
582
- end
583
-
584
- def cancel
585
- puts "cancel"
586
- end
587
- end
588
-
589
- class Message::TextDocument::DidClose < Message
590
- METHOD = "textDocument/didClose"
591
- def run
592
- case @params
593
- in { textDocument: { uri: } }
594
- else
595
- raise
596
- end
597
- @server.open_texts.delete(uri)
598
- end
599
- end
600
-
601
- class Message::TextDocument::Definition < Message
602
- METHOD = "textDocument/definition"
603
- def run
604
- case @params
605
- in {
606
- textDocument: { uri:, },
607
- position: loc,
608
- }
609
- else
610
- raise
611
- end
612
-
613
- definition_table = @server.open_texts[uri]&.definition_table
614
- code_locations = definition_table[CodeLocation.from_lsp(loc)] if definition_table
615
- if code_locations
616
- respond(
617
- code_locations.map do |path, code_range|
618
- {
619
- uri: "file://" + path,
620
- range: code_range.to_lsp,
621
- }
622
- end
623
- )
624
- else
625
- respond(nil)
626
- end
627
- end
628
- end
629
-
630
- class Message::TextDocument::TypeDefinition < Message
631
- METHOD = "textDocument/typeDefinition"
632
- def run
633
- respond(nil)
634
- # jump example
635
- #respond(
636
- # uri: "file:///path/to/typeprof/vscode/sandbox/test.rbs",
637
- # range: {
638
- # start: { line: 1, character: 4 },
639
- # end: { line: 1, character: 7 },
640
- # },
641
- #)
642
- end
643
- end
644
-
645
- class Message::TextDocument::References < Message
646
- METHOD = "textDocument/references"
647
- def run
648
- case @params
649
- in {
650
- textDocument: { uri:, },
651
- position: loc,
652
- }
653
- else
654
- raise
655
- end
656
-
657
- caller_table = @server.open_texts[uri]&.caller_table
658
- code_locations = caller_table[CodeLocation.from_lsp(loc)] if caller_table
659
- if code_locations
660
- respond(
661
- code_locations.map do |path, code_range|
662
- {
663
- uri: "file://" + path,
664
- range: code_range.to_lsp,
665
- }
666
- end
667
- )
668
- else
669
- respond(nil)
670
- end
671
- end
672
- end
673
-
674
- module CompletionTriggerKind
675
- INVOKED = 1
676
- TRIGGER_CHARACTER = 2
677
- TRIGGER_FOR_INCOMPLETE_COMPLETIONS = 3
678
- end
679
-
680
- class Message::TextDocument::Completion < Message
681
- METHOD = "textDocument/completion"
682
- def run
683
- case @params
684
- in {
685
- textDocument: { uri:, },
686
- position: loc,
687
- context: {
688
- triggerKind: trigger_kind
689
- },
690
- }
691
- in {
692
- textDocument: { uri:, },
693
- position: loc,
694
- }
695
- trigger_kind = 1
696
- else
697
- raise
698
- end
699
-
700
- items = @server.open_texts[uri]&.code_complete(loc, trigger_kind)
701
-
702
- if items
703
- respond(
704
- {
705
- isIncomplete: true,
706
- items: items
707
- }
708
- )
709
- else
710
- respond(nil)
711
- end
712
- end
713
- end
714
-
715
- class Message::TextDocument::SignatureHelp < Message
716
- METHOD = "textDocument/signatureHelp"
717
- def run
718
- case @params
719
- in {
720
- textDocument: { uri:, },
721
- position: loc,
722
- context: {
723
- triggerKind: trigger_kind
724
- },
725
- }
726
- in {
727
- textDocument: { uri:, },
728
- position: loc,
729
- }
730
- trigger_kind = 1
731
- else
732
- raise
733
- end
734
-
735
- items = @server.open_texts[uri]&.signature_help(loc, trigger_kind)
736
-
737
- if items
738
- respond({
739
- signatures: items
740
- })
741
- else
742
- respond(nil)
743
- end
744
- end
745
- end
746
-
747
- class Message::TextDocument::CodeLens < Message
748
- METHOD = "textDocument/codeLens"
749
- def run
750
- case @params
751
- in { textDocument: { uri: } }
752
- else
753
- raise
754
- end
755
-
756
- text = @server.open_texts[uri]
757
- if text && @server.signature_enabled
758
- # enqueue in the analysis queue because codeLens is order sensitive
759
- text.push_analysis_queue do
760
- respond(text.sigs)
761
- end
762
- else
763
- respond(nil)
764
- end
765
- end
766
- end
767
-
768
- class Message::CancelRequest < Message
769
- METHOD = "$/cancelRequest"
770
- def run
771
- req = @server.running_requests_from_client[@params[:id]]
772
- #p [:cancel, @params[:id]]
773
- req.cancel if req.respond_to?(:cancel)
774
- end
775
- end
776
-
777
- Message.build_table
778
-
779
- class Reader
780
- class ProtocolError < StandardError
781
- end
782
-
783
- def initialize(io)
784
- @io = io
785
- end
786
-
787
- def read
788
- while line = @io.gets
789
- line2 = @io.gets
790
- if line =~ /\AContent-length: (\d+)\r\n\z/i && line2 == "\r\n"
791
- len = $1.to_i
792
- json = JSON.parse(@io.read(len), symbolize_names: true)
793
- yield json
794
- else
795
- raise ProtocolError, "LSP broken header"
796
- end
797
- end
798
- end
799
- end
800
-
801
- class Writer
802
- def initialize(io)
803
- @io = io
804
- end
805
-
806
- def write(**json)
807
- json = JSON.generate(json.merge(jsonrpc: "2.0"))
808
- @io << "Content-Length: #{ json.bytesize }\r\n\r\n" << json
809
- @io.flush
810
- end
811
-
812
- module ErrorCodes
813
- ParseError = -32700
814
- InvalidRequest = -32600
815
- MethodNotFound = -32601
816
- InvalidParams = -32602
817
- InternalError = -32603
818
- end
819
- end
820
-
821
- module Helpers
822
- def pos(line, character)
823
- { line: line, character: character }
824
- end
825
-
826
- def range(s, e)
827
- { start: s, end: e }
828
- end
829
- end
830
-
831
- module MessageType
832
- Error = 1
833
- Warning = 2
834
- Info = 3
835
- Log = 4
836
- end
837
-
838
- class Server
839
- class Exit < StandardError; end
840
-
841
- include Helpers
842
-
843
- def initialize(config, reader, writer)
844
- @typeprof_config = config
845
- @reader = reader
846
- @writer = writer
847
- @tx_mutex = Mutex.new
848
- @request_id = 0
849
- @running_requests_from_client = {}
850
- @running_requests_from_server = {}
851
- @open_texts = {}
852
- @sigs = {} # tmp
853
- @signature_enabled = true
854
- end
855
-
856
- attr_reader :typeprof_config, :open_texts, :sigs, :running_requests_from_client
857
- attr_accessor :root_uri, :signature_enabled
858
-
859
- def run
860
- @reader.read do |json|
861
- if json[:method]
862
- # request or notification
863
- msg = Message.find(json[:method]).new(self, json)
864
- @running_requests_from_client[json[:id]] = msg if json[:id]
865
- msg.run
866
- else
867
- callback = @running_requests_from_server.delete(json[:id])
868
- callback&.call(json[:params])
869
- end
870
- end
871
- rescue Exit
872
- rescue => e
873
- msg = "Tyeprof fatal error: #{e.message}"
874
- send_notification(
875
- 'window/showMessage',
876
- type: MessageType::Error,
877
- message: msg
878
- )
879
- send_notification(
880
- 'window/logMessage',
881
- type: MessageType::Error,
882
- message: "#{msg} Backtrace: #{e.backtrace}"
883
- )
884
- send_notification('typeprof.showErrorStatus')
885
- retry
886
- end
887
-
888
- def send_response(**msg)
889
- @running_requests_from_client.delete(msg[:id])
890
- exclusive_write(**msg)
891
- end
892
-
893
- def send_notification(method, params = nil)
894
- exclusive_write(method: method, params: params)
895
- end
896
-
897
- def send_request(method, **params, &blk)
898
- id = @request_id += 1
899
- @running_requests_from_server[id] = blk
900
- exclusive_write(id: id, method: method, params: params)
901
- end
902
-
903
- def exclusive_write(**json)
904
- @tx_mutex.synchronize do
905
- @writer.write(**json)
906
- end
907
- end
908
- end
909
- end
910
- end
4
+ require_relative "lsp/text"
5
+ require_relative "lsp/messages"
6
+ require_relative "lsp/server"
7
+ require_relative "lsp/util"