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
@@ -0,0 +1,415 @@
1
+ module TypeProf::LSP
2
+ class Message
3
+ def initialize(server, json)
4
+ @server = server
5
+ @id = json[:id]
6
+ @method = json[:method]
7
+ @params = json[:params]
8
+ end
9
+
10
+ def run
11
+ p [:ignored, @method]
12
+ end
13
+
14
+ def log(msg)
15
+ end
16
+
17
+ def respond(result)
18
+ raise "do not respond to notification" if @id == nil
19
+ @server.send_response(id: @id, result: result)
20
+ end
21
+
22
+ def respond_error(error)
23
+ raise "do not respond to notification" if @id == nil
24
+ @server.send_response(id: @id, error: error)
25
+ end
26
+
27
+ def notify(method, **params)
28
+ @server.send_notification(method, **params)
29
+ end
30
+
31
+ Classes = []
32
+ def self.inherited(klass)
33
+ Classes << klass
34
+ end
35
+
36
+ Table = Hash.new(Message)
37
+ def self.build_table
38
+ Classes.each do |klass|
39
+ Table[klass::METHOD] = klass
40
+ end
41
+ end
42
+
43
+ def self.find(method)
44
+ Table[method]
45
+ end
46
+ end
47
+
48
+ class Message::CancelRequest < Message
49
+ METHOD = "$/cancelRequest" # notification
50
+ def run
51
+ @server.cancel_request(@params[:id])
52
+ end
53
+ end
54
+
55
+ class Message::Initialize < Message
56
+ METHOD = "initialize" # request (required)
57
+ def run
58
+ folders = @params[:workspaceFolders].map do |folder|
59
+ folder => { uri:, }
60
+ @server.uri_to_path(uri)
61
+ end
62
+
63
+ @server.add_workspaces(folders)
64
+
65
+ respond(
66
+ capabilities: {
67
+ textDocumentSync: {
68
+ openClose: true,
69
+ change: 2, # Incremental
70
+ },
71
+ hoverProvider: true,
72
+ definitionProvider: true,
73
+ typeDefinitionProvider: true,
74
+ completionProvider: {
75
+ triggerCharacters: [".", ":"],
76
+ },
77
+ #signatureHelpProvider: {
78
+ # triggerCharacters: ["(", ","],
79
+ #},
80
+ codeLensProvider: {
81
+ resolveProvider: false,
82
+ },
83
+ renameProvider: {
84
+ prepareProvider: false,
85
+ },
86
+ executeCommandProvider: {
87
+ commands: [
88
+ "typeprof.createPrototypeRBS",
89
+ "typeprof.enableSignature",
90
+ "typeprof.disableSignature",
91
+ ],
92
+ },
93
+ #typeDefinitionProvider: true,
94
+ referencesProvider: true,
95
+ },
96
+ serverInfo: {
97
+ name: "typeprof",
98
+ version: TypeProf::VERSION,
99
+ },
100
+ )
101
+
102
+ log "TypeProf for IDE is started successfully"
103
+ end
104
+ end
105
+
106
+ class Message::Initialized < Message
107
+ METHOD = "initialized" # notification
108
+ def run
109
+ end
110
+ end
111
+
112
+ class Message::Shutdown < Message
113
+ METHOD = "shutdown" # request (required)
114
+ def run
115
+ respond(nil)
116
+ end
117
+ end
118
+
119
+ class Message::Exit < Message
120
+ METHOD = "exit" # notification
121
+ def run
122
+ @server.exit
123
+ end
124
+ end
125
+
126
+ module Message::TextDocument
127
+ end
128
+
129
+ class Message::TextDocument::DidOpen < Message
130
+ METHOD = "textDocument/didOpen" # notification
131
+ def run
132
+ @params => { textDocument: { uri:, version:, text: } }
133
+
134
+ path = @server.uri_to_path(uri)
135
+ return unless @server.target_path?(path)
136
+
137
+ text = Text.new(path, text, version)
138
+ @server.open_texts[uri] = text
139
+ @server.core.update_file(text.path, text.string)
140
+ @server.send_request("workspace/codeLens/refresh")
141
+ @server.publish_diagnostics(uri)
142
+ end
143
+ end
144
+
145
+ class Message::TextDocument::DidChange < Message
146
+ METHOD = "textDocument/didChange" # notification
147
+ def run
148
+ @params => { textDocument: { uri:, version: }, contentChanges: changes }
149
+ text = @server.open_texts[uri]
150
+ return unless text
151
+ text.apply_changes(changes, version)
152
+ @server.core.update_file(text.path, text.string)
153
+ @server.send_request("workspace/codeLens/refresh")
154
+ @server.publish_diagnostics(uri)
155
+ end
156
+ end
157
+
158
+ # textDocument/willSave notification
159
+ # textDocument/willSaveWaitUntil request
160
+ # textDocument/didSave notification
161
+
162
+ class Message::TextDocument::DidClose < Message
163
+ METHOD = "textDocument/didClose" # notification
164
+ def run
165
+ @params => { textDocument: { uri: } }
166
+ text = @server.open_texts.delete(uri)
167
+ return unless text
168
+ @server.core.update_file(text.path, nil)
169
+ end
170
+ end
171
+
172
+ # textDocument/declaration request
173
+
174
+ class Message::TextDocument::Definition < Message
175
+ METHOD = "textDocument/definition" # request
176
+ def run
177
+ @params => {
178
+ textDocument: { uri: },
179
+ position: pos,
180
+ }
181
+ text = @server.open_texts[uri]
182
+ unless text
183
+ respond(nil)
184
+ return
185
+ end
186
+ defs = @server.core.definitions(text.path, TypeProf::CodePosition.from_lsp(pos))
187
+ if defs.empty?
188
+ respond(nil)
189
+ else
190
+ respond(defs.map do |path, code_range|
191
+ {
192
+ uri: @server.path_to_uri(path),
193
+ range: code_range.to_lsp,
194
+ }
195
+ end)
196
+ end
197
+ end
198
+ end
199
+
200
+ class Message::TextDocument::TypeDefinition < Message
201
+ METHOD = "textDocument/typeDefinition" # request
202
+ def run
203
+ @params => {
204
+ textDocument: { uri: },
205
+ position: pos,
206
+ }
207
+ text = @server.open_texts[uri]
208
+ unless text
209
+ respond(nil)
210
+ return
211
+ end
212
+ defs = @server.core.type_definitions(text.path, TypeProf::CodePosition.from_lsp(pos))
213
+ if defs.empty?
214
+ respond(nil)
215
+ else
216
+ respond(defs.map do |path, code_range|
217
+ {
218
+ uri: @server.path_to_uri(path),
219
+ range: code_range.to_lsp,
220
+ }
221
+ end)
222
+ end
223
+ end
224
+ end
225
+
226
+ class Message::TextDocument::References < Message
227
+ METHOD = "textDocument/references" # request
228
+ def run
229
+ @params => {
230
+ textDocument: { uri: },
231
+ position: pos,
232
+ }
233
+ text = @server.open_texts[uri]
234
+ unless text
235
+ respond(nil)
236
+ return
237
+ end
238
+ callsites = @server.core.references(text.path, TypeProf::CodePosition.from_lsp(pos))
239
+ if callsites
240
+ respond(callsites.map do |path, code_range|
241
+ {
242
+ uri: @server.path_to_uri(path),
243
+ range: code_range.to_lsp,
244
+ }
245
+ end)
246
+ else
247
+ respond(nil)
248
+ end
249
+ end
250
+ end
251
+
252
+ class Message::TextDocument::Hover < Message
253
+ METHOD = "textDocument/hover" # request
254
+ def run
255
+ @params => {
256
+ textDocument: { uri: },
257
+ position: pos,
258
+ }
259
+ text = @server.open_texts[uri]
260
+ unless text
261
+ respond(nil)
262
+ return
263
+ end
264
+ str = @server.core.hover(text.path, TypeProf::CodePosition.from_lsp(pos))
265
+ if str
266
+ respond(contents: { language: "ruby", value: str })
267
+ else
268
+ respond(nil)
269
+ end
270
+ end
271
+ end
272
+
273
+ class Message::TextDocument::CodeLens < Message
274
+ METHOD = "textDocument/codeLens"
275
+ def run
276
+ @params => { textDocument: { uri: } }
277
+ text = @server.open_texts[uri]
278
+ if !text || !@server.signature_enabled
279
+ respond(nil)
280
+ return
281
+ end
282
+ ret = []
283
+ @server.core.code_lens(text.path) do |code_range, title|
284
+ pos = code_range.first
285
+ ret << {
286
+ range: TypeProf::CodeRange.new(pos, pos.right).to_lsp,
287
+ command: {
288
+ title: "#: " + title,
289
+ command: "typeprof.createPrototypeRBS",
290
+ arguments: [uri, code_range.first.lineno, code_range.first.column, title],
291
+ },
292
+ }
293
+ end
294
+ respond(ret)
295
+ end
296
+ end
297
+
298
+ # textDocument/documentSymbol request
299
+
300
+ # textDocument/diagnostic request
301
+ # workspace/diagnostic request
302
+ # workspace/diagnostic/refresh request
303
+
304
+ class Message::TextDocument::Completion < Message
305
+ METHOD = "textDocument/completion" # request
306
+ def run
307
+ @params => {
308
+ textDocument: { uri: },
309
+ position: pos,
310
+ }
311
+ #trigger_kind = @params.key?(:context) ? @params[:context][:triggerKind] : 1 # Invoked
312
+ text = @server.open_texts[uri]
313
+ unless text
314
+ respond(nil)
315
+ return
316
+ end
317
+ items = []
318
+ sort = "aaaa"
319
+ text.modify_for_completion(text, pos) do |string, trigger, pos|
320
+ @server.core.update_file(text.path, string)
321
+ pos = TypeProf::CodePosition.from_lsp(pos)
322
+ @server.core.completion(text.path, trigger, pos) do |mid, hint|
323
+ items << {
324
+ label: mid,
325
+ kind: 2, # Method
326
+ sortText: sort,
327
+ detail: hint,
328
+ }
329
+ sort = sort.succ
330
+ end
331
+ end
332
+ respond(
333
+ isIncomplete: false,
334
+ items: items,
335
+ )
336
+ @server.core.update_file(text.path, text.string)
337
+ end
338
+ end
339
+
340
+ # textDocument/signatureHelp request
341
+
342
+ # textDocument/prepareRename request
343
+
344
+ class Message::TextDocument::Rename < Message
345
+ METHOD = "textDocument/rename" # request
346
+ def run
347
+ @params => {
348
+ textDocument: { uri: },
349
+ position: pos,
350
+ newName:,
351
+ }
352
+ text = @server.open_texts[uri]
353
+ unless text
354
+ respond(nil)
355
+ return
356
+ end
357
+ renames = @server.core.rename(text.path, TypeProf::CodePosition.from_lsp(pos))
358
+ if renames
359
+ changes = {}
360
+ renames.each do |path, cr|
361
+ (changes[@server.path_to_uri(path)] ||= []) << {
362
+ range: cr.to_lsp,
363
+ newText: newName,
364
+ }
365
+ end
366
+ respond({
367
+ changes:,
368
+ })
369
+ else
370
+ respond(nil)
371
+ end
372
+ end
373
+ end
374
+
375
+ module Message::Workspace
376
+ end
377
+
378
+ # workspace/symbol request
379
+ # workspaceSymbol/resolve request
380
+
381
+ # workspace/didChangeWatchedFiles notification
382
+
383
+ class Message::Workspace::ExecuteCommand < Message
384
+ METHOD = "workspace/executeCommand" # request
385
+ def run
386
+ case @params[:command]
387
+ when "typeprof.enableSignature"
388
+ @server.signature_enabled = true
389
+ @server.send_request("workspace/codeLens/refresh")
390
+ respond(nil)
391
+ when "typeprof.disableSignature"
392
+ @server.signature_enabled = false
393
+ @server.send_request("workspace/codeLens/refresh")
394
+ respond(nil)
395
+ when "typeprof.createPrototypeRBS"
396
+ uri, row, col, str = @params[:arguments]
397
+ @server.send_request(
398
+ "workspace/applyEdit",
399
+ edit: {
400
+ changes: {
401
+ uri => [{
402
+ range: TypeProf::CodeRange[row, col, row, col].to_lsp,
403
+ newText: "#: #{ str }\n" + " " * col,
404
+ }],
405
+ },
406
+ },
407
+ ) do |res, err|
408
+ end
409
+ respond(nil)
410
+ end
411
+ end
412
+ end
413
+
414
+ Message.build_table
415
+ end
@@ -0,0 +1,203 @@
1
+ module TypeProf::LSP
2
+ module ErrorCodes
3
+ ParseError = -32700
4
+ InvalidRequest = -32600
5
+ MethodNotFound = -32601
6
+ InvalidParams = -32602
7
+ InternalError = -32603
8
+ end
9
+
10
+ class Server
11
+ def self.start_stdio(core)
12
+ $stdin.binmode
13
+ $stdout.binmode
14
+ reader = Reader.new($stdin)
15
+ writer = Writer.new($stdout)
16
+ # pipe all builtin print output to stderr to avoid conflicting with lsp
17
+ $stdout = $stderr
18
+ new(core, reader, writer).run
19
+ end
20
+
21
+ def self.start_socket(core)
22
+ Socket.tcp_server_sockets("localhost", nil) do |servs|
23
+ serv = servs[0].local_address
24
+ $stdout << JSON.generate({
25
+ host: serv.ip_address,
26
+ port: serv.ip_port,
27
+ pid: $$,
28
+ })
29
+ $stdout.flush
30
+
31
+ $stdout = $stderr
32
+
33
+ Socket.accept_loop(servs) do |sock|
34
+ sock.set_encoding("UTF-8")
35
+ begin
36
+ reader = Reader.new(sock)
37
+ writer = Writer.new(sock)
38
+ new(core, reader, writer).run
39
+ ensure
40
+ sock.close
41
+ end
42
+ exit
43
+ end
44
+ end
45
+ end
46
+
47
+ def initialize(core, reader, writer, url_schema: nil, publish_all_diagnostics: false)
48
+ @core = core
49
+ @workspaces = {}
50
+ @reader = reader
51
+ @writer = writer
52
+ @request_id = 0
53
+ @running_requests_from_client = {}
54
+ @running_requests_from_server = {}
55
+ @open_texts = {}
56
+ @exit = false
57
+ @signature_enabled = true
58
+ @url_schema = url_schema || (File::ALT_SEPARATOR != "\\" ? "file://" : "file:///")
59
+ @publish_all_diagnostics = publish_all_diagnostics # TODO: implement more dedicated publish feature
60
+ end
61
+
62
+ attr_reader :core, :open_texts
63
+ attr_accessor :signature_enabled
64
+
65
+ def path_to_uri(path)
66
+ @url_schema + File.expand_path(path)
67
+ end
68
+
69
+ def uri_to_path(url)
70
+ url.delete_prefix(@url_schema)
71
+ end
72
+
73
+ def add_workspaces(folders)
74
+ folders.each do |path|
75
+ conf_path = File.join(path, "typeprof.conf.json")
76
+ if File.readable?(conf_path)
77
+ conf = TypeProf::LSP.load_json_with_comments(conf_path, symbolize_names: true)
78
+ if conf
79
+ if conf[:typeprof_version] == "experimental"
80
+ if conf[:analysis_unit_dirs].size >= 2
81
+ puts "currently analysis_unit_dirs can have only one directory"
82
+ end
83
+ conf[:analysis_unit_dirs].each do |dir|
84
+ dir = File.expand_path(dir, path)
85
+ @workspaces[dir] = true
86
+ @core.add_workspace(dir, conf[:rbs_dir])
87
+ end
88
+ else
89
+ puts "Unknown typeprof_version: #{ conf[:typeprof_version] }"
90
+ end
91
+ end
92
+ else
93
+ puts "typeprof.conf.json is not found"
94
+ end
95
+ end
96
+ end
97
+
98
+ def target_path?(path)
99
+ @workspaces.each do |folder, _|
100
+ return true if path.start_with?(folder)
101
+ end
102
+ return false
103
+ end
104
+
105
+ def run
106
+ @reader.read do |json|
107
+ if json[:method]
108
+ # request or notification
109
+ msg_class = Message.find(json[:method])
110
+ if msg_class
111
+ msg = msg_class.new(self, json)
112
+ @running_requests_from_client[json[:id]] = msg if json[:id]
113
+ msg.run
114
+ else
115
+
116
+ end
117
+ else
118
+ # response
119
+ callback = @running_requests_from_server.delete(json[:id])
120
+ callback&.call(json[:params], json[:error])
121
+ end
122
+ break if @exit
123
+ end
124
+ end
125
+
126
+ def send_response(**msg)
127
+ @running_requests_from_client.delete(msg[:id])
128
+ @writer.write(**msg)
129
+ end
130
+
131
+ def send_notification(method, **params)
132
+ @writer.write(method: method, params: params)
133
+ end
134
+
135
+ def send_request(method, **params, &blk)
136
+ id = @request_id += 1
137
+ @running_requests_from_server[id] = blk
138
+ @writer.write(id: id, method: method, params: params)
139
+ end
140
+
141
+ def cancel_request(id)
142
+ req = @running_requests_from_client[id]
143
+ req.cancel if req.respond_to?(:cancel)
144
+ end
145
+
146
+ def exit
147
+ @exit = true
148
+ end
149
+
150
+ def publish_diagnostics(uri)
151
+ (@publish_all_diagnostics ? @open_texts : [[uri, @open_texts[uri]]]).each do |uri, text|
152
+ diags = []
153
+ if text
154
+ @core.diagnostics(text.path) do |diag|
155
+ diags << diag.to_lsp
156
+ end
157
+ end
158
+ send_notification(
159
+ "textDocument/publishDiagnostics",
160
+ uri: uri,
161
+ diagnostics: diags
162
+ )
163
+ end
164
+ end
165
+ end
166
+
167
+ class Reader
168
+ class ProtocolError < StandardError
169
+ end
170
+
171
+ def initialize(io)
172
+ @io = io
173
+ end
174
+
175
+ def read
176
+ while line = @io.gets
177
+ line2 = @io.gets
178
+ if line =~ /\AContent-length: (\d+)\r\n\z/i && line2 == "\r\n"
179
+ len = $1.to_i
180
+ json = JSON.parse(@io.read(len), symbolize_names: true)
181
+ yield json
182
+ else
183
+ raise ProtocolError, "LSP broken header"
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ class Writer
190
+ def initialize(io)
191
+ @io = io
192
+ @mutex = Mutex.new
193
+ end
194
+
195
+ def write(**json)
196
+ json = JSON.generate(json.merge(jsonrpc: "2.0"))
197
+ @mutex.synchronize do
198
+ @io << "Content-Length: #{ json.bytesize }\r\n\r\n" << json
199
+ @io.flush
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,69 @@
1
+ module TypeProf::LSP
2
+ class Text
3
+ def initialize(path, text, version)
4
+ @path = path
5
+ @lines = Text.split(text)
6
+ @version = version
7
+ end
8
+
9
+ attr_reader :path, :lines, :version
10
+
11
+ def self.split(str)
12
+ lines = str.lines
13
+ lines << "" if lines.empty? || lines.last.include?("\n")
14
+ lines
15
+ end
16
+
17
+ def string
18
+ @lines.join
19
+ end
20
+
21
+ def apply_changes(changes, version)
22
+ changes.each do |change|
23
+ change => {
24
+ range: {
25
+ start: { line: start_row, character: start_col },
26
+ end: { line: end_row , character: end_col }
27
+ },
28
+ text: new_text,
29
+ }
30
+
31
+ new_text = Text.split(new_text)
32
+
33
+ prefix = @lines[start_row][0...start_col]
34
+ suffix = @lines[end_row][end_col...]
35
+ if new_text.size == 1
36
+ new_text[0] = prefix + new_text[0] + suffix
37
+ else
38
+ new_text[0] = prefix + new_text[0]
39
+ new_text[-1] = new_text[-1] + suffix
40
+ end
41
+ @lines[start_row .. end_row] = new_text
42
+ end
43
+
44
+ validate
45
+
46
+ @version = version
47
+ end
48
+
49
+ def validate
50
+ raise unless @lines[0..-2].all? {|s| s.count("\n") == 1 && s.end_with?("\n") }
51
+ raise unless @lines[-1].count("\n") == 0
52
+ end
53
+
54
+ def modify_for_completion(changes, pos)
55
+ pos => { line: row, character: col }
56
+ if col >= 2 && @lines[row][col - 1] == "." && (col == 1 || @lines[row][col - 2] != ".")
57
+ @lines[row][col - 1] = " "
58
+ yield string, ".", { line: row, character: col - 2}
59
+ @lines[row][col - 1] = "."
60
+ elsif col >= 3 && @lines[row][col - 2, 2] == "::"
61
+ @lines[row][col - 2, 2] = " "
62
+ yield string, "::", { line: row, character: col - 3 }
63
+ @lines[row][col - 2, 2] = "::"
64
+ else
65
+ yield string, nil, { line: row, character: col }
66
+ end
67
+ end
68
+ end
69
+ end