ruby-lsp-ree 0.1.0 → 0.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 925a5297663b9b4e653e9c837cd6bfc8942c5445f657619f867699b33e765809
4
- data.tar.gz: 3d3d73751c5c60d3b55ab426b896dcfcc633c921ab0b33ca7ba1aea4b1ae04dd
3
+ metadata.gz: 350bb1ae1a376f2a8594e3945c396c49d01907b1c840d36311e28761c2fe9d8e
4
+ data.tar.gz: dfa9bc947cf5f951d62e2686290a7ac603c5dfa34652cb21be4b1547d2e3b5e1
5
5
  SHA512:
6
- metadata.gz: 46a0c311c26ffe7bea0bc546476cf92a7b45f620b1d38c261be5c409b4c75b0249bfea393bc532166b461d2373491f1093bd1be3afa44b1dd2530ae67f8cf77f
7
- data.tar.gz: e150468d50597f9be7988891972bd708710ecc2991dadb512c36bada2bcbf4f619873435744a4b7f0b95f6eda7dc180d8e4edeec9c487de4e5084be99c59c0b8
6
+ metadata.gz: eaaa38d222082eb2a55f73a977114a2632d2db2a2b7d10a0d96d2284b87fbd2c02bcab43e45ec617afa63e5c9fd4f9e64339f7cb919ba23365a5deae1aa3df4e
7
+ data.tar.gz: 64e7ce28fdb802eadbe27c6f7fd5d5e156c9c3d39627c9a7e191bf808fc9519eb26befa49182cc62bda12941c8e6fd4f478060ec61e33d38035aa10726c087d1
data/CHANGELOG.md CHANGED
@@ -3,3 +3,20 @@
3
3
  ## [0.1.0] - 2025-01-31
4
4
 
5
5
  - Initial release
6
+
7
+ ## [0.1.1] - 2025-02-10
8
+
9
+ - sort links for objects with FnDSL
10
+ - autocomplete for objects with FnDSL
11
+ - Add Link for objects with FnDSL
12
+ - improve params in autocomplete
13
+ - Go To Definition for symbols in link section
14
+ - autocomplete for enums
15
+ - autocomplete for enum values
16
+ - Add Link for enums
17
+ - Go To Definition for enums
18
+ - autocomplete for ree actions
19
+ - Add Link for ree actions
20
+ - sort links in ree actions
21
+ - autocomplete for ree dao
22
+ - autocomplete for dao filters
data/Gemfile.lock ADDED
@@ -0,0 +1,19 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ruby-lsp-ree (0.1.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ rake (13.2.1)
10
+
11
+ PLATFORMS
12
+ x86_64-linux
13
+
14
+ DEPENDENCIES
15
+ rake (~> 13.0)
16
+ ruby-lsp-ree!
17
+
18
+ BUNDLED WITH
19
+ 2.4.20
@@ -4,6 +4,7 @@ require_relative "completion"
4
4
  require_relative "ree_indexing_enhancement"
5
5
  require_relative "ree_lsp_utils"
6
6
  require_relative "ree_formatter"
7
+ require_relative "parsing/parsed_document_builder"
7
8
 
8
9
  module RubyLsp
9
10
  module Ree
@@ -23,10 +24,8 @@ module RubyLsp
23
24
  end
24
25
 
25
26
  def create_definition_listener(response_builder, uri, node_context, dispatcher)
26
- $stderr.puts("create_definition_listener")
27
-
28
27
  index = @global_state.index
29
- RubyLsp::Ree::Definition.new(response_builder, node_context, index, dispatcher)
28
+ RubyLsp::Ree::Definition.new(response_builder, node_context, index, dispatcher, uri)
30
29
  end
31
30
 
32
31
  def create_completion_listener(response_builder, node_context, dispatcher, uri)
@@ -1,17 +1,22 @@
1
1
  require_relative "ree_lsp_utils"
2
+ require_relative "completion_utils"
3
+ require_relative "ree_object_finder"
2
4
 
3
5
  module RubyLsp
4
6
  module Ree
5
7
  class Completion
6
8
  include Requests::Support::Common
7
9
  include RubyLsp::Ree::ReeLspUtils
10
+ include RubyLsp::Ree::CompletionUtils
8
11
 
9
- CHARS_COUNT = 4
12
+ CHARS_COUNT = 3
13
+ CANDIDATES_LIMIT = 20
10
14
 
11
15
  def initialize(response_builder, node_context, index, dispatcher, uri)
12
16
  @response_builder = response_builder
13
17
  @index = index
14
18
  @uri = uri
19
+ @node_context = node_context
15
20
 
16
21
  dispatcher.register(self, :on_call_node_enter)
17
22
  dispatcher.register(self, :on_constant_read_node_enter)
@@ -24,142 +29,62 @@ module RubyLsp
24
29
  class_name_objects = @index.instance_variable_get(:@entries).keys.select{ _1.split('::').last[0...node_name.size] == node_name}
25
30
  return if class_name_objects.size == 0
26
31
 
27
- doc_info = parse_document_from_uri(@uri)
28
-
29
- class_name_objects.take(15).each do |full_class_name|
30
- entry = @index[full_class_name].first
31
- class_name = full_class_name.split('::').last
32
-
33
- package_name = package_name_from_uri(entry.uri)
34
-
35
- label_details = Interface::CompletionItemLabelDetails.new(
36
- description: "from: :#{package_name}",
37
- detail: ""
38
- )
39
-
40
- @response_builder << Interface::CompletionItem.new(
41
- label: class_name,
42
- label_details: label_details,
43
- filter_text: class_name,
44
- text_edit: Interface::TextEdit.new(
45
- range: range_from_location(node.location),
46
- new_text: class_name,
47
- ),
48
- kind: Constant::CompletionItemKind::CLASS,
49
- additional_text_edits: get_additional_text_edits_for_constant(doc_info, class_name, package_name, entry)
50
- )
51
- end
32
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_uri(@uri)
52
33
 
53
- nil
34
+ completion_items = get_class_name_completion_items(class_name_objects, parsed_doc, node, @index, CANDIDATES_LIMIT)
35
+ puts_items_into_response(completion_items)
54
36
  end
55
37
 
56
38
  def on_call_node_enter(node)
57
- return if node.receiver
58
- return if node.name.to_s.size < CHARS_COUNT
59
-
60
- ree_objects = @index.prefix_search(node.name.to_s)
61
- .take(50).map(&:first)
62
- .select{ _1.comments }
63
- .select{ _1.comments.to_s.lines.first&.chomp == 'ree_object' }
64
- .take(10)
65
-
66
- return if ree_objects.size == 0
67
-
68
- doc_info = parse_document_from_uri(@uri)
69
-
70
- ree_objects.each do |ree_object|
71
- fn_name = ree_object.name
72
-
73
- package_name = package_name_from_uri(ree_object.uri)
74
-
75
- params_str = ree_object.signatures.first.parameters.map(&:name).join(', ')
76
-
77
- label_details = Interface::CompletionItemLabelDetails.new(
78
- description: "from: :#{package_name}",
79
- detail: "(#{params_str})"
80
- )
81
-
82
- $stderr.puts("ree object #{ree_object.inspect}")
83
-
84
- @response_builder << Interface::CompletionItem.new(
85
- label: fn_name,
86
- label_details: label_details,
87
- filter_text: fn_name,
88
- text_edit: Interface::TextEdit.new(
89
- range: range_from_location(node.location),
90
- new_text: "#{fn_name}(#{params_str})",
91
- ),
92
- kind: Constant::CompletionItemKind::METHOD,
93
- data: {
94
- owner_name: "Object",
95
- guessed_type: false,
96
- },
97
- additional_text_edits: get_additional_text_edits_for_method(doc_info, fn_name, package_name)
98
- )
39
+ if receiver_is_enum?(node)
40
+ return enum_value_completion(node)
99
41
  end
100
-
101
- nil
102
- end
103
42
 
104
- def get_additional_text_edits_for_constant(doc_info, class_name, package_name, entry)
105
- if doc_info.linked_objects.map(&:imports).flatten.include?(class_name)
106
- $stderr.puts("links already include #{class_name}")
107
- return []
43
+ if receiver_is_dao?(node)
44
+ return dao_filter_completion(node)
108
45
  end
109
46
 
110
- entry_uri = entry.uri.to_s
111
-
112
- link_text = if doc_info.package_name == package_name
113
- fn_name = File.basename(entry_uri, ".*")
114
- "\n\s\s\s\slink :#{fn_name}, import: -> { #{class_name} }"
115
- else
116
- path = path_from_package(entry_uri)
117
- "\n\s\s\s\slink \"#{path}\", import: -> { #{class_name} }"
118
- end
47
+ return if node.receiver
48
+ return if node.name.to_s.size < CHARS_COUNT
119
49
 
120
- new_text = link_text
50
+ ree_objects = ReeObjectFinder.search_objects(@index, node.name.to_s, CANDIDATES_LIMIT)
51
+ return if ree_objects.size == 0
121
52
 
122
- unless doc_info.block_node
123
- new_text = "\sdo#{link_text}\n\s\send\n"
124
- end
53
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_uri(@uri)
125
54
 
126
- range = get_range_for_fn_insert(doc_info, link_text)
55
+ completion_items = get_ree_objects_completions_items(ree_objects, parsed_doc, node)
56
+ puts_items_into_response(completion_items)
57
+ end
127
58
 
128
- [
129
- Interface::TextEdit.new(
130
- range: range,
131
- new_text: new_text,
132
- )
133
- ]
59
+ def receiver_is_enum?(node)
60
+ node.receiver && node.receiver.is_a?(Prism::CallNode) && ReeObjectFinder.find_enum(@index, node.receiver.name.to_s)
134
61
  end
135
62
 
136
- def get_additional_text_edits_for_method(doc_info, fn_name, package_name)
137
- if doc_info.linked_objects.map(&:name).include?(fn_name)
138
- $stderr.puts("links already include #{fn_name}")
139
- return []
140
- end
63
+ def receiver_is_dao?(node)
64
+ node.receiver && node.receiver.is_a?(Prism::CallNode) && ReeObjectFinder.find_dao(@index, node.receiver.name.to_s)
65
+ end
141
66
 
142
- link_text = if doc_info.package_name == package_name
143
- "\n\s\s\s\slink :#{fn_name}"
144
- else
145
- "\n\s\s\s\slink :#{fn_name}, from: :#{package_name}"
146
- end
67
+ def enum_value_completion(node)
68
+ enum_obj = ReeObjectFinder.find_enum(@index, node.receiver.name.to_s)
69
+ location = node.receiver.location
147
70
 
148
- new_text = link_text
71
+ completion_items = get_enum_values_completion_items(enum_obj, location)
72
+ puts_items_into_response(completion_items)
73
+ end
149
74
 
150
- unless doc_info.block_node
151
- new_text = "\sdo#{link_text}\n\s\send\n"
152
- end
75
+ def dao_filter_completion(node)
76
+ dao_obj = ReeObjectFinder.find_dao(@index, node.receiver.name.to_s)
77
+ location = node.receiver.location
153
78
 
154
- range = get_range_for_fn_insert(doc_info, link_text)
79
+ completion_items = get_dao_filters_completion_items(dao_obj, location)
80
+ puts_items_into_response(completion_items)
81
+ end
155
82
 
156
- [
157
- Interface::TextEdit.new(
158
- range: range,
159
- new_text: new_text,
160
- )
161
- ]
83
+ def puts_items_into_response(items)
84
+ items.each do |item|
85
+ @response_builder << item
86
+ end
162
87
  end
163
88
  end
164
89
  end
165
- end
90
+ end
@@ -0,0 +1,234 @@
1
+ require_relative "ree_lsp_utils"
2
+
3
+ module RubyLsp
4
+ module Ree
5
+ module CompletionUtils
6
+ include Requests::Support::Common
7
+ include RubyLsp::Ree::ReeLspUtils
8
+
9
+ def get_dao_filters_completion_items(dao_obj, location)
10
+ dao_node = RubyLsp::Ree::ParsedDocumentBuilder.build_from_uri(dao_obj.uri, :dao)
11
+
12
+ range = Interface::Range.new(
13
+ start: Interface::Position.new(line: location.start_line - 1, character: location.end_column + 1),
14
+ end: Interface::Position.new(line: location.start_line - 1, character: location.end_column + 1),
15
+ )
16
+
17
+ dao_node.filters.map do |filter|
18
+ signature = filter.signatures.first
19
+
20
+ label_details = Interface::CompletionItemLabelDetails.new(
21
+ description: "filter",
22
+ detail: get_detail_string(signature)
23
+ )
24
+
25
+ Interface::CompletionItem.new(
26
+ label: filter.name,
27
+ label_details: label_details,
28
+ filter_text: filter.name,
29
+ text_edit: Interface::TextEdit.new(
30
+ range: range,
31
+ new_text: get_method_string(filter.name, signature)
32
+ ),
33
+ kind: Constant::CompletionItemKind::METHOD,
34
+ insert_text_format: Constant::InsertTextFormat::SNIPPET,
35
+ data: {
36
+ owner_name: "Object",
37
+ guessed_type: false,
38
+ }
39
+ )
40
+ end
41
+ end
42
+
43
+ def get_enum_values_completion_items(enum_obj, location)
44
+ enum_node = RubyLsp::Ree::ParsedDocumentBuilder.build_from_uri(enum_obj.uri, :enum)
45
+
46
+ class_name = enum_node.get_class_name
47
+
48
+ label_details = Interface::CompletionItemLabelDetails.new(
49
+ description: "from: #{class_name}",
50
+ detail: ''
51
+ )
52
+
53
+ range = Interface::Range.new(
54
+ start: Interface::Position.new(line: location.start_line - 1, character: location.end_column + 1),
55
+ end: Interface::Position.new(line: location.start_line - 1, character: location.end_column + 1),
56
+ )
57
+
58
+ enum_node.values.map do |val|
59
+ Interface::CompletionItem.new(
60
+ label: val.name,
61
+ label_details: label_details,
62
+ filter_text: val.name,
63
+ text_edit: Interface::TextEdit.new(
64
+ range: range,
65
+ new_text: val.name
66
+ ),
67
+ kind: Constant::CompletionItemKind::METHOD,
68
+ data: {
69
+ owner_name: "Object",
70
+ guessed_type: false,
71
+ }
72
+ )
73
+ end
74
+ end
75
+
76
+ def get_class_name_completion_items(class_name_objects, parsed_doc, node, index, limit)
77
+ class_name_objects.take(limit).map do |full_class_name|
78
+ entry = index[full_class_name].first
79
+ class_name = full_class_name.split('::').last
80
+
81
+ package_name = package_name_from_uri(entry.uri)
82
+
83
+ label_details = Interface::CompletionItemLabelDetails.new(
84
+ description: "from: :#{package_name}",
85
+ detail: ""
86
+ )
87
+
88
+ Interface::CompletionItem.new(
89
+ label: class_name,
90
+ label_details: label_details,
91
+ filter_text: class_name,
92
+ text_edit: Interface::TextEdit.new(
93
+ range: range_from_location(node.location),
94
+ new_text: class_name,
95
+ ),
96
+ kind: Constant::CompletionItemKind::CLASS,
97
+ additional_text_edits: get_additional_text_edits_for_constant(parsed_doc, class_name, package_name, entry)
98
+ )
99
+ end
100
+ end
101
+
102
+ def get_ree_objects_completions_items(ree_objects, parsed_doc, node)
103
+ ree_objects.map do |ree_object|
104
+ ree_object_name = ree_object.name
105
+ package_name = package_name_from_uri(ree_object.uri)
106
+ signature = ree_object.signatures.first
107
+ ree_type = get_ree_type(ree_object)
108
+
109
+ label_details = Interface::CompletionItemLabelDetails.new(
110
+ description: "#{ree_type}, from: :#{package_name}",
111
+ detail: get_detail_string(signature)
112
+ )
113
+
114
+ Interface::CompletionItem.new(
115
+ label: ree_object_name,
116
+ label_details: label_details,
117
+ filter_text: ree_object_name,
118
+ text_edit: Interface::TextEdit.new(
119
+ range: range_from_location(node.location),
120
+ new_text: get_method_string(ree_object_name, signature)
121
+ ),
122
+ kind: Constant::CompletionItemKind::METHOD,
123
+ insert_text_format: Constant::InsertTextFormat::SNIPPET,
124
+ data: {
125
+ owner_name: "Object",
126
+ guessed_type: false,
127
+ },
128
+ additional_text_edits: get_additional_text_edits_for_method(parsed_doc, ree_object_name, package_name)
129
+ )
130
+ end
131
+ end
132
+
133
+ def get_detail_string(signature)
134
+ return '' unless signature
135
+
136
+ "(#{get_parameters_string(signature)})"
137
+ end
138
+
139
+ def get_parameters_string(signature)
140
+ return '' unless signature
141
+
142
+ signature.parameters.map(&:decorated_name).join(', ')
143
+ end
144
+
145
+ def get_method_string(fn_name, signature)
146
+ return fn_name unless signature
147
+
148
+ "#{fn_name}(#{get_parameters_placeholder(signature)})"
149
+ end
150
+
151
+ def get_parameters_placeholder(signature)
152
+ return '' unless signature
153
+
154
+ signature.parameters.to_enum.with_index.map do |signature_param, index|
155
+ case signature_param
156
+ when RubyIndexer::Entry::KeywordParameter, RubyIndexer::Entry::OptionalKeywordParameter
157
+ "#{signature_param.name}: ${#{index+1}:#{signature_param.name}}"
158
+ else
159
+ "${#{index+1}:#{signature_param.name}}"
160
+ end
161
+ end.join(', ')
162
+ end
163
+
164
+ def get_additional_text_edits_for_constant(parsed_doc, class_name, package_name, entry)
165
+ if parsed_doc.includes_linked_constant?(class_name)
166
+ $stderr.puts("links already include #{class_name}")
167
+ return []
168
+ end
169
+
170
+ entry_uri = entry.uri.to_s
171
+
172
+ link_text = if parsed_doc.package_name == package_name
173
+ fn_name = File.basename(entry_uri, ".*")
174
+ "\s\slink :#{fn_name}, import: -> { #{class_name} }"
175
+ else
176
+ path = path_from_package(entry_uri)
177
+ "\s\slink \"#{path}\", import: -> { #{class_name} }"
178
+ end
179
+
180
+ if parsed_doc.links_container_node
181
+ link_text = "\s\s" + link_text
182
+ end
183
+
184
+ new_text = "\n" + link_text
185
+
186
+
187
+ if parsed_doc.has_blank_links_container?
188
+ new_text = "\sdo#{link_text}\n\s\send\n"
189
+ end
190
+
191
+ range = get_range_for_fn_insert(parsed_doc, link_text)
192
+
193
+ [
194
+ Interface::TextEdit.new(
195
+ range: range,
196
+ new_text: new_text,
197
+ )
198
+ ]
199
+ end
200
+
201
+ def get_additional_text_edits_for_method(parsed_doc, fn_name, package_name)
202
+ if parsed_doc.includes_linked_object?(fn_name)
203
+ $stderr.puts("links already include #{fn_name}")
204
+ return []
205
+ end
206
+
207
+ link_text = if parsed_doc.package_name == package_name
208
+ "\s\slink :#{fn_name}"
209
+ else
210
+ "\s\slink :#{fn_name}, from: :#{package_name}"
211
+ end
212
+
213
+ if parsed_doc.links_container_node
214
+ link_text = "\s\s" + link_text
215
+ end
216
+
217
+ new_text = "\n" + link_text
218
+
219
+ if parsed_doc.has_blank_links_container?
220
+ new_text = "\sdo#{link_text}\n\s\send\n"
221
+ end
222
+
223
+ range = get_range_for_fn_insert(parsed_doc, link_text)
224
+
225
+ [
226
+ Interface::TextEdit.new(
227
+ range: range,
228
+ new_text: new_text,
229
+ )
230
+ ]
231
+ end
232
+ end
233
+ end
234
+ end
@@ -1,16 +1,20 @@
1
+ require_relative "ree_lsp_utils"
2
+ require_relative "parsing/parsed_link_node"
3
+
1
4
  module RubyLsp
2
5
  module Ree
3
6
  class Definition
4
7
  include Requests::Support::Common
8
+ include RubyLsp::Ree::ReeLspUtils
5
9
 
6
- def initialize(response_builder, node_context, index, dispatcher)
10
+ def initialize(response_builder, node_context, index, dispatcher, uri)
7
11
  @response_builder = response_builder
8
12
  @node_context = node_context
9
13
  @nesting = node_context.nesting
10
14
  @index = index
15
+ @uri = uri
11
16
 
12
- # dispatcher.register(self, :on_call_node_enter, :on_symbol_node_enter, :on_string_node_enter)
13
- dispatcher.register(self, :on_call_node_enter)
17
+ dispatcher.register(self, :on_call_node_enter, :on_symbol_node_enter, :on_string_node_enter)
14
18
  end
15
19
 
16
20
  def on_call_node_enter(node)
@@ -33,6 +37,47 @@ module RubyLsp
33
37
 
34
38
  nil
35
39
  end
40
+
41
+ def on_symbol_node_enter(node)
42
+ parent_node = @node_context.parent
43
+ return unless parent_node.name == :link
44
+
45
+ link_node = RubyLsp::Ree::ParsedLinkNode.new(parent_node, package_name_from_uri(@uri))
46
+ package_name = link_node.link_package_name
47
+
48
+ method_candidates = @index[node.unescaped]
49
+ return if !method_candidates || method_candidates.size == 0
50
+
51
+ method = method_candidates.detect{ package_name_from_uri(_1.uri) == package_name }
52
+ return unless method
53
+
54
+ @response_builder << Interface::Location.new(
55
+ uri: method.uri.to_s,
56
+ range: Interface::Range.new(
57
+ start: Interface::Position.new(line: 0, character: 0),
58
+ end: Interface::Position.new(line: 0, character: 0),
59
+ ),
60
+ )
61
+
62
+ nil
63
+ end
64
+
65
+ def on_string_node_enter(node)
66
+ file_name = node.unescaped + ".rb"
67
+ local_path = Dir[File.join('**', file_name)].first
68
+
69
+ if local_path
70
+ @response_builder << Interface::Location.new(
71
+ uri: File.join(Dir.pwd, local_path),
72
+ range: Interface::Range.new(
73
+ start: Interface::Position.new(line: 0, character: 0),
74
+ end: Interface::Position.new(line: 0, character: 0),
75
+ ),
76
+ )
77
+ end
78
+
79
+ nil
80
+ end
36
81
  end
37
82
  end
38
83
  end
@@ -0,0 +1,130 @@
1
+ require_relative 'parsed_link_node'
2
+
3
+ class RubyLsp::Ree::ParsedDocument
4
+ include RubyLsp::Ree::ReeLspUtils
5
+
6
+ LINK_DSL_MODULE = 'Ree::LinkDSL'
7
+
8
+ attr_reader :ast, :package_name, :class_node, :fn_node, :fn_block_node, :class_includes,
9
+ :link_nodes, :values, :action_node, :action_block_node, :dao_node, :dao_block_node, :filters
10
+
11
+ def initialize(ast)
12
+ @ast = ast
13
+ end
14
+
15
+ def links_container_node
16
+ @fn_node || @action_node || @dao_node
17
+ end
18
+
19
+ def links_container_block_node
20
+ @fn_block_node || @action_block_node || @dao_block_node
21
+ end
22
+
23
+ def includes_link_dsl?
24
+ @class_includes.any?{ _1.name == LINK_DSL_MODULE }
25
+ end
26
+
27
+ def includes_linked_constant?(const_name)
28
+ @link_nodes.map(&:imports).flatten.include?(const_name)
29
+ end
30
+
31
+ def includes_linked_object?(obj_name)
32
+ @link_nodes.map(&:name).include?(obj_name)
33
+ end
34
+
35
+ def has_blank_links_container?
36
+ links_container_node && !links_container_block_node
37
+ end
38
+
39
+ def set_package_name(package_name)
40
+ @package_name = package_name
41
+ end
42
+
43
+ def parse_class_node
44
+ @class_node ||= ast.statements.body.detect{ |node| node.is_a?(Prism::ClassNode) }
45
+ end
46
+
47
+ def parse_fn_node
48
+ return unless class_node
49
+
50
+ @fn_node ||= class_node.body.body.detect{ |node| node.name == :fn }
51
+ @fn_block_node = @fn_node&.block
52
+ end
53
+
54
+ def parse_action_node
55
+ return unless class_node
56
+
57
+ @action_node ||= class_node.body.body.detect{ |node| node.name == :action }
58
+ @action_block_node = @action_node&.block
59
+ end
60
+
61
+ def parse_dao_node
62
+ return unless class_node
63
+
64
+ @dao_node ||= class_node.body.body.detect{ |node| node.name == :dao }
65
+ @dao_block_node = @dao_node&.block
66
+ end
67
+
68
+ def parse_class_includes
69
+ return unless class_node
70
+
71
+ @class_includes ||= class_node.body.body.select{ _1.name == :include }.map do |class_include|
72
+ parent_name = class_include.arguments.arguments.first.parent.name.to_s
73
+ module_name = class_include.arguments.arguments.first.name
74
+
75
+ OpenStruct.new(
76
+ name: [parent_name, module_name].compact.join('::')
77
+ )
78
+ end
79
+ end
80
+
81
+ def parse_links
82
+ return unless class_node
83
+
84
+ nodes = if links_container_node && links_container_block_node.body
85
+ links_container_block_node.body.body.select{ |node| node.name == :link }
86
+ elsif class_includes.any?{ _1.name == LINK_DSL_MODULE }
87
+ class_node.body.body.select{ |node| node.name == :link }
88
+ else
89
+ []
90
+ end
91
+
92
+ @link_nodes = nodes.map do |link_node|
93
+ link_node = RubyLsp::Ree::ParsedLinkNode.new(link_node, package_name)
94
+ link_node.parse_imports
95
+ link_node
96
+ end
97
+ end
98
+
99
+ def parse_values
100
+ return unless class_node
101
+
102
+ @values ||= class_node.body.body
103
+ .select{ _1.name == :val }
104
+ .map{ OpenStruct.new(name: _1.arguments.arguments.first.unescaped) }
105
+ end
106
+
107
+ def parse_filters
108
+ return unless class_node
109
+
110
+ @filters ||= class_node.body.body
111
+ .select{ _1.name == :filter }
112
+ .map{ OpenStruct.new(name: _1.arguments.arguments.first.unescaped, signatures: parse_filter_signature(_1)) }
113
+
114
+ end
115
+
116
+ def parse_filter_signature(filter_node)
117
+ return [] unless filter_node
118
+
119
+ lambda_node = filter_node.arguments&.arguments[1]
120
+ return [] unless lambda_node
121
+
122
+ signature_params = signature_params_from_node(lambda_node.parameters.parameters)
123
+ [RubyIndexer::Entry::Signature.new(signature_params)]
124
+ end
125
+
126
+ def get_class_name
127
+ name_parts = [class_node.constant_path&.parent&.name, class_node.constant_path.name]
128
+ name_parts.compact.map(&:to_s).join('::')
129
+ end
130
+ end
@@ -0,0 +1,61 @@
1
+ require 'prism'
2
+ require_relative 'parsed_document'
3
+
4
+ class RubyLsp::Ree::ParsedDocumentBuilder
5
+ extend RubyLsp::Ree::ReeLspUtils
6
+
7
+ def self.build_from_uri(uri, type = nil)
8
+ ast = Prism.parse_file(uri.path).value
9
+ document = build_document(ast, type)
10
+
11
+ document.set_package_name(package_name_from_uri(uri))
12
+
13
+ document
14
+ end
15
+
16
+ def self.build_from_source(source, type = nil)
17
+ ast = Prism.parse(source).value
18
+ build_document(ast, type)
19
+ end
20
+
21
+ def self.build_document(ast, type)
22
+ case type
23
+ when :enum
24
+ build_enum_document(ast)
25
+ when :dao
26
+ build_dao_document(ast)
27
+ else
28
+ build_regular_document(ast)
29
+ end
30
+ end
31
+
32
+ def self.build_regular_document(ast)
33
+ document = RubyLsp::Ree::ParsedDocument.new(ast)
34
+
35
+ document.parse_class_node
36
+ document.parse_fn_node
37
+ document.parse_action_node
38
+ document.parse_class_includes
39
+ document.parse_links
40
+
41
+ document
42
+ end
43
+
44
+ def self.build_enum_document(ast)
45
+ document = RubyLsp::Ree::ParsedDocument.new(ast)
46
+
47
+ document.parse_class_node
48
+ document.parse_values
49
+
50
+ document
51
+ end
52
+
53
+ def self.build_dao_document(ast)
54
+ document = RubyLsp::Ree::ParsedDocument.new(ast)
55
+
56
+ document.parse_class_node
57
+ document.parse_filters
58
+
59
+ document
60
+ end
61
+ end
@@ -0,0 +1,71 @@
1
+ require 'prism'
2
+
3
+ class RubyLsp::Ree::ParsedLinkNode
4
+ attr_reader :node, :document_package, :name, :imports
5
+
6
+ FROM_ARG_KEY = 'from'
7
+ IMPORT_ARG_KEY = 'import'
8
+
9
+ def initialize(node, document_package = nil)
10
+ @node = node
11
+ @document_package = document_package
12
+ @name = parse_name
13
+ end
14
+
15
+ def link_package_name
16
+ from_arg_value || document_package
17
+ end
18
+
19
+ def location
20
+ @node.location
21
+ end
22
+
23
+ def from_arg_value
24
+ @kw_args ||= @node.arguments.arguments.detect{ |arg| arg.is_a?(Prism::KeywordHashNode) }
25
+ return unless @kw_args
26
+
27
+ @from_param ||= @kw_args.elements.detect{ _1.key.unescaped == FROM_ARG_KEY }
28
+ return unless @from_param
29
+
30
+ @from_param.value.unescaped
31
+ end
32
+
33
+ def parse_name
34
+ name_arg_node = @node.arguments.arguments.first
35
+
36
+ case name_arg_node
37
+ when Prism::SymbolNode
38
+ name_arg_node.value
39
+ when Prism::StringNode
40
+ name_arg_node.unescaped
41
+ else
42
+ ""
43
+ end
44
+ end
45
+
46
+ def parse_imports
47
+ @imports ||= get_imports
48
+ end
49
+
50
+ private
51
+
52
+ def get_imports
53
+ return [] if @node.arguments.arguments.size == 1
54
+
55
+ last_arg = @node.arguments.arguments.last
56
+
57
+ if last_arg.is_a?(Prism::KeywordHashNode)
58
+ import_arg = last_arg.elements.detect{ _1.key.unescaped == IMPORT_ARG_KEY }
59
+ return [] unless import_arg
60
+
61
+ [import_arg.value.body.body.first.name.to_s]
62
+ elsif last_arg.is_a?(Prism::LambdaNode)
63
+ [last_arg.body.body.first.name.to_s]
64
+ else
65
+ return []
66
+ end
67
+ rescue => e
68
+ $stderr.puts("can't parse imports: #{e.message}")
69
+ return []
70
+ end
71
+ end
@@ -17,25 +17,18 @@ module RubyLsp
17
17
  private
18
18
 
19
19
  def sort_links(source)
20
- doc_info = parse_document_from_source(source)
21
-
22
- return source unless doc_info.fn_node
23
- return source if doc_info.fn_node && !doc_info.block_node
24
-
25
- if doc_info.link_nodes.size < doc_info.block_node.body.body.size
26
- $stderr.puts("block contains not only link, don't sort")
27
- return source
28
- end
29
-
30
- if doc_info.link_nodes.any?{ _1.location.start_line != _1.location.end_line }
20
+ parsed_doc = RubyLsp::Ree::ParsedDocumentBuilder.build_from_source(source)
21
+ return source if parsed_doc.link_nodes.size == 0
22
+
23
+ if parsed_doc.link_nodes.any?{ _1.location.start_line != _1.location.end_line }
31
24
  $stderr.puts("multiline link definitions, don't sort")
32
25
  return source
33
26
  end
34
27
 
35
28
  # sort link nodes
36
- sorted_link_nodes = doc_info.link_nodes.sort{ |a, b|
37
- a_name = a.arguments.arguments.first
38
- b_name = b.arguments.arguments.first
29
+ sorted_link_nodes = parsed_doc.link_nodes.sort{ |a, b|
30
+ a_name = a.node.arguments.arguments.first
31
+ b_name = b.node.arguments.arguments.first
39
32
 
40
33
  if a_name.is_a?(Prism::SymbolNode) && !b_name.is_a?(Prism::SymbolNode)
41
34
  -1
@@ -47,12 +40,12 @@ module RubyLsp
47
40
  }
48
41
 
49
42
  # check if no re-order
50
- if doc_info.link_nodes.map{ _1.arguments.arguments.first.unescaped } == sorted_link_nodes.map{ _1.arguments.arguments.first.unescaped }
43
+ if parsed_doc.link_nodes.map{ _1.node.arguments.arguments.first.unescaped } == sorted_link_nodes.map{ _1.node.arguments.arguments.first.unescaped }
51
44
  return source
52
45
  end
53
46
 
54
47
  # insert nodes to source
55
- link_lines = doc_info.link_nodes.map{ _1.location.start_line }
48
+ link_lines = parsed_doc.link_nodes.map{ _1.location.start_line }
56
49
 
57
50
  source_lines = source.lines
58
51
 
@@ -1,42 +1,37 @@
1
+ require 'prism'
2
+ require_relative "ree_lsp_utils"
3
+
1
4
  module RubyLsp
2
5
  module Ree
3
6
  class ReeIndexingEnhancement < RubyIndexer::Enhancement
7
+ include RubyLsp::Ree::ReeLspUtils
8
+
9
+ REE_INDEXED_OBJECTS = [:fn, :enum, :action, :dao]
10
+
4
11
  def on_call_node_enter(node)
5
12
  return unless @listener.current_owner
6
13
 
7
- # Return early unless the method call is the one we want to handle
8
- return unless node.name == :fn
14
+ return unless REE_INDEXED_OBJECTS.include?(node.name)
9
15
  return unless node.arguments
16
+ return unless node.arguments.child_nodes.first.is_a?(Prism::SymbolNode)
17
+
18
+ obj_name = node.arguments.child_nodes.first.unescaped
19
+ return unless current_filename == obj_name
10
20
 
11
- # index = @listener.instance_variable_get(:@index)
12
-
13
21
  location = node.location
14
- fn_name = node.arguments.child_nodes.first.unescaped
15
- signatures = parse_signatures(fn_name)
16
- comments = "ree_object\nsome_documentation"
17
-
18
- # visibility = RubyIndexer::Entry::Visibility::PUBLIC
19
- # owner = index['Object'].first
20
-
21
- # index.add(RubyIndexer::Entry::Method.new(
22
- # fn_name,
23
- # @listener.instance_variable_get(:@uri),
24
- # location,
25
- # location,
26
- # comments,
27
- # signatures,
28
- # visibility,
29
- # owner,
30
- # ))
22
+ signatures = parse_signatures(obj_name)
23
+ comments = "ree_object\ntype: :#{node.name}"
31
24
 
32
25
  @listener.add_method(
33
- fn_name,
26
+ obj_name,
34
27
  location,
35
28
  signatures,
36
29
  comments: comments
37
30
  )
38
31
  end
39
32
 
33
+ private
34
+
40
35
  def parse_signatures(fn_name)
41
36
  uri = @listener.instance_variable_get(:@uri)
42
37
  ast = Prism.parse_file(uri.path).value
@@ -47,10 +42,15 @@ module RubyLsp
47
42
  call_node = class_node.body.body.detect{ |node| node.name == :call }
48
43
  return [] unless call_node
49
44
 
50
- signature_params = @listener.send(:list_params, call_node.parameters)
51
-
45
+ signature_params = signature_params_from_node(call_node.parameters)
46
+
52
47
  [RubyIndexer::Entry::Signature.new(signature_params)]
53
48
  end
49
+
50
+ def current_filename
51
+ uri = @listener.instance_variable_get(:@uri)
52
+ File.basename(uri.path, '.rb')
53
+ end
54
54
  end
55
55
  end
56
56
  end
@@ -1,6 +1,8 @@
1
1
  module RubyLsp
2
2
  module Ree
3
3
  module ReeLspUtils
4
+ Entry = RubyIndexer::Entry
5
+
4
6
  def package_name_from_uri(uri)
5
7
  uri_parts = uri.to_s.split('/')
6
8
  package_index = uri_parts.find_index('package') + 1
@@ -13,66 +15,29 @@ module RubyLsp
13
15
  uri_parts.drop(pack_folder_index+1).join('/')
14
16
  end
15
17
 
16
- def parse_document_from_uri(uri)
17
- ast = Prism.parse_file(uri.path).value
18
- result = parse_document(ast)
19
-
20
- result.package_name = package_name_from_uri(uri)
21
-
22
- result
23
- end
18
+ def get_ree_type(ree_object)
19
+ type_comment = ree_object.comments.to_s.lines[1]
20
+ return unless type_comment
24
21
 
25
- def parse_document_from_source(source)
26
- ast = Prism.parse(source).value
27
- parse_document(ast)
22
+ type_comment.split(' ').last
28
23
  end
29
24
 
30
- def parse_document(ast)
31
- class_node = ast.statements.body.detect{ |node| node.is_a?(Prism::ClassNode) }
32
- fn_node = class_node.body.body.detect{ |node| node.name == :fn }
33
- block_node = fn_node.block
34
-
35
- link_nodes = if block_node && block_node.body
36
- block_node.body.body.select{ |node| node.name == :link }
37
- else
38
- []
39
- end
25
+ def get_range_for_fn_insert(parsed_doc, link_text)
26
+ fn_line = nil
27
+ position = nil
40
28
 
41
- linked_objects = link_nodes.map do |link_node|
42
- name_arg_node = link_node.arguments.arguments.first
29
+ if parsed_doc.links_container_node
30
+ links_container_node = parsed_doc.links_container_node
31
+ fn_line = links_container_node.location.start_line
43
32
 
44
- name_val = case name_arg_node
45
- when Prism::SymbolNode
46
- name_arg_node.value
47
- when Prism::StringNode
48
- name_arg_node.unescaped
33
+ position = if parsed_doc.links_container_block_node
34
+ parsed_doc.links_container_block_node.opening_loc.end_column + 1
49
35
  else
50
- ""
36
+ links_container_node.arguments.location.end_column + 1
51
37
  end
52
-
53
- OpenStruct.new(
54
- name: name_val,
55
- imports: parse_link_node_imports(link_node)
56
- )
57
- end
58
-
59
- return OpenStruct.new(
60
- ast: ast,
61
- class_node: class_node,
62
- fn_node: fn_node,
63
- block_node: block_node,
64
- link_nodes: link_nodes,
65
- linked_objects: linked_objects
66
- )
67
- end
68
-
69
- def get_range_for_fn_insert(doc_info, link_text)
70
- fn_line = doc_info.fn_node.location.start_line
71
-
72
- position = if doc_info.block_node
73
- doc_info.block_node.opening_loc.end_column + 1
74
- else
75
- doc_info.fn_node.arguments.location.end_column + 1
38
+ elsif parsed_doc.includes_link_dsl?
39
+ fn_line = parsed_doc.link_nodes.first.location.start_line - 1
40
+ position = parsed_doc.link_nodes.first.location.start_column
76
41
  end
77
42
 
78
43
  Interface::Range.new(
@@ -81,24 +46,94 @@ module RubyLsp
81
46
  )
82
47
  end
83
48
 
84
- def parse_link_node_imports(node)
85
- return [] if node.arguments.arguments.size == 1
86
-
87
- last_arg = node.arguments.arguments.last
88
49
 
89
- if last_arg.is_a?(Prism::KeywordHashNode)
90
- import_arg = last_arg.elements.detect{ _1.key.unescaped == 'import' }
91
- return [] unless import_arg
50
+ # params(parameters_node: Prism::ParametersNode).returns(Array[Entry::Parameter])
51
+ # copied from ruby-lsp DeclarationListener#list_params
52
+ def signature_params_from_node(parameters_node)
53
+ return [] unless parameters_node
54
+
55
+ parameters = []
56
+
57
+ parameters_node.requireds.each do |required|
58
+ name = parameter_name(required)
59
+ next unless name
60
+
61
+ parameters << Entry::RequiredParameter.new(name: name)
62
+ end
63
+
64
+ parameters_node.optionals.each do |optional|
65
+ name = parameter_name(optional)
66
+ next unless name
67
+
68
+ parameters << Entry::OptionalParameter.new(name: name)
69
+ end
70
+
71
+ rest = parameters_node.rest
72
+
73
+ if rest.is_a?(Prism::RestParameterNode)
74
+ rest_name = rest.name || Entry::RestParameter::DEFAULT_NAME
75
+ parameters << Entry::RestParameter.new(name: rest_name)
76
+ end
77
+
78
+ parameters_node.keywords.each do |keyword|
79
+ name = parameter_name(keyword)
80
+ next unless name
81
+
82
+ case keyword
83
+ when Prism::RequiredKeywordParameterNode
84
+ parameters << Entry::KeywordParameter.new(name: name)
85
+ when Prism::OptionalKeywordParameterNode
86
+ parameters << Entry::OptionalKeywordParameter.new(name: name)
87
+ end
88
+ end
89
+
90
+ keyword_rest = parameters_node.keyword_rest
91
+
92
+ case keyword_rest
93
+ when Prism::KeywordRestParameterNode
94
+ keyword_rest_name = parameter_name(keyword_rest) || Entry::KeywordRestParameter::DEFAULT_NAME
95
+ parameters << Entry::KeywordRestParameter.new(name: keyword_rest_name)
96
+ when Prism::ForwardingParameterNode
97
+ parameters << Entry::ForwardingParameter.new
98
+ end
99
+
100
+ parameters_node.posts.each do |post|
101
+ name = parameter_name(post)
102
+ next unless name
103
+
104
+ parameters << Entry::RequiredParameter.new(name: name)
105
+ end
106
+
107
+ block = parameters_node.block
108
+ parameters << Entry::BlockParameter.new(name: block.name || Entry::BlockParameter::DEFAULT_NAME) if block
109
+
110
+ parameters
111
+ end
112
+
113
+ # params(node: Prism::Node).returns(Symbol)
114
+ # copied from ruby-lsp DeclarationListener#parameter_name
115
+ def parameter_name(node)
116
+ case node
117
+ when Prism::RequiredParameterNode, Prism::OptionalParameterNode,
118
+ Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode,
119
+ Prism::RestParameterNode, Prism::KeywordRestParameterNode
120
+ node.name
121
+ when Prism::MultiTargetNode
122
+ names = node.lefts.map { |parameter_node| parameter_name(parameter_node) }
123
+
124
+ rest = node.rest
125
+ if rest.is_a?(Prism::SplatNode)
126
+ name = rest.expression&.slice
127
+ names << (rest.operator == "*" ? "*#{name}".to_sym : name&.to_sym)
128
+ end
129
+
130
+ names << nil if rest.is_a?(Prism::ImplicitRestNode)
131
+
132
+ names.concat(node.rights.map { |parameter_node| parameter_name(parameter_node) })
92
133
 
93
- [import_arg.value.body.body.first.name.to_s]
94
- elsif last_arg.is_a?(Prism::LambdaNode)
95
- [last_arg.body.body.first.name.to_s]
96
- else
97
- return []
134
+ names_with_commas = names.join(", ")
135
+ :"(#{names_with_commas})"
98
136
  end
99
- rescue => e
100
- $stderr.puts("can't parse imports: #{e.message}")
101
- return []
102
137
  end
103
138
  end
104
139
  end
@@ -0,0 +1,33 @@
1
+ module RubyLsp
2
+ module Ree
3
+ class ReeObjectFinder
4
+ MAX_LIMIT = 100
5
+
6
+ REE_OBJECT_STRING = 'ree_object'
7
+ ENUM_TYPE_STRING = 'type: :enum'
8
+ DAO_TYPE_STRING = 'type: :dao'
9
+
10
+ def self.search_objects(index, name, limit)
11
+ index.prefix_search(name)
12
+ .take(MAX_LIMIT).map(&:first)
13
+ .select{ _1.comments }
14
+ .select{ _1.comments.to_s.lines.first&.chomp == REE_OBJECT_STRING }
15
+ .take(limit)
16
+ end
17
+
18
+ def self.find_enum(index, name)
19
+ objects_by_name = index[name]
20
+ return unless objects_by_name
21
+
22
+ objects_by_name.detect{ _1.comments.lines[1]&.chomp == ENUM_TYPE_STRING }
23
+ end
24
+
25
+ def self.find_dao(index, name)
26
+ objects_by_name = index[name]
27
+ return unless objects_by_name
28
+
29
+ objects_by_name.detect{ _1.comments.lines[1]&.chomp == DAO_TYPE_STRING }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -3,7 +3,7 @@
3
3
  module Ruby
4
4
  module Lsp
5
5
  module Ree
6
- VERSION = "0.1.0"
6
+ VERSION = "0.1.1"
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp-ree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruslan Gatiyatov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-01-31 00:00:00.000000000 Z
11
+ date: 2025-02-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A Ruby LSP addon that adds extra editor functionality for Ree applications
14
14
  email:
@@ -20,15 +20,21 @@ files:
20
20
  - CHANGELOG.md
21
21
  - CODE_OF_CONDUCT.md
22
22
  - Gemfile
23
+ - Gemfile.lock
23
24
  - LICENSE.txt
24
25
  - README.md
25
26
  - Rakefile
26
27
  - lib/ruby_lsp/ruby_lsp_ree/addon.rb
27
28
  - lib/ruby_lsp/ruby_lsp_ree/completion.rb
29
+ - lib/ruby_lsp/ruby_lsp_ree/completion_utils.rb
28
30
  - lib/ruby_lsp/ruby_lsp_ree/definition.rb
31
+ - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_document.rb
32
+ - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_document_builder.rb
33
+ - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_link_node.rb
29
34
  - lib/ruby_lsp/ruby_lsp_ree/ree_formatter.rb
30
35
  - lib/ruby_lsp/ruby_lsp_ree/ree_indexing_enhancement.rb
31
36
  - lib/ruby_lsp/ruby_lsp_ree/ree_lsp_utils.rb
37
+ - lib/ruby_lsp/ruby_lsp_ree/ree_object_finder.rb
32
38
  - lib/ruby_lsp_ree.rb
33
39
  - lib/ruby_lsp_ree/version.rb
34
40
  - ruby-lsp-ree.gemspec