ruby-lsp-ree 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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