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.
- checksums.yaml +4 -4
- data/README.md +15 -31
- data/bin/typeprof +5 -0
- data/doc/doc.ja.md +134 -0
- data/doc/doc.md +136 -0
- data/lib/typeprof/cli/cli.rb +178 -0
- data/lib/typeprof/cli.rb +3 -133
- data/lib/typeprof/code_range.rb +112 -0
- data/lib/typeprof/core/ast/base.rb +263 -0
- data/lib/typeprof/core/ast/call.rb +259 -0
- data/lib/typeprof/core/ast/const.rb +126 -0
- data/lib/typeprof/core/ast/control.rb +433 -0
- data/lib/typeprof/core/ast/meta.rb +150 -0
- data/lib/typeprof/core/ast/method.rb +339 -0
- data/lib/typeprof/core/ast/misc.rb +263 -0
- data/lib/typeprof/core/ast/module.rb +123 -0
- data/lib/typeprof/core/ast/pattern.rb +140 -0
- data/lib/typeprof/core/ast/sig_decl.rb +471 -0
- data/lib/typeprof/core/ast/sig_type.rb +663 -0
- data/lib/typeprof/core/ast/value.rb +319 -0
- data/lib/typeprof/core/ast/variable.rb +315 -0
- data/lib/typeprof/core/ast.rb +472 -0
- data/lib/typeprof/core/builtin.rb +146 -0
- data/lib/typeprof/core/env/method.rb +137 -0
- data/lib/typeprof/core/env/method_entity.rb +55 -0
- data/lib/typeprof/core/env/module_entity.rb +408 -0
- data/lib/typeprof/core/env/static_read.rb +155 -0
- data/lib/typeprof/core/env/type_alias_entity.rb +27 -0
- data/lib/typeprof/core/env/value_entity.rb +32 -0
- data/lib/typeprof/core/env.rb +366 -0
- data/lib/typeprof/core/graph/box.rb +998 -0
- data/lib/typeprof/core/graph/change_set.rb +224 -0
- data/lib/typeprof/core/graph/filter.rb +155 -0
- data/lib/typeprof/core/graph/vertex.rb +225 -0
- data/lib/typeprof/core/service.rb +514 -0
- data/lib/typeprof/core/type.rb +352 -0
- data/lib/typeprof/core/util.rb +81 -0
- data/lib/typeprof/core.rb +31 -0
- data/lib/typeprof/diagnostic.rb +35 -0
- data/lib/typeprof/lsp/messages.rb +415 -0
- data/lib/typeprof/lsp/server.rb +203 -0
- data/lib/typeprof/lsp/text.rb +69 -0
- data/lib/typeprof/lsp/util.rb +51 -0
- data/lib/typeprof/lsp.rb +4 -907
- data/lib/typeprof/version.rb +1 -1
- data/lib/typeprof.rb +4 -18
- data/typeprof.gemspec +5 -7
- metadata +47 -33
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/main.yml +0 -39
- data/.gitignore +0 -9
- data/Gemfile +0 -17
- data/Gemfile.lock +0 -41
- data/Rakefile +0 -10
- data/exe/typeprof +0 -10
- data/lib/typeprof/analyzer.rb +0 -2598
- data/lib/typeprof/arguments.rb +0 -414
- data/lib/typeprof/block.rb +0 -176
- data/lib/typeprof/builtin.rb +0 -893
- data/lib/typeprof/code-range.rb +0 -177
- data/lib/typeprof/config.rb +0 -158
- data/lib/typeprof/container-type.rb +0 -912
- data/lib/typeprof/export.rb +0 -589
- data/lib/typeprof/import.rb +0 -852
- data/lib/typeprof/insns-def.rb +0 -65
- data/lib/typeprof/iseq.rb +0 -864
- data/lib/typeprof/method.rb +0 -355
- data/lib/typeprof/type.rb +0 -1140
- data/lib/typeprof/utils.rb +0 -212
- data/tools/coverage.rb +0 -14
- data/tools/setup-insns-def.rb +0 -30
- 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
|