-
Notifications
You must be signed in to change notification settings - Fork 171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add autocomplete for classes, modules and constants #957
Conversation
|
# When the user types in the first letter of a constant name, we actually receive the position of the next | ||
# immediate character. We check to see if the character is uppercase and then remove the offset to try to locate | ||
# the node, as it could not be a constant | ||
target_node_types = if ("A".."Z").cover?(document.source[char_position - 1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about constants beginning with letters from other alphabets, e.g. Á
? 😁
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or just _FOO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_FOO
would just be an odd-looking local variable:
irb(main):001:0> _FOO = 1
=> 1
irb(main):002:0> defined? _FOO
=> "local-variable"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we get all upper case characters including the ones with accents in Ruby? I never seen this done before. And we need to return the list of trigger characters to the editor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe with /[[:upper:]]/
, if that's consistent with what Ruby considers for constants:
irb(main):003> "Á".match?(/[[:upper:]]/)
=> true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(if you can find the lower and upper bounds, you can use that for the input)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With a little help from ChatGPT 😁
# Define the range of Unicode characters
unicode_range = 0..0x10FFFF
# Initialize an array to store the uppercase characters
uppercase_chars = []
# Iterate over the unicode range
unicode_range.each do |i|
char = [i].pack('U*')
if char =~ /[[:upper:]]/
uppercase_chars << char
end
rescue # to avoid 'invalid byte sequence in UTF-8' for some values
nil
end.compact
# Output the uppercase characters
puts uppercase_chars
(1951 results)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created #990 to explore this in more detail. For now, let's ship without unicode support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is usually best to ask Ruby to compile a snippet with the given input and see if Ruby treats it as a constant:
pry> RubyVM::InstructionSequence.compile("Ã = 42", nil, nil, 0, false).to_a.dig(-1, -2, 0) == :setconstant
=> true
pry> RubyVM::InstructionSequence.compile("_FOO = 42", nil, nil, 0, false).to_a.dig(-1, -2, 0) == :setconstant
=> false
pry> RubyVM::InstructionSequence.compile("FOO = 42", nil, nil, 0, false).to_a.dig(-1, -2, 0) == :setconstant
=> true
pry> RubyVM::InstructionSequence.compile("Ҷ = 42", nil, nil, 0, false).to_a.dig(-1, -2, 0) == :setconstant
=> true
# When the user types in the first letter of a constant name, we actually receive the position of the next | ||
# immediate character. We check to see if the character is uppercase and then remove the offset to try to locate | ||
# the node, as it could not be a constant | ||
target_node_types = if ("A".."Z").cover?(document.source[char_position - 1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or just _FOO
Constant::CompletionItemKind::REFERENCE | ||
end | ||
|
||
insertion_text = first_entry.name.dup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this dup necessary? Is name
mutable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're mutating it on the lines below. If we don't dup
it, we end up mutating the index entry itself.
We were actually bit by this in the past
a = first_entry.name # => Foo::Bar
a.delete_prefix!("Foo::")
first_entry.name # => Bar
0647342
to
357a126
Compare
934956b
to
4605f6e
Compare
4605f6e
to
d6dbb33
Compare
The example config as show would result in one config key being overwritten by the other so I thought it would be better to simplify the example to show one instance of the `rubyLsp.bundleGemfile` key and adding a note about relative and absolute path support.
Motivation
Closes #61
Add completion for classes, modules and constants.
Implementation
I'll explain the changes per file
Executor
: when a completion request is triggered, we receive the position for the character immediately after the letter the user just typed. So I started checking if the previous character (the one just inserted) is an uppercase character and then we go down the path of completion for classes, modules and constants. Otherwise, we do the previous path completion. Also started passing thenesting
to the completion request and adapted our capabilitiesCompletion
: started handlingconst
andconst_path_ref
to provide completion for both namespaced and regular constants. These events use theprefix_search
added in Add entries prefix tree #955 and then build the completions. I also extracted the logic to build markdown content from hover intoCommon
, so that we can display the exact same documentation on both hover and completion (the documentation shows up on the side when selecting a completion)Other than that, I just renamed the request to be more generic, using
Completion
instead ofPathCompletion
.Automated Tests
Added tests for both const and const path refs.
Manual Tests
"rubyLsp.branch": "vs/add_autocomplete"