# File typeprof-0.21.1/lib/typeprof/lsp.rb, line 64
def initialize(server, uri, text, version)
@server = server
@uri = uri
@text = text
@version = version
@sigs = nil
@last_analysis_cancel_token = nil
@analysis_queue = Queue.new
@analysis_thread = Thread.new do
loop do
work = @analysis_queue.pop
begin
work.call
rescue Exception
puts "Rescued exception:"
puts $!.full_message
puts
end
end
end
# analyze synchronously to respond the first codeLens request
res, def_table, caller_table = self.analyze(uri, text)
on_text_changed_analysis(res, def_table, caller_table)
end
# File typeprof-0.21.1/lib/typeprof/lsp.rb, line 275
def analyze(uri, text, cancel_token: nil, signature_help_loc: nil)
config = @server.typeprof_config.dup
path = URI(uri).path
config.rb_files = [[path, text]]
config.rbs_files = ["typeprof.rbs"] # XXX
config.verbose = 0
config.max_sec = 1
config.options[:show_errors] = true
config.options[:show_indicator] = false
config.options[:lsp] = true
config.options[:signature_help_loc] = [path, signature_help_loc] if signature_help_loc
TypeProf.analyze(config, cancel_token)
rescue SyntaxError
end
# File typeprof-0.21.1/lib/typeprof/lsp.rb, line 98
def apply_changes(changes, version)
@definition_table = nil
text = @text.empty? ? [] : @text.lines
changes.each do |change|
case change
in {
range: {
start: { line: start_row, character: start_col },
end: { line: end_row , character: end_col }
},
text: change_text,
}
else
raise
end
text << "" if start_row == text.size
text << "" if end_row == text.size
if start_row == end_row
text[start_row][start_col...end_col] = change_text
else
text[start_row][start_col..] = ""
text[end_row][...end_col] = ""
change_text = change_text.lines
case change_text.size
when 0
text[start_row] += text[end_row]
text[start_row + 1 .. end_row] = []
when 1
text[start_row] += change_text.first + text[end_row]
text[start_row + 1 .. end_row] = []
else
text[start_row] += change_text.shift
text[end_row].prepend(change_text.pop)
text[start_row + 1 ... end_row - 1] = change_text
end
end
end
@text = text.join
@version = version
on_text_changed
end
# File typeprof-0.21.1/lib/typeprof/lsp.rb, line 159
def code_complete(loc, trigger_kind)
case loc
in { line: row, character: col }
end
unless row < @text.lines.length && col >= 1 && @text.lines[row][0, col] =~ /\.\w*$/
return nil
end
start_offset = $~.begin(0)
end_offset = $&.size
case trigger_kind
when LSP::CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS
unless @current_completion_session&.reusable?(row, start_offset)
puts "no reusable completion session but got TRIGGER_FOR_INCOMPLETE_COMPLETIONS"
@current_completion_session = new_code_completion_session(row, start_offset, end_offset)
end
return @current_completion_session.results
else
@current_completion_session = new_code_completion_session(row, start_offset, end_offset)
return @current_completion_session&.results
end
end
# File typeprof-0.21.1/lib/typeprof/lsp.rb, line 94
def lines
@text.lines
end
# File typeprof-0.21.1/lib/typeprof/lsp.rb, line 141
def new_code_completion_session(row, start_offset, end_offset)
lines = @text.lines
lines[row][start_offset, end_offset] = ".__typeprof_lsp_completion"
tmp_text = lines.join
res, = analyze(@uri, tmp_text)
if res && res[:completion]
results = res[:completion].keys.map do |name|
{
label: name,
kind: 2, # Method
}
end
return CompletionSession.new(results, row, start_offset)
else
nil
end
end
# File typeprof-0.21.1/lib/typeprof/lsp.rb, line 295
def on_text_changed
cancel_token = AnalysisToken.new
@last_analysis_cancel_token&.cancel
@last_analysis_cancel_token = cancel_token
uri = @uri
text = @text
self.push_analysis_queue do
if cancel_token.cancelled?
next
end
res, def_table, caller_table = self.analyze(uri, text, cancel_token: cancel_token)
unless cancel_token.cancelled?
on_text_changed_analysis(res, def_table, caller_table)
end
end
end
# File typeprof-0.21.1/lib/typeprof/lsp.rb, line 313
def on_text_changed_analysis(res, definition_table, caller_table)
@definition_table = definition_table
@caller_table = caller_table
return unless res
@sigs = []
res[:sigs].each do |file, lineno, sig_str, rbs_code_range, class_kind, class_name|
uri0 = "file://" + file
if @uri == uri0
command = { title: sig_str }
if rbs_code_range
command[:command] = "typeprof.jumpToRBS"
command[:arguments] = [uri0, { line: lineno - 1, character: 0 }, @server.root_uri + "/" + rbs_code_range[0], rbs_code_range[1].to_lsp]
else
command[:command] = "typeprof.createPrototypeRBS"
command[:arguments] = [class_kind, class_name, sig_str]
end
@sigs << {
range: {
start: { line: lineno - 1, character: 0 },
end: { line: lineno - 1, character: 1 },
},
command: command,
}
end
end
diagnostics = {}
res[:errors]&.each do |(file, code_range), msg|
next unless file and code_range
uri0 = "file://" + file
diagnostics[uri0] ||= []
diagnostics[uri0] << {
range: code_range.to_lsp,
severity: 1,
source: "TypeProf",
message: msg,
}
end
@server.send_request("workspace/codeLens/refresh")
@server.send_notification(
"textDocument/publishDiagnostics",
{
uri: @uri,
version: version,
diagnostics: diagnostics[@uri] || [],
}
)
end
# File typeprof-0.21.1/lib/typeprof/lsp.rb, line 291
def push_analysis_queue(&work)
@analysis_queue.push(work)
end
# File typeprof-0.21.1/lib/typeprof/lsp.rb, line 248
def signature_help(loc, trigger_kind)
loc = CodeLocation.from_lsp(loc)
res, = analyze(@uri, @text, signature_help_loc: loc)
if res
res[:signature_help].filter_map do |sig_str, sig_help, node_id|
node = ISeq.find_node_by_id(@text, node_id)
if node && ISeq.code_range_from_node(node).contain_loc?(loc)
idx = locate_arg_index_in_signature_help(node, loc, sig_help)
{
label: sig_str,
parameters: sig_help.values.map do |r|
{
label: [r.begin, r.end],
}
end,
activeParameter: idx,
}
end
end
else
nil
end
end