Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 38 additions & 16 deletions lib/ruby_lsp/listeners/completion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,15 @@ class Completion
"__LINE__",
].freeze

#: (ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem] response_builder, GlobalState global_state, NodeContext node_context, SorbetLevel sorbet_level, Prism::Dispatcher dispatcher, URI::Generic uri, String? trigger_character) -> void
#: (ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem] response_builder, GlobalState global_state, NodeContext node_context, SorbetLevel sorbet_level, Prism::Dispatcher dispatcher, URI::Generic uri, (^(Integer arg0) -> Integer | Prism::CodeUnitsCache) code_units_cache, String? trigger_character) -> void
def initialize( # rubocop:disable Metrics/ParameterLists
response_builder,
global_state,
node_context,
sorbet_level,
dispatcher,
uri,
code_units_cache,
trigger_character
)
@response_builder = response_builder
Expand All @@ -67,6 +68,7 @@ def initialize( # rubocop:disable Metrics/ParameterLists
@node_context = node_context
@sorbet_level = sorbet_level
@uri = uri
@code_units_cache = code_units_cache
@trigger_character = trigger_character

dispatcher.register(
Expand Down Expand Up @@ -105,7 +107,7 @@ def on_constant_read_node_enter(node)
name = RubyIndexer::Index.constant_name(node)
return if name.nil?

range = range_from_location(node.location)
range = range_from_prism_location(node.location)
candidates = @index.constant_completion_candidates(name, @node_context.nesting)
candidates.each do |entries|
complete_name = entries.first #: as !nil
Expand Down Expand Up @@ -136,7 +138,7 @@ def on_constant_path_node_enter(node)
end
return if name.nil?

constant_path_completion(name, range_from_location(node.location))
constant_path_completion(name, range_from_prism_location(node.location))
end

#: (Prism::CallNode node) -> void
Expand All @@ -161,8 +163,14 @@ def on_call_node_enter(node)
constant_path_completion(
"#{name}::",
Interface::Range.new(
start: Interface::Position.new(line: start_loc.start_line - 1, character: start_loc.start_column),
end: Interface::Position.new(line: end_loc.end_line - 1, character: end_loc.end_column),
start: Interface::Position.new(
line: start_loc.start_line - 1,
character: start_loc.cached_start_code_units_column(@code_units_cache),
),
end: Interface::Position.new(
line: end_loc.end_line - 1,
character: end_loc.cached_end_code_units_column(@code_units_cache),
),
),
)
return
Expand Down Expand Up @@ -275,6 +283,20 @@ def on_class_variable_write_node_enter(node)

private

#: (Prism::Location location) -> Interface::Range
def range_from_prism_location(location)
Interface::Range.new(
start: Interface::Position.new(
line: location.start_line - 1,
character: location.cached_start_code_units_column(@code_units_cache),
),
end: Interface::Position.new(
line: location.end_line - 1,
character: location.cached_end_code_units_column(@code_units_cache),
),
)
end

#: (String name, Interface::Range range) -> void
def constant_path_completion(name, range)
top_level_reference = if name.start_with?("::")
Expand Down Expand Up @@ -338,7 +360,7 @@ def handle_global_variable_completion(name, location)

return if candidates.none?

range = range_from_location(location)
range = range_from_prism_location(location)

candidates.flatten.uniq(&:name).each do |entry|
entry_name = entry.name
Expand All @@ -360,7 +382,7 @@ def handle_class_variable_completion(name, location)
type = @type_inferrer.infer_receiver_type(@node_context)
return unless type

range = range_from_location(location)
range = range_from_prism_location(location)

@index.class_variable_completion_candidates(name, type.name).each do |entry|
variable_name = entry.name
Expand Down Expand Up @@ -395,7 +417,7 @@ def handle_instance_variable_completion(name, location)
type = @type_inferrer.infer_receiver_type(@node_context)
return unless type

range = range_from_location(location)
range = range_from_prism_location(location)
@index.instance_variable_completion_candidates(name, type.name).each do |entry|
variable_name = entry.name

Expand Down Expand Up @@ -495,16 +517,16 @@ def complete_methods(node, name)
method_name = @trigger_character == "." ? nil : name

range = if method_name
range_from_location(
node.message_loc, #: as !nil
)
location = node.message_loc #: as !nil
range_from_prism_location(location)
else
loc = node.call_operator_loc

if loc
character = loc.cached_start_code_units_column(@code_units_cache) + 1
Interface::Range.new(
start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
start: Interface::Position.new(line: loc.start_line - 1, character: character),
end: Interface::Position.new(line: loc.start_line - 1, character: character),
)
end
end
Expand Down Expand Up @@ -550,7 +572,7 @@ def complete_methods(node, name)

#: (Prism::CallNode node, String name) -> void
def add_local_completions(node, name)
range = range_from_location(
range = range_from_prism_location(
node.message_loc, #: as !nil
)

Expand All @@ -572,7 +594,7 @@ def add_local_completions(node, name)

#: (Prism::CallNode node, String name) -> void
def add_keyword_completions(node, name)
range = range_from_location(
range = range_from_prism_location(
node.message_loc, #: as !nil
)

Expand All @@ -598,7 +620,7 @@ def build_completion(label, node)
Interface::CompletionItem.new(
label: label,
text_edit: Interface::TextEdit.new(
range: range_from_location(loc),
range: range_from_prism_location(loc),
new_text: label,
),
kind: Constant::CompletionItemKind::FILE,
Expand Down
1 change: 1 addition & 0 deletions lib/ruby_lsp/requests/completion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def initialize(document, global_state, params, sorbet_level, dispatcher)
sorbet_level,
dispatcher,
document.uri,
document.code_units_cache,
params.dig(:context, :triggerCharacter),
)

Expand Down
23 changes: 23 additions & 0 deletions test/requests/completion_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1535,6 +1535,29 @@ def do_something(abc1, abc2, abc3)
end
end

def test_completion_for_locals_with_unicode_before_cursor
source = +<<~'RUBY'
test = "moo"
"🐮 says #{te}"
RUBY

with_server(source, stub_no_typechecker: true) do |server, uri|
server.process_message(id: 1, method: "textDocument/completion", params: {
textDocument: { uri: uri },
position: { line: 1, character: 13 },
})

result = server.pop_response.response
item = result.find { |completion| completion.label == "test" } #: as !nil

assert_equal("test", item.text_edit.new_text)
assert_equal(
{ start: { line: 1, character: 11 }, end: { line: 1, character: 13 } },
item.text_edit.range.to_hash.transform_values(&:to_hash),
)
end
end

def test_completion_for_locals_only_happens_when_there_is_no_receiver
source = +<<~RUBY
class Child
Expand Down
Loading