typeprof 0.21.11 → 0.30.1

Sign up to get free protection for your applications and to get access to all the features.
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"