holistic-ruby 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/lib/holistic/application.rb +12 -4
  4. data/lib/holistic/database/migrations.rb +20 -0
  5. data/lib/holistic/database/node.rb +29 -0
  6. data/lib/holistic/database/table.rb +53 -53
  7. data/lib/holistic/document/file/record.rb +10 -0
  8. data/lib/holistic/document/file/repository.rb +24 -0
  9. data/lib/holistic/document/file/store.rb +13 -0
  10. data/lib/holistic/document/location.rb +4 -6
  11. data/lib/holistic/document/unsaved/record.rb +0 -4
  12. data/lib/holistic/extensions/ruby/stdlib.rb +6 -6
  13. data/lib/holistic/language_server/requests/lifecycle/initialize.rb +5 -10
  14. data/lib/holistic/language_server/requests/text_document/completion.rb +5 -4
  15. data/lib/holistic/language_server/requests/text_document/did_close.rb +5 -9
  16. data/lib/holistic/language_server/requests/text_document/did_open.rb +1 -0
  17. data/lib/holistic/language_server/requests/text_document/did_save.rb +5 -9
  18. data/lib/holistic/language_server/requests/text_document/find_references.rb +7 -4
  19. data/lib/holistic/language_server/requests/text_document/go_to_definition.rb +3 -2
  20. data/lib/holistic/ruby/autocompletion/suggest.rb +40 -20
  21. data/lib/holistic/ruby/parser/constant_resolution.rb +8 -8
  22. data/lib/holistic/ruby/parser/live_editing/process_file_changed.rb +21 -21
  23. data/lib/holistic/ruby/parser/program_visitor.rb +21 -24
  24. data/lib/holistic/ruby/parser.rb +8 -11
  25. data/lib/holistic/ruby/reference/delete.rb +18 -0
  26. data/lib/holistic/ruby/reference/find_referenced_scope.rb +2 -2
  27. data/lib/holistic/ruby/reference/record.rb +7 -8
  28. data/lib/holistic/ruby/reference/repository.rb +19 -41
  29. data/lib/holistic/ruby/reference/store.rb +18 -0
  30. data/lib/holistic/ruby/scope/delete.rb +29 -0
  31. data/lib/holistic/ruby/scope/lexical.rb +11 -0
  32. data/lib/holistic/ruby/scope/list_references.rb +2 -2
  33. data/lib/holistic/ruby/scope/location.rb +9 -9
  34. data/lib/holistic/ruby/scope/outline.rb +8 -8
  35. data/lib/holistic/ruby/scope/record.rb +15 -51
  36. data/lib/holistic/ruby/scope/repository.rb +24 -25
  37. data/lib/holistic/ruby/scope/store.rb +45 -0
  38. data/lib/holistic/ruby/type_inference/processing_queue.rb +19 -0
  39. data/lib/holistic/ruby/type_inference/solve.rb +16 -25
  40. data/lib/holistic/ruby/type_inference/solve_pending_references.rb +3 -1
  41. data/lib/holistic/version.rb +1 -1
  42. metadata +13 -9
  43. data/lib/holistic/document/file.rb +0 -36
  44. data/lib/holistic/ruby/parser/table_of_contents.rb +0 -17
  45. data/lib/holistic/ruby/reference/register.rb +0 -15
  46. data/lib/holistic/ruby/reference/unregister.rb +0 -11
  47. data/lib/holistic/ruby/scope/register.rb +0 -31
  48. data/lib/holistic/ruby/scope/unregister.rb +0 -27
  49. data/lib/holistic/ruby/type_inference/conclusion.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: abe4144d1f581c6151784152792604f799c0140d62c28f445f377f6039c40457
4
- data.tar.gz: 6593b92046a8087f75c171280ae66401ff0bb1eb0f6dba9fa496df127bb39ee8
3
+ metadata.gz: 43d41e307a4ae8b8b904fda778342ef5d175db25580697f597f606f3aa2a0173
4
+ data.tar.gz: 5286406cf4e0f5a5cb21745b3f7d49268eb934e4002e76d1ba43f9dfdda034fd
5
5
  SHA512:
6
- metadata.gz: eb1f43eaa7f78c334da3f8d8f2fc782bfa662b94f8a5a6b5a8a24ab271cae26d62703bd722860dbecf637d9aae5d4595d1424257406d9d349f0d90a95ab702d4
7
- data.tar.gz: f42ed13efc65779f8ef175c7127d03cf68bebd5df990a1744d714100efe639cad104b55592507d4737ecfa4d48e75d6b69da194a033f056740a16fde8bf3d65d
6
+ metadata.gz: 882cac6ec7c32d309898aab43f001a3fcf0e497b6c019569472db3880b792e95fe50158b8672690c80237e0a9c6ac361ede5b79a09e2784ddc944d103c54eaba
7
+ data.tar.gz: 64e1becad926aba30e7c9f25b294a2ff9b53dcdfe7affc5e59792054cf21b17b3509eb7fc80e1819fecc9f7d7e22957df3bda2c03990b9e77476c3bc1fa956e0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- holistic-ruby (0.1.0)
4
+ holistic-ruby (0.1.4)
5
5
  activesupport (~> 7.0)
6
6
  syntax_tree (~> 6.0)
7
7
  zeitwerk (~> 2.6)
@@ -2,12 +2,12 @@
2
2
 
3
3
  module Holistic
4
4
  class Application
5
- attr_reader :name, :root_directory, :root_scope
5
+ attr_reader :name, :root_directory, :database
6
6
 
7
7
  def initialize(name:, root_directory:)
8
8
  @name = name
9
9
  @root_directory = root_directory
10
- @root_scope = Ruby::Scope::Record.new(kind: Ruby::Scope::Kind::ROOT, name: "::", parent: nil)
10
+ @database = Database::Table.new.tap(&Database::Migrations::Run)
11
11
  end
12
12
 
13
13
  def extensions
@@ -15,11 +15,19 @@ module Holistic
15
15
  end
16
16
 
17
17
  def scopes
18
- @scopes ||= Ruby::Scope::Repository.new
18
+ @scopes ||= Ruby::Scope::Repository.new(database:)
19
19
  end
20
20
 
21
21
  def references
22
- @references ||= Ruby::Reference::Repository.new
22
+ @references ||= Ruby::Reference::Repository.new(database:)
23
+ end
24
+
25
+ def files
26
+ @files ||= Document::File::Repository.new(database:)
27
+ end
28
+
29
+ def type_inference_processing_queue
30
+ @type_inference_processing_queue ||= Ruby::TypeInference::ProcessingQueue.new
23
31
  end
24
32
 
25
33
  def unsaved_documents
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Database::Migrations
4
+ Run = ->(database) do
5
+ # scope parent-children relation
6
+ database.define_connection(name: :children, inverse_of: :parent)
7
+
8
+ # type inference conclusion
9
+ database.define_connection(name: :referenced_scope, inverse_of: :referenced_by)
10
+
11
+ # reference definition
12
+ database.define_connection(name: :located_in_scope, inverse_of: :contains_many_references)
13
+
14
+ # scope location in files
15
+ database.define_connection(name: :defines_scopes, inverse_of: :scope_defined_in_file)
16
+
17
+ # reference location in files
18
+ database.define_connection(name: :defines_references, inverse_of: :reference_defined_in_file)
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Database
4
+ class Node
5
+ attr_accessor :attributes, :connections, :__database__
6
+
7
+ def initialize(id, attributes)
8
+ @id = id
9
+ @attributes = attributes
10
+ @connections = ::Hash.new { |hash, key| hash[key] = ::Set.new }
11
+ end
12
+
13
+ def attr(attribute_name)
14
+ @attributes[attribute_name]
15
+ end
16
+
17
+ def has_many(connection_name)
18
+ @connections[connection_name].to_a
19
+ end
20
+
21
+ def has_one(connection_name)
22
+ @connections[connection_name].first
23
+ end
24
+
25
+ def __set_database__(database)
26
+ @__database__ = database
27
+ end
28
+ end
29
+ end
@@ -1,78 +1,78 @@
1
- # frozen_string_literal: true
2
-
3
- class Holistic::Database::Table
4
- attr_reader :primary_attribute, :primary_index, :secondary_indices
5
-
6
- def initialize(primary_attribute:, indices: [])
7
- @primary_attribute = primary_attribute
8
1
 
9
- @primary_index = ::Hash.new
2
+ # frozen_string_literal: true
10
3
 
11
- @secondary_indices = indices.map do |attribute_name|
12
- [attribute_name, ::Hash.new { |hash, key| hash[key] = ::Set.new }]
13
- end.to_h
14
- end
4
+ module Holistic::Database
5
+ class Table
6
+ attr_reader :records, :connections
15
7
 
16
- RecordNotUniqueError = ::Class.new(::StandardError)
8
+ def initialize
9
+ @records = ::Hash.new
10
+ @connections = ::Hash.new
11
+ end
17
12
 
18
- def insert(record)
19
- primary_key = record.fetch(primary_attribute)
13
+ def define_connection(name:, inverse_of:)
14
+ raise ::ArgumentError if @connections.key?(name) || @connections.key?(inverse_of)
20
15
 
21
- if primary_index.key?(primary_key)
22
- raise RecordNotUniqueError, "record already inserted: #{record.inspect}"
16
+ @connections[name] = { inverse_of: }
17
+ @connections[inverse_of] = { inverse_of: name }
23
18
  end
24
19
 
25
- primary_index[primary_key] = record
26
-
27
- secondary_indices.each do |attribute_name, secondary_index|
28
- Array(record[attribute_name]).each do |value|
29
- secondary_index[value].add(primary_key)
20
+ def store(id, node_or_attrs)
21
+ if @records.key?(id)
22
+ return @records[id]&.tap do |node|
23
+ node.attributes =
24
+ case node_or_attrs
25
+ in ::Hash then node_or_attrs
26
+ in Node then node_or_attrs.attributes
27
+ end
28
+ end
30
29
  end
31
- end
32
- end
33
30
 
34
- def find(identifier)
35
- primary_index[identifier]
36
- end
31
+ node =
32
+ case node_or_attrs
33
+ in ::Hash then Node.new(id, node_or_attrs)
34
+ in Node then node_or_attrs
35
+ end
37
36
 
38
- def filter(name, value)
39
- return [] unless secondary_indices[name].key?(value)
37
+ node.__set_database__(self)
40
38
 
41
- secondary_indices.dig(name, value).to_a.map { find(_1) }
42
- end
43
-
44
- def update(record)
45
- primary_key = record.fetch(primary_attribute)
39
+ @records[id] = node
40
+ end
46
41
 
47
- delete(primary_key)
42
+ def connect(source:, target:, name:, inverse_of:)
43
+ connection = @connections[name]
48
44
 
49
- insert(record)
50
- end
45
+ raise ::ArgumentError if connection.nil? || connection[:inverse_of] != inverse_of
51
46
 
52
- def delete(primary_key)
53
- record = find(primary_key)
47
+ source.connections[name].add(target)
48
+ target.connections[inverse_of].add(source)
49
+ end
54
50
 
55
- return if record.nil?
51
+ def disconnect(source:, target:, name:, inverse_of:)
52
+ connection = @connections[name]
56
53
 
57
- primary_index.delete(primary_key)
54
+ raise ::ArgumentError if connection.nil? || connection[:inverse_of] != inverse_of
58
55
 
59
- secondary_indices.each do |attribute_name, index_data|
60
- Array(record[attribute_name]).each do |value|
61
- index_data[value].delete(primary_key)
62
- index_data.delete(value) if index_data[value].empty?
63
- end
56
+ source.connections[name].delete(target)
57
+ target.connections[inverse_of].delete(source)
64
58
  end
65
59
 
66
- record
67
- end
60
+ def find(id)
61
+ @records[id]
62
+ end
68
63
 
69
- concerning :TestHelpers do
70
- def all
71
- primary_index.values
64
+ def delete(id)
65
+ records.delete(id)
72
66
  end
73
67
 
74
- def size
75
- primary_index.size
68
+ concerning :TestHelpers do
69
+ def all
70
+ records.values
71
+ end
72
+
73
+ def size
74
+ records.size
75
+ end
76
76
  end
77
77
  end
78
78
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Document::File
4
+ class Record < ::Holistic::Database::Node
5
+ def path = attr(:path)
6
+
7
+ def defines_scopes = has_many(:defines_scopes)
8
+ def defines_references = has_many(:defines_references)
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ::Holistic::Document::File
4
+ class Repository
5
+ attr_reader :database
6
+
7
+ def initialize(database:)
8
+ @database = database
9
+ end
10
+
11
+ # rename to `find_file`
12
+ def find(file_path)
13
+ @database.find(file_path)
14
+ end
15
+
16
+ concerning :TestHelpers do
17
+ def build_fake_location(file_path)
18
+ file = Store.call(database:, file_path:)
19
+
20
+ ::Holistic::Document::Location.new(file, 0, 0, 0, 0)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Document::File
4
+ module Store
5
+ extend self
6
+
7
+ def call(database:, file_path:)
8
+ record = Record.new(file_path, { path: file_path })
9
+
10
+ database.store(file_path, record)
11
+ end
12
+ end
13
+ end
@@ -2,20 +2,18 @@
2
2
 
3
3
  module Holistic::Document
4
4
  Location = ::Data.define(
5
- :file_path,
5
+ :file,
6
6
  :start_line,
7
7
  :start_column,
8
8
  :end_line,
9
9
  :end_column
10
10
  ) do
11
- def self.beginning_of_file(file_path)
12
- new(file_path, 0, 0, 0, 0)
11
+ def identifier
12
+ "#{file.path}[#{start_line},#{start_column},#{end_line},#{end_column}]"
13
13
  end
14
14
 
15
- def identifier = "#{file_path}[#{start_line},#{start_column},#{end_line},#{end_column}]"
16
-
17
15
  def contains?(cursor)
18
- same_file = cursor.file_path == file_path
16
+ same_file = cursor.file_path == file.path
19
17
  contains_line = cursor.line >= start_line && cursor.line <= end_line
20
18
 
21
19
  contains_column =
@@ -75,9 +75,5 @@ module Holistic::Document
75
75
  end
76
76
  end
77
77
  end
78
-
79
- def to_file
80
- File::Fake.new(path:, content:)
81
- end
82
78
  end
83
79
  end
@@ -10,7 +10,7 @@ module Holistic::Extensions::Ruby
10
10
  if method_call_clue.method_name == "new" && referenced_scope.class?
11
11
  initialize_method = "#{referenced_scope.fully_qualified_name}#initialize"
12
12
 
13
- return application.scopes.find_by_fully_qualified_name(initialize_method)
13
+ return application.scopes.find(initialize_method)
14
14
  end
15
15
 
16
16
  nil
@@ -19,11 +19,11 @@ module Holistic::Extensions::Ruby
19
19
  RegisterClassConstructor = ->(application, params) do
20
20
  class_scope, location = params[:class_scope], params[:location]
21
21
 
22
- has_overridden_new_method = class_scope.children.find { _1.class_method? && _1.name == "new" }
22
+ has_overridden_new_method = class_scope.children.find { _1.instance_method? && _1.name == "initialize" }
23
23
 
24
24
  unless has_overridden_new_method
25
- ::Holistic::Ruby::Scope::Register.call(
26
- repository: application.scopes,
25
+ ::Holistic::Ruby::Scope::Store.call(
26
+ database: application.database,
27
27
  parent: class_scope,
28
28
  kind: ::Holistic::Ruby::Scope::Kind::CLASS_METHOD,
29
29
  name: "new",
@@ -38,8 +38,8 @@ module Holistic::Extensions::Ruby
38
38
  lambda_scope, location = params[:lambda_scope], params[:location]
39
39
 
40
40
  LAMBDA_METHODS.each do |method_name|
41
- ::Holistic::Ruby::Scope::Register.call(
42
- repository: application.scopes,
41
+ ::Holistic::Ruby::Scope::Store.call(
42
+ database: application.database,
43
43
  parent: lambda_scope,
44
44
  kind: ::Holistic::Ruby::Scope::Kind::CLASS_METHOD,
45
45
  name: method_name,
@@ -14,23 +14,18 @@ module Holistic::LanguageServer
14
14
 
15
15
  parse_application_in_background(application)
16
16
 
17
- respond_with_holistic_capabilities(request)
17
+ respond_with_language_server_capabilities(request)
18
18
  end
19
19
 
20
20
  private
21
21
 
22
22
  def create_application(request)
23
- ::Holistic.logger.info("===========")
24
- ::Holistic.logger.info(request.message.inspect)
25
-
26
23
  root_directory = request.param("rootPath")
27
24
  name = ::File.basename(root_directory)
28
25
 
29
- Current.application = ::Holistic::Application.new(name:, root_directory:)
30
-
31
- ::Holistic::Extensions::Ruby::Stdlib.register(Current.application)
32
-
33
- Current.application
26
+ Current.application = ::Holistic::Application.new(name:, root_directory:).tap do |application|
27
+ ::Holistic::Extensions::Ruby::Stdlib.register(application)
28
+ end
34
29
  end
35
30
 
36
31
  def advance_lifecycle_state
@@ -45,7 +40,7 @@ module Holistic::LanguageServer
45
40
  end
46
41
  end
47
42
 
48
- def respond_with_holistic_capabilities(request)
43
+ def respond_with_language_server_capabilities(request)
49
44
  request.respond_with({
50
45
  capabilities: {
51
46
  # Defines how the host (editor) should sync document changes to the language server.
@@ -8,21 +8,22 @@ module Holistic::LanguageServer
8
8
  cursor = build_cursor_from_request_params(request)
9
9
 
10
10
  document = request.application.unsaved_documents.find(cursor.file_path)
11
-
11
+
12
12
  return request.respond_with(nil) if document.nil?
13
13
 
14
14
  if document.has_unsaved_changes?
15
15
  ::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(
16
16
  application: request.application,
17
- file: document.to_file
17
+ file_path: document.path,
18
+ content: document.content
18
19
  )
19
20
  end
20
21
 
21
22
  code = document.expand_code(cursor)
22
-
23
+
23
24
  return request.respond_with(nil) if code.blank?
24
25
 
25
- scope = request.application.scopes.find_inner_most_scope_by_cursor(cursor) || request.application.root_scope
26
+ scope = request.application.scopes.find_inner_most_scope_by_cursor(cursor) || request.application.scopes.root
26
27
 
27
28
  suggestions = ::Holistic::Ruby::Autocompletion::Suggest.call(code:, scope:)
28
29
 
@@ -13,19 +13,15 @@ module Holistic::LanguageServer
13
13
  if unsaved_document.has_unsaved_changes?
14
14
  unsaved_document.restore_original_content!
15
15
 
16
- process_in_background(application: request.application, file: unsaved_document.to_file)
16
+ ::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(
17
+ application: request.application,
18
+ file_path: unsaved_document.path,
19
+ content: unsaved_document.content
20
+ )
17
21
  end
18
22
  end
19
23
 
20
24
  request.respond_with(nil)
21
25
  end
22
-
23
- private
24
-
25
- def process_in_background(application:, file:)
26
- ::Holistic::BackgroundProcess.run do
27
- ::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(application:, file:)
28
- end
29
- end
30
26
  end
31
27
  end
@@ -9,6 +9,7 @@ module Holistic::LanguageServer
9
9
  content = request.message.param("textDocument", "text")
10
10
 
11
11
  request.application.unsaved_documents.add(path:, content:)
12
+ ::Holistic::Document::File::Store.call(database: request.application.database, file_path: path)
12
13
 
13
14
  request.respond_with(nil)
14
15
  end
@@ -17,17 +17,13 @@ module Holistic::LanguageServer
17
17
 
18
18
  unsaved_document.mark_as_saved!
19
19
 
20
- process_in_background(application: request.application, file: unsaved_document.to_file)
20
+ ::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(
21
+ application: request.application,
22
+ file_path: unsaved_document.path,
23
+ content: unsaved_document.content
24
+ )
21
25
 
22
26
  request.respond_with(nil)
23
27
  end
24
-
25
- private
26
-
27
- def process_in_background(application:, file:)
28
- ::Holistic::BackgroundProcess.run do
29
- ::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(application:, file:)
30
- end
31
- end
32
28
  end
33
29
  end
@@ -12,7 +12,8 @@ module Holistic::LanguageServer
12
12
  if unsaved_document.has_unsaved_changes?
13
13
  ::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(
14
14
  application: request.application,
15
- file: unsaved_document.to_file
15
+ file_path: unsaved_document.path,
16
+ content: unsaved_document.content
16
17
  )
17
18
  end
18
19
  end
@@ -37,11 +38,13 @@ module Holistic::LanguageServer
37
38
 
38
39
  def respond_with_locations(request, references)
39
40
  locations = references.map do |reference|
41
+ location = reference.location
42
+
40
43
  {
41
- "uri" => Format::FileUri.from_path(reference.location.file_path),
44
+ "uri" => Format::FileUri.from_path(location.file.path),
42
45
  "range" => {
43
- "start" => { "line" => reference.location.start_line, "character" => reference.location.start_column },
44
- "end" => { "line" => reference.location.end_line, "character" => reference.location.end_column }
46
+ "start" => { "line" => location.start_line, "character" => location.start_column },
47
+ "end" => { "line" => location.end_line, "character" => location.end_column }
45
48
  }
46
49
  }
47
50
  end
@@ -12,7 +12,8 @@ module Holistic::LanguageServer
12
12
  if unsaved_document.has_unsaved_changes?
13
13
  ::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(
14
14
  application: request.application,
15
- file: unsaved_document.to_file
15
+ file_path: unsaved_document.path,
16
+ content: unsaved_document.content
16
17
  )
17
18
  end
18
19
  end
@@ -46,7 +47,7 @@ module Holistic::LanguageServer
46
47
  "start" => { "line" => origin_location.start_line, "character" => origin_location.start_column },
47
48
  "end" => { "line" => origin_location.end_line, "character" => origin_location.end_column }
48
49
  },
49
- "targetUri" => Format::FileUri.from_path(target_declaration_location.file_path),
50
+ "targetUri" => Format::FileUri.from_path(target_declaration_location.file.path),
50
51
  "targetRange" => {
51
52
  "start" => { "line" => target_declaration_location.start_line, "character" => target_declaration_location.start_column },
52
53
  "end" => { "line" => target_declaration_location.end_line, "character" => target_declaration_location.end_column }
@@ -6,6 +6,12 @@ module Holistic::Ruby::Autocompletion
6
6
 
7
7
  Suggestion = ::Data.define(:code, :kind)
8
8
 
9
+ StartsWithLowerCaseLetter = ->(code) do
10
+ return false if [".", ":", "@"].include?(code[0])
11
+
12
+ code[0] == code[0].downcase
13
+ end
14
+
9
15
  def call(code:, scope:)
10
16
  lookup_scope = scope
11
17
 
@@ -13,7 +19,9 @@ module Holistic::Ruby::Autocompletion
13
19
  lookup_scope = lookup_scope.parent until lookup_scope.root?
14
20
  end
15
21
 
16
- if code.include?(".")
22
+ if StartsWithLowerCaseLetter[code]
23
+ suggest_local_methods_from_current_scope(code:, scope: lookup_scope)
24
+ elsif code.include?(".")
17
25
  suggest_methods_from_scope(code:, scope: lookup_scope)
18
26
  else
19
27
  suggest_namespaces_from_scope(code:, scope: lookup_scope)
@@ -22,12 +30,32 @@ module Holistic::Ruby::Autocompletion
22
30
 
23
31
  private
24
32
 
25
- NonMethods = ->(scope) { !Methods[scope] }
26
- Methods = ->(scope) { scope.instance_method? || scope.class_method? }
33
+ def suggest_local_methods_from_current_scope(code:, scope:)
34
+ suggestions = []
35
+
36
+ method_to_autocomplete = code
37
+
38
+ if scope.instance_method?
39
+ sibling_methods = scope.parent.children.filter { _1.instance_method? }
40
+
41
+ sibling_methods.each do |method_scope|
42
+ if method_scope.name.start_with?(method_to_autocomplete)
43
+ suggestions << Suggestion.new(code: method_scope.name, kind: method_scope.kind)
44
+ end
45
+ end
46
+ elsif scope.class_method?
47
+ sibling_methods = scope.parent.children.filter { _1.class_method? }
48
+
49
+ sibling_methods.each do |method_scope|
50
+ if method_scope.name.start_with?(method_to_autocomplete)
51
+ suggestions << Suggestion.new(code: method_scope.name, kind: method_scope.kind)
52
+ end
53
+ end
54
+ end
55
+
56
+ suggestions
57
+ end
27
58
 
28
- # Payment. <--
29
- # Payment::Notifications. <--
30
- # current_user.a
31
59
  def suggest_methods_from_scope(code:, scope:)
32
60
  suggestions = []
33
61
 
@@ -41,7 +69,9 @@ module Holistic::Ruby::Autocompletion
41
69
  return suggestions if scope.nil?
42
70
  end
43
71
 
44
- scope.children.filter(&:class_method?).each do |method_scope|
72
+ class_methods = scope.children.filter { _1.class_method? }
73
+
74
+ class_methods.each do |method_scope|
45
75
  if method_scope.name.start_with?(method_to_autocomplete)
46
76
  suggestions << Suggestion.new(code: method_scope.name, kind: method_scope.kind)
47
77
  end
@@ -63,22 +93,12 @@ module Holistic::Ruby::Autocompletion
63
93
  return suggestions if scope.nil?
64
94
  end
65
95
 
66
- # special case when user did not type :: at the end but the current word
67
- # is matches an existing namespace. In this case, suggestions will start with ::.
68
- # For example:
69
- #
70
- # \/ cursor here
71
- # typing: "::MyApp::Payments"
72
- # suggestions: ["::Record", "::SendReminder"]
73
- resolve_scope(name: namespace_to_autocomplete, from_scope: scope)&.then do |fully_typed_scope|
74
- scope = fully_typed_scope
75
- namespace_to_autocomplete = ""
76
- end
77
-
78
96
  should_search_upwards = namespaces_to_resolve.empty?
79
97
 
80
98
  search = ->(scope) do
81
- scope.children.filter(&NonMethods).each do |child_scope|
99
+ scope.children.each do |child_scope|
100
+ next if child_scope.method?
101
+
82
102
  if child_scope.name.start_with?(namespace_to_autocomplete)
83
103
  suggestions << Suggestion.new(code: child_scope.name, kind: child_scope.kind)
84
104
  end