Skip to content

Commit

Permalink
feat: add completion for all global variable nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
snutij committed Oct 21, 2024
1 parent 6361649 commit 3b4fb4b
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 45 deletions.
69 changes: 52 additions & 17 deletions lib/ruby_lsp/listeners/completion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,12 @@ def initialize( # rubocop:disable Metrics/ParameterLists
:on_constant_path_node_enter,
:on_constant_read_node_enter,
:on_call_node_enter,
:on_global_variable_and_write_node_enter,
:on_global_variable_operator_write_node_enter,
:on_global_variable_or_write_node_enter,
:on_global_variable_read_node_enter,
:on_global_variable_target_node_enter,
:on_global_variable_write_node_enter,
:on_instance_variable_read_node_enter,
:on_instance_variable_write_node_enter,
:on_instance_variable_and_write_node_enter,
Expand Down Expand Up @@ -181,27 +186,34 @@ def on_call_node_enter(node)
end
end

sig { params(node: Prism::GlobalVariableReadNode).void }
def on_global_variable_read_node_enter(node)
candidates = @index.prefix_search(node.name.to_s)
sig { params(node: Prism::GlobalVariableAndWriteNode).void }
def on_global_variable_and_write_node_enter(node)
handle_global_variable_completion(node.name.to_s, node.name_loc)
end

return if candidates.none?
sig { params(node: Prism::GlobalVariableOperatorWriteNode).void }
def on_global_variable_operator_write_node_enter(node)
handle_global_variable_completion(node.name.to_s, node.name_loc)
end

range = range_from_location(node.location)
sig { params(node: Prism::GlobalVariableOrWriteNode).void }
def on_global_variable_or_write_node_enter(node)
handle_global_variable_completion(node.name.to_s, node.name_loc)
end

candidates.flatten.each do |entry|
entry_name = entry.name
sig { params(node: Prism::GlobalVariableReadNode).void }
def on_global_variable_read_node_enter(node)
handle_global_variable_completion(node.name.to_s, node.location)
end

@response_builder << Interface::CompletionItem.new(
label: entry_name,
filter_text: entry_name,
label_details: Interface::CompletionItemLabelDetails.new(
description: entry.file_name,
),
text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
kind: Constant::CompletionItemKind::VARIABLE,
)
end
sig { params(node: Prism::GlobalVariableTargetNode).void }
def on_global_variable_target_node_enter(node)
handle_global_variable_completion(node.name.to_s, node.location)
end

sig { params(node: Prism::GlobalVariableWriteNode).void }
def on_global_variable_write_node_enter(node)
handle_global_variable_completion(node.name.to_s, node.name_loc)
end

sig { params(node: Prism::InstanceVariableReadNode).void }
Expand Down Expand Up @@ -291,6 +303,29 @@ def constant_path_completion(name, range)
end
end

sig { params(name: String, location: Prism::Location).void }
def handle_global_variable_completion(name, location)
candidates = @index.prefix_search(name)

return if candidates.none?

range = range_from_location(location)

candidates.flatten.each do |entry|
entry_name = entry.name

@response_builder << Interface::CompletionItem.new(
label: entry_name,
filter_text: entry_name,
label_details: Interface::CompletionItemLabelDetails.new(
description: entry.file_name,
),
text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
kind: Constant::CompletionItemKind::VARIABLE,
)
end
end

sig { params(name: String, location: Prism::Location).void }
def handle_instance_variable_completion(name, location)
# Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
Expand Down
5 changes: 5 additions & 0 deletions lib/ruby_lsp/requests/completion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ def initialize(document, global_state, params, sorbet_level, dispatcher)
Prism::CallNode,
Prism::ConstantReadNode,
Prism::ConstantPathNode,
Prism::GlobalVariableAndWriteNode,
Prism::GlobalVariableOperatorWriteNode,
Prism::GlobalVariableOrWriteNode,
Prism::GlobalVariableReadNode,
Prism::GlobalVariableTargetNode,
Prism::GlobalVariableWriteNode,
Prism::InstanceVariableReadNode,
Prism::InstanceVariableAndWriteNode,
Prism::InstanceVariableOperatorWriteNode,
Expand Down
72 changes: 44 additions & 28 deletions test/requests/completion_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,50 @@ def test_completion_addons
end
end

def test_completion_for_global_variables
source = <<~RUBY
$qar &&= 1
$qaz += 1
$qux ||= 1
$quux, $qorge = 1
$qoo = 1
$q
$LOAD
$
RUBY

with_server(source) do |server, uri|
index = server.instance_variable_get(:@global_state).index
RubyIndexer::RBSIndexer.new(index).index_ruby_core

server.process_message(id: 1, method: "textDocument/completion", params: {
textDocument: { uri: uri },
position: { line: 6, character: 2 },
})

result = server.pop_response.response
assert_equal(["$qar", "$qaz", "$qux", "$quux", "$qorge", "$qoo"], result.map(&:label))

server.process_message(id: 1, method: "textDocument/completion", params: {
textDocument: { uri: uri },
position: { line: 7, character: 5 },
})

result = server.pop_response.response
assert_equal(["$LOADED_FEATURES", "$LOAD_PATH"], result.map(&:label))
assert_equal(["global_variables.rbs", "global_variables.rbs"], result.map { _1.label_details.description })

server.process_message(id: 1, method: "textDocument/completion", params: {
textDocument: { uri: uri },
position: { line: 8, character: 1 },
})

result = server.pop_response.response
assert_operator(result.size, :>, 40)
end
end

def test_completion_for_instance_variables
source = +<<~RUBY
class Foo
Expand Down Expand Up @@ -1431,34 +1475,6 @@ def test_guessed_type_name_is_only_included_for_guessed_types
end
end

def test_completion_for_global_variables
source = <<~RUBY
$
$LOAD
RUBY

with_server(source) do |server, uri|
index = server.instance_variable_get(:@global_state).index
RubyIndexer::RBSIndexer.new(index).index_ruby_core

server.process_message(id: 1, method: "textDocument/completion", params: {
textDocument: { uri: uri },
position: { line: 0, character: 1 },
})
items = server.pop_response.response
assert_operator(items.size, :>, 40)

server.process_message(id: 1, method: "textDocument/completion", params: {
textDocument: { uri: uri },
position: { line: 1, character: 1 },
})
items = server.pop_response.response
assert_operator(items.size, :>=, 2)
assert_includes(items.map(&:label), "$LOADED_FEATURES")
assert_includes(items.map(&:label), "$LOAD_PATH")
end
end

private

def with_file_structure(server, &block)
Expand Down

0 comments on commit 3b4fb4b

Please sign in to comment.