Skip to content

Commit

Permalink
Add completion support
Browse files Browse the repository at this point in the history
  • Loading branch information
r7kamura committed Sep 11, 2022
1 parent 0582d53 commit 3d193e4
Show file tree
Hide file tree
Showing 9 changed files with 458 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -65,6 +65,10 @@ This extension supports the folowiing types of symbols:

![demo](images/document-symbol.gif)

### Completion (experimental)

Provides completion items for constant names and method names.

### Signature help (experimental)

Shows method signature help when you start to type method arguments like `"100".to_i(`.
Expand Down
10 changes: 10 additions & 0 deletions lib/rucoa/configuration.rb
Expand Up @@ -11,6 +11,11 @@ def disable_code_action
disable_feature('codeAction')
end

# @return [void]
def disable_completion
disable_feature('completion')
end

# @return [void]
def disable_diagnostics
disable_feature('diagnostics')
Expand Down Expand Up @@ -67,6 +72,11 @@ def enables_code_action?
enables_feature?('codeAction')
end

# @return [Boolean]
def enables_completion?
enables_feature?('completion')
end

# @return [Boolean]
def enables_diagnostics?
enables_feature?('diagnostics')
Expand Down
1 change: 1 addition & 0 deletions lib/rucoa/handlers.rb
Expand Up @@ -8,6 +8,7 @@ module Handlers
autoload :InitializedHandler, 'rucoa/handlers/initialized_handler'
autoload :ShutdownHandler, 'rucoa/handlers/shutdown_handler'
autoload :TextDocumentCodeActionHandler, 'rucoa/handlers/text_document_code_action_handler'
autoload :TextDocumentCompletionHandler, 'rucoa/handlers/text_document_completion_handler'
autoload :TextDocumentDidChangeHandler, 'rucoa/handlers/text_document_did_change_handler'
autoload :TextDocumentDidOpenHandler, 'rucoa/handlers/text_document_did_open_handler'
autoload :TextDocumentDocumentSymbolHandler, 'rucoa/handlers/text_document_document_symbol_handler'
Expand Down
6 changes: 6 additions & 0 deletions lib/rucoa/handlers/initialize_handler.rb
Expand Up @@ -7,6 +7,12 @@ def call
respond(
capabilities: {
codeActionProvider: true,
completionProvider: {
resolveProvider: true,
triggerCharacters: %w[
.
]
},
documentFormattingProvider: true,
documentRangeFormattingProvider: true,
documentSymbolProvider: true,
Expand Down
216 changes: 216 additions & 0 deletions lib/rucoa/handlers/text_document_completion_handler.rb
@@ -0,0 +1,216 @@
# frozen_string_literal: true

module Rucoa
module Handlers
class TextDocumentCompletionHandler < Base
COMPLETION_ITEM_KIND_FOR_TEXT = 1
COMPLETION_ITEM_KIND_FOR_METHOD = 2
COMPLETION_ITEM_KIND_FOR_FUNCTION = 3
COMPLETION_ITEM_KIND_FOR_CONSTRUCTOR = 4
COMPLETION_ITEM_KIND_FOR_FIELD = 5
COMPLETION_ITEM_KIND_FOR_VARIABLE = 6
COMPLETION_ITEM_KIND_FOR_CLASS = 7
COMPLETION_ITEM_KIND_FOR_INTERFACE = 8
COMPLETION_ITEM_KIND_FOR_MODULE = 9
COMPLETION_ITEM_KIND_FOR_PROPERTY = 10
COMPLETION_ITEM_KIND_FOR_UNIT = 11
COMPLETION_ITEM_KIND_FOR_VALUE = 12
COMPLETION_ITEM_KIND_FOR_ENUM = 13
COMPLETION_ITEM_KIND_FOR_KEYWORD = 14
COMPLETION_ITEM_KIND_FOR_SNIPPET = 15
COMPLETION_ITEM_KIND_FOR_COLOR = 16
COMPLETION_ITEM_KIND_FOR_FILE = 17
COMPLETION_ITEM_KIND_FOR_REFERENCE = 18
COMPLETION_ITEM_KIND_FOR_FOLDER = 19
COMPLETION_ITEM_KIND_FOR_ENUM_MEMBER = 20
COMPLETION_ITEM_KIND_FOR_CONSTANT = 21
COMPLETION_ITEM_KIND_FOR_STRUCT = 22
COMPLETION_ITEM_KIND_FOR_EVENT = 23
COMPLETION_ITEM_KIND_FOR_OPERATOR = 24
COMPLETION_ITEM_KIND_FOR_TYPE_PARAMETER = 25

EXAMPLE_IDENTIFIER = 'a'
private_constant :EXAMPLE_IDENTIFIER

def call
respond(completion_items)
end

private

# @return [Array<Hash>, nil]
def completion_items
return unless responsible?

case node
when Nodes::ConstNode
completion_items_for_constant
when Nodes::SendNode
if node.location.dot&.is?('::')
completion_items_for_constant
else
completion_items_for_method
end
else
[]
end
end

# @return [Boolean]
def responsible?
configuration.enables_completion? &&
!source.nil?
end

# @return [Rucoa::Source, nil]
def source
@source ||= source_store.get(uri)
end

# @return [Rucoa::Position]
def position
@position ||= Position.from_vscode_position(
request.dig('params', 'position')
)
end

# @return [String]
def uri
request.dig('params', 'textDocument', 'uri')
end

# @return [Array<Hash>]
def completion_items_for_method
completable_method_names.map do |method_name|
{
kind: COMPLETION_ITEM_KIND_FOR_METHOD,
label: method_name,
textEdit: {
newText: method_name,
range: range.to_vscode_range
}
}
end
end

# @return [Array<Hash>]
def completion_items_for_constant
completable_constant_names.map do |constant_name|
{
kind: COMPLETION_ITEM_KIND_FOR_CONSTANT,
label: constant_name,
textEdit: {
newText: constant_name,
range: range.to_vscode_range
}
}
end
end

# @return [Array<String>]
def completable_constant_names
referrable_constant_names.select do |constant_name|
constant_name.start_with?(completion_head)
end.sort
end

# @return [String] e.g. "SE" to `File::SE|`, "ba" to `foo.ba|`
def completion_head
@completion_head ||=
if @repaired
''
else
node.name
end
end

def referrable_constant_names
definition_store.constant_definitions_under(constant_namespace).map(&:name).uniq
end

# @return [String] e.g. "Foo::Bar" to `Foo::Bar.baz|`.
def constant_namespace
node.each_child_node(:const).map(&:name).reverse.join('::')
end

# @return [Array<String>]
def completable_method_names
callable_method_names.select do |method_name|
method_name.start_with?(completion_head)
end.sort
end

# @return [Array<String>]
def callable_method_names
callable_method_definitions.map(&:method_name).uniq
end

# @return [Array<String>]
def callable_method_definitions
receiver_types.flat_map do |type|
definition_store.method_definitions_of(type)
end
end

# @return [Array<String>]
def receiver_types
NodeInspector.new(
definition_store: definition_store,
node: node
).method_receiver_types
end

# @return [Rucoa::Node, nil]
def node
@node ||=
if source.syntax_error?
repair
repaired_node
else
normal_node
end
end

# @return [Rucoa::Node, nil]
def normal_node
source.node_at(position)
end

# @return [Rucoa::Node, nil]
def repaired_node
repaired_source.node_at(position)
end

# @return [void]
def repair
@repaired = true
end

# @return [String]
def repaired_content
source.content.dup.insert(
position.to_index_of(source.content),
EXAMPLE_IDENTIFIER
)
end

# @return [Rucoa::Source]
def repaired_source
Source.new(
content: repaired_content,
uri: source.uri
)
end

# @return [Rucoa::Range]
def range
@range ||=
if @repaired
position.to_range
else
Range.from_parser_range(node.location.expression)
end
end
end
end
end
11 changes: 11 additions & 0 deletions lib/rucoa/position.rb
Expand Up @@ -44,6 +44,17 @@ def initialize(column:, line:)
@line = line
end

# @param text [String]
# @return [Integer]
def to_index_of(text)
text.each_line.take(@line - 1).sum(&:length) + @column
end

# @return [Rucoa::Range]
def to_range
Range.new(self, self)
end

# @return [Hash]
def to_vscode_position
{
Expand Down
1 change: 1 addition & 0 deletions lib/rucoa/server.rb
Expand Up @@ -12,6 +12,7 @@ class Server
'initialized' => Handlers::InitializedHandler,
'shutdown' => Handlers::ShutdownHandler,
'textDocument/codeAction' => Handlers::TextDocumentCodeActionHandler,
'textDocument/completion' => Handlers::TextDocumentCompletionHandler,
'textDocument/didChange' => Handlers::TextDocumentDidChangeHandler,
'textDocument/didOpen' => Handlers::TextDocumentDidOpenHandler,
'textDocument/documentSymbol' => Handlers::TextDocumentDocumentSymbolHandler,
Expand Down
5 changes: 5 additions & 0 deletions lib/rucoa/source.rb
Expand Up @@ -78,6 +78,11 @@ def root_node
nil
end

# @return [Boolean]
def syntax_error?
root_node.nil?
end

private

# @return [Array<Rucoa::Nodes::Base>]
Expand Down

0 comments on commit 3d193e4

Please sign in to comment.