ruby-lsp 0.17.4 → 0.17.5

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.
@@ -313,12 +313,12 @@ module RubyIndexer
313
313
  @index.index_single(indexable_path)
314
314
  end
315
315
 
316
- refute_empty(@index.instance_variable_get(:@entries))
316
+ refute_empty(@index)
317
317
  end
318
318
 
319
319
  def test_index_single_does_not_fail_for_non_existing_file
320
320
  @index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"))
321
- entries_after_indexing = @index.instance_variable_get(:@entries).keys
321
+ entries_after_indexing = @index.names
322
322
  assert_equal(@default_indexed_entries.keys, entries_after_indexing)
323
323
  end
324
324
 
@@ -1007,6 +1007,43 @@ module RubyIndexer
1007
1007
  assert_instance_of(Entry::Alias, baz_entry)
1008
1008
  end
1009
1009
 
1010
+ def test_resolving_constants_in_aliased_namespace
1011
+ index(<<~RUBY)
1012
+ module Original
1013
+ module Something
1014
+ CONST = 123
1015
+ end
1016
+ end
1017
+
1018
+ module Other
1019
+ ALIAS = Original::Something
1020
+ end
1021
+
1022
+ module Third
1023
+ Other::ALIAS::CONST
1024
+ end
1025
+ RUBY
1026
+
1027
+ entry = T.must(@index.resolve("Other::ALIAS::CONST", ["Third"])&.first)
1028
+ assert_kind_of(Entry::Constant, entry)
1029
+ assert_equal("Original::Something::CONST", entry.name)
1030
+ end
1031
+
1032
+ def test_resolving_top_level_aliases
1033
+ index(<<~RUBY)
1034
+ class Foo
1035
+ CONST = 123
1036
+ end
1037
+
1038
+ FOO = Foo
1039
+ FOO::CONST
1040
+ RUBY
1041
+
1042
+ entry = T.must(@index.resolve("FOO::CONST", [])&.first)
1043
+ assert_kind_of(Entry::Constant, entry)
1044
+ assert_equal("Foo::CONST", entry.name)
1045
+ end
1046
+
1010
1047
  def test_resolving_top_level_compact_reference
1011
1048
  index(<<~RUBY)
1012
1049
  class Foo::Bar
@@ -1321,11 +1358,6 @@ module RubyIndexer
1321
1358
  entries = @index.instance_variable_completion_candidates("@", "Foo::Bar::<Class:Bar>").map(&:name)
1322
1359
  assert_includes(entries, "@a")
1323
1360
  assert_includes(entries, "@b")
1324
-
1325
- assert_includes(
1326
- @index.instance_variable_completion_candidates("@", "Foo::Bar::<Class:Bar>::<Class:<Class:Bar>>").map(&:name),
1327
- "@c",
1328
- )
1329
1361
  end
1330
1362
 
1331
1363
  def test_singletons_are_excluded_from_prefix_search
@@ -1472,5 +1504,208 @@ module RubyIndexer
1472
1504
 
1473
1505
  assert_empty(@index.method_completion_candidates("bar", "Foo"))
1474
1506
  end
1507
+
1508
+ def test_completion_does_not_duplicate_overridden_methods
1509
+ index(<<~RUBY)
1510
+ class Foo
1511
+ def bar; end
1512
+ end
1513
+
1514
+ class Baz < Foo
1515
+ def bar; end
1516
+ end
1517
+ RUBY
1518
+
1519
+ entries = @index.method_completion_candidates("bar", "Baz")
1520
+ assert_equal(["bar"], entries.map(&:name))
1521
+ assert_equal("Baz", T.must(entries.first.owner).name)
1522
+ end
1523
+
1524
+ def test_completion_does_not_duplicate_methods_overridden_by_aliases
1525
+ index(<<~RUBY)
1526
+ class Foo
1527
+ def bar; end
1528
+ end
1529
+
1530
+ class Baz < Foo
1531
+ alias bar to_s
1532
+ end
1533
+ RUBY
1534
+
1535
+ entries = @index.method_completion_candidates("bar", "Baz")
1536
+ assert_equal(["bar"], entries.map(&:name))
1537
+ assert_equal("Baz", T.must(entries.first.owner).name)
1538
+ end
1539
+
1540
+ def test_decorated_parameters
1541
+ index(<<~RUBY)
1542
+ class Foo
1543
+ def bar(a, b = 1, c: 2)
1544
+ end
1545
+ end
1546
+ RUBY
1547
+
1548
+ methods = @index.resolve_method("bar", "Foo")
1549
+ refute_nil(methods)
1550
+
1551
+ entry = T.must(methods.first)
1552
+
1553
+ assert_equal("(a, b = <default>, c: <default>)", entry.decorated_parameters)
1554
+ end
1555
+
1556
+ def test_decorated_parameters_when_method_has_no_parameters
1557
+ index(<<~RUBY)
1558
+ class Foo
1559
+ def bar
1560
+ end
1561
+ end
1562
+ RUBY
1563
+
1564
+ methods = @index.resolve_method("bar", "Foo")
1565
+ refute_nil(methods)
1566
+
1567
+ entry = T.must(methods.first)
1568
+
1569
+ assert_equal("()", entry.decorated_parameters)
1570
+ end
1571
+
1572
+ def test_linearizing_singleton_ancestors_of_singleton_when_class_has_parent
1573
+ @index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), <<~RUBY)
1574
+ class Foo; end
1575
+
1576
+ class Bar < Foo
1577
+ end
1578
+
1579
+ class Baz < Bar
1580
+ class << self
1581
+ class << self
1582
+ end
1583
+ end
1584
+ end
1585
+ RUBY
1586
+
1587
+ assert_equal(
1588
+ [
1589
+ "Baz::<Class:Baz>::<Class:<Class:Baz>>",
1590
+ "Bar::<Class:Bar>::<Class:<Class:Bar>>",
1591
+ "Foo::<Class:Foo>::<Class:<Class:Foo>>",
1592
+ "Object::<Class:Object>::<Class:<Class:Object>>",
1593
+ "BasicObject::<Class:BasicObject>::<Class:<Class:BasicObject>>",
1594
+ "Class::<Class:Class>",
1595
+ "Module::<Class:Module>",
1596
+ "Object::<Class:Object>",
1597
+ "BasicObject::<Class:BasicObject>",
1598
+ "Class",
1599
+ "Module",
1600
+ "Object",
1601
+ "Kernel",
1602
+ "BasicObject",
1603
+ ],
1604
+ @index.linearized_ancestors_of("Baz::<Class:Baz>::<Class:<Class:Baz>>"),
1605
+ )
1606
+ end
1607
+
1608
+ def test_linearizing_singleton_object
1609
+ assert_equal(
1610
+ [
1611
+ "Object::<Class:Object>",
1612
+ "BasicObject::<Class:BasicObject>",
1613
+ "Class",
1614
+ "Module",
1615
+ "Object",
1616
+ "Kernel",
1617
+ "BasicObject",
1618
+ ],
1619
+ @index.linearized_ancestors_of("Object::<Class:Object>"),
1620
+ )
1621
+ end
1622
+
1623
+ def test_linearizing_singleton_ancestors
1624
+ @index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), <<~RUBY)
1625
+ module First
1626
+ end
1627
+
1628
+ module Second
1629
+ include First
1630
+ end
1631
+
1632
+ module Foo
1633
+ class Bar
1634
+ class << self
1635
+ class Baz
1636
+ extend Second
1637
+
1638
+ class << self
1639
+ include First
1640
+ end
1641
+ end
1642
+ end
1643
+ end
1644
+ end
1645
+ RUBY
1646
+
1647
+ assert_equal(
1648
+ [
1649
+ "Foo::Bar::<Class:Bar>::Baz::<Class:Baz>",
1650
+ "Second",
1651
+ "First",
1652
+ "Object::<Class:Object>",
1653
+ "BasicObject::<Class:BasicObject>",
1654
+ "Class",
1655
+ "Module",
1656
+ "Object",
1657
+ "Kernel",
1658
+ "BasicObject",
1659
+ ],
1660
+ @index.linearized_ancestors_of("Foo::Bar::<Class:Bar>::Baz::<Class:Baz>"),
1661
+ )
1662
+ end
1663
+
1664
+ def test_linearizing_singleton_ancestors_when_class_has_parent
1665
+ @index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), <<~RUBY)
1666
+ class Foo; end
1667
+
1668
+ class Bar < Foo
1669
+ end
1670
+
1671
+ class Baz < Bar
1672
+ class << self
1673
+ end
1674
+ end
1675
+ RUBY
1676
+
1677
+ assert_equal(
1678
+ [
1679
+ "Baz::<Class:Baz>",
1680
+ "Bar::<Class:Bar>",
1681
+ "Foo::<Class:Foo>",
1682
+ "Object::<Class:Object>",
1683
+ "BasicObject::<Class:BasicObject>",
1684
+ "Class",
1685
+ "Module",
1686
+ "Object",
1687
+ "Kernel",
1688
+ "BasicObject",
1689
+ ],
1690
+ @index.linearized_ancestors_of("Baz::<Class:Baz>"),
1691
+ )
1692
+ end
1693
+
1694
+ def test_linearizing_a_module_singleton_class
1695
+ @index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), <<~RUBY)
1696
+ module A; end
1697
+ RUBY
1698
+
1699
+ assert_equal(
1700
+ [
1701
+ "A::<Class:A>",
1702
+ "Module",
1703
+ "Object",
1704
+ "Kernel",
1705
+ "BasicObject",
1706
+ ],
1707
+ @index.linearized_ancestors_of("A::<Class:A>"),
1708
+ )
1709
+ end
1475
1710
  end
1476
1711
  end
@@ -401,7 +401,7 @@ module RubyIndexer
401
401
  assert_entry("foo", Entry::UnresolvedMethodAlias, "/fake/path/foo.rb:2-15:2-19")
402
402
  assert_entry("bar", Entry::UnresolvedMethodAlias, "/fake/path/foo.rb:3-15:3-20")
403
403
  # Foo plus 3 valid aliases
404
- assert_equal(4, @index.instance_variable_get(:@entries).length - @default_indexed_entries.length)
404
+ assert_equal(4, @index.length - @default_indexed_entries.length)
405
405
  end
406
406
 
407
407
  def test_singleton_methods
@@ -428,5 +428,25 @@ module RubyIndexer
428
428
  # the exact same
429
429
  assert_same(bar_owner, baz_owner)
430
430
  end
431
+
432
+ def test_name_location_points_to_method_identifier_location
433
+ index(<<~RUBY)
434
+ class Foo
435
+ def bar
436
+ a = 123
437
+ a + 456
438
+ end
439
+ end
440
+ RUBY
441
+
442
+ entry = T.must(@index["bar"].first)
443
+ refute_equal(entry.location, entry.name_location)
444
+
445
+ name_location = entry.name_location
446
+ assert_equal(2, name_location.start_line)
447
+ assert_equal(2, name_location.end_line)
448
+ assert_equal(6, name_location.start_column)
449
+ assert_equal(9, name_location.end_column)
450
+ end
431
451
  end
432
452
  end
@@ -63,5 +63,16 @@ module RubyIndexer
63
63
  assert_instance_of(Entry::SingletonClass, owner)
64
64
  assert_equal("File::<Class:File>", owner.name)
65
65
  end
66
+
67
+ def test_location_and_name_location_are_the_same
68
+ # NOTE: RBS does not store the name location for classes, modules or methods. This behaviour is not exactly what
69
+ # we would like, but for now we assign the same location to both
70
+
71
+ entries = @index["Array"]
72
+ refute_nil(entries)
73
+ entry = entries.find { |entry| entry.is_a?(Entry::Class) }
74
+
75
+ assert_same(entry.location, entry.name_location)
76
+ end
66
77
  end
67
78
  end
@@ -40,16 +40,12 @@ module RubyIndexer
40
40
  assert_nil(entries, "Expected #{expected_name} to not be indexed")
41
41
  end
42
42
 
43
- def assert_no_entries
44
- assert_empty(@index.instance_variable_get(:@entries), "Expected nothing to be indexed")
45
- end
46
-
47
43
  def assert_no_indexed_entries
48
44
  assert_equal(@default_indexed_entries, @index.instance_variable_get(:@entries))
49
45
  end
50
46
 
51
47
  def assert_no_entry(entry)
52
- refute(@index.instance_variable_get(:@entries).key?(entry), "Expected '#{entry}' to not be indexed")
48
+ refute(@index.indexed?(entry), "Expected '#{entry}' to not be indexed")
53
49
  end
54
50
  end
55
51
  end
@@ -72,6 +72,15 @@ module RubyLsp
72
72
  addon.add_error(e)
73
73
  end
74
74
  end
75
+
76
+ # Intended for use by tests for addons
77
+ sig { params(addon_name: String).returns(Addon) }
78
+ def get(addon_name)
79
+ addon = addons.find { |addon| addon.name == addon_name }
80
+ raise "Could not find addon '#{addon_name}'" unless addon
81
+
82
+ addon
83
+ end
75
84
  end
76
85
 
77
86
  sig { void }
@@ -157,7 +166,10 @@ module RubyLsp
157
166
  # Creates a new Definition listener. This method is invoked on every Definition request
158
167
  sig do
159
168
  overridable.params(
160
- response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
169
+ response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
170
+ Interface::Location,
171
+ Interface::LocationLink,
172
+ )],
161
173
  uri: URI::Generic,
162
174
  node_context: NodeContext,
163
175
  dispatcher: Prism::Dispatcher,
@@ -3,6 +3,13 @@
3
3
 
4
4
  module RubyLsp
5
5
  class Document
6
+ class LanguageId < T::Enum
7
+ enums do
8
+ Ruby = new("ruby")
9
+ ERB = new("erb")
10
+ end
11
+ end
12
+
6
13
  extend T::Sig
7
14
  extend T::Helpers
8
15
 
@@ -34,21 +41,14 @@ module RubyLsp
34
41
  @parse_result = T.let(parse, Prism::ParseResult)
35
42
  end
36
43
 
37
- sig { returns(Prism::ProgramNode) }
38
- def tree
39
- @parse_result.value
40
- end
41
-
42
- sig { returns(T::Array[Prism::Comment]) }
43
- def comments
44
- @parse_result.comments
45
- end
46
-
47
44
  sig { params(other: Document).returns(T::Boolean) }
48
45
  def ==(other)
49
- @source == other.source
46
+ self.class == other.class && uri == other.uri && @source == other.source
50
47
  end
51
48
 
49
+ sig { abstract.returns(LanguageId) }
50
+ def language_id; end
51
+
52
52
  # TODO: remove this method once all nonpositional requests have been migrated to the listener pattern
53
53
  sig do
54
54
  type_parameters(:T)
@@ -96,10 +96,8 @@ module RubyLsp
96
96
  sig { abstract.returns(Prism::ParseResult) }
97
97
  def parse; end
98
98
 
99
- sig { returns(T::Boolean) }
100
- def syntax_error?
101
- @parse_result.failure?
102
- end
99
+ sig { abstract.returns(T::Boolean) }
100
+ def syntax_error?; end
103
101
 
104
102
  sig { returns(Scanner) }
105
103
  def create_scanner
@@ -0,0 +1,125 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ class ERBDocument < Document
6
+ extend T::Sig
7
+
8
+ sig { override.returns(Prism::ParseResult) }
9
+ def parse
10
+ return @parse_result unless @needs_parsing
11
+
12
+ @needs_parsing = false
13
+ scanner = ERBScanner.new(@source)
14
+ scanner.scan
15
+ @parse_result = Prism.parse(scanner.ruby)
16
+ end
17
+
18
+ sig { override.returns(T::Boolean) }
19
+ def syntax_error?
20
+ @parse_result.failure?
21
+ end
22
+
23
+ sig { override.returns(LanguageId) }
24
+ def language_id
25
+ LanguageId::ERB
26
+ end
27
+
28
+ class ERBScanner
29
+ extend T::Sig
30
+
31
+ sig { returns(String) }
32
+ attr_reader :ruby, :html
33
+
34
+ sig { params(source: String).void }
35
+ def initialize(source)
36
+ @source = source
37
+ @html = T.let(+"", String)
38
+ @ruby = T.let(+"", String)
39
+ @current_pos = T.let(0, Integer)
40
+ @inside_ruby = T.let(false, T::Boolean)
41
+ end
42
+
43
+ sig { void }
44
+ def scan
45
+ while @current_pos < @source.length
46
+ scan_char
47
+ @current_pos += 1
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ sig { void }
54
+ def scan_char
55
+ char = @source[@current_pos]
56
+
57
+ case char
58
+ when "<"
59
+ if next_char == "%"
60
+ @inside_ruby = true
61
+ @current_pos += 1
62
+ push_char(" ")
63
+
64
+ if next_char == "=" && @source[@current_pos + 2] == "="
65
+ @current_pos += 2
66
+ push_char(" ")
67
+ elsif next_char == "=" || next_char == "-"
68
+ @current_pos += 1
69
+ push_char(" ")
70
+ end
71
+ else
72
+ push_char(T.must(char))
73
+ end
74
+ when "-"
75
+ if @inside_ruby && next_char == "%" &&
76
+ @source[@current_pos + 2] == ">"
77
+ @current_pos += 2
78
+ push_char(" ")
79
+ @inside_ruby = false
80
+ else
81
+ push_char(T.must(char))
82
+ end
83
+ when "%"
84
+ if @inside_ruby && next_char == ">"
85
+ @inside_ruby = false
86
+ @current_pos += 1
87
+ push_char(" ")
88
+ else
89
+ push_char(T.must(char))
90
+ end
91
+ when "\r"
92
+ @ruby << char
93
+ @html << char
94
+
95
+ if next_char == "\n"
96
+ @ruby << next_char
97
+ @html << next_char
98
+ @current_pos += 1
99
+ end
100
+ when "\n"
101
+ @ruby << char
102
+ @html << char
103
+ else
104
+ push_char(T.must(char))
105
+ end
106
+ end
107
+
108
+ sig { params(char: String).void }
109
+ def push_char(char)
110
+ if @inside_ruby
111
+ @ruby << char
112
+ @html << " " * char.length
113
+ else
114
+ @ruby << " " * char.length
115
+ @html << char
116
+ end
117
+ end
118
+
119
+ sig { returns(String) }
120
+ def next_char
121
+ @source[@current_pos + 1] || ""
122
+ end
123
+ end
124
+ end
125
+ end
@@ -21,7 +21,7 @@ module RubyLsp
21
21
  attr_reader :encoding
22
22
 
23
23
  sig { returns(T::Boolean) }
24
- attr_reader :supports_watching_files
24
+ attr_reader :supports_watching_files, :experimental_features
25
25
 
26
26
  sig { returns(TypeInferrer) }
27
27
  attr_reader :type_inferrer
@@ -39,6 +39,7 @@ module RubyLsp
39
39
  @type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
40
40
  @supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
41
41
  @supports_watching_files = T.let(false, T::Boolean)
42
+ @experimental_features = T.let(false, T::Boolean)
42
43
  end
43
44
 
44
45
  sig { params(identifier: String, instance: Requests::Support::Formatter).void }
@@ -87,6 +88,8 @@ module RubyLsp
87
88
  if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
88
89
  @supports_watching_files = true
89
90
  end
91
+
92
+ @experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
90
93
  end
91
94
 
92
95
  sig { returns(String) }
@@ -15,6 +15,7 @@ Bundler.ui.level = :silent
15
15
  require "uri"
16
16
  require "cgi"
17
17
  require "set"
18
+ require "strscan"
18
19
  require "prism"
19
20
  require "prism/visitor"
20
21
  require "language_server-protocol"
@@ -34,6 +35,7 @@ require "ruby_lsp/response_builders"
34
35
  require "ruby_lsp/node_context"
35
36
  require "ruby_lsp/document"
36
37
  require "ruby_lsp/ruby_document"
38
+ require "ruby_lsp/erb_document"
37
39
  require "ruby_lsp/store"
38
40
  require "ruby_lsp/addon"
39
41
  require "ruby_lsp/requests/support/rubocop_runner"
@@ -209,8 +209,13 @@ module RubyLsp
209
209
  @index.instance_variable_completion_candidates(name, type).each do |entry|
210
210
  variable_name = entry.name
211
211
 
212
+ label_details = Interface::CompletionItemLabelDetails.new(
213
+ description: entry.file_name,
214
+ )
215
+
212
216
  @response_builder << Interface::CompletionItem.new(
213
217
  label: variable_name,
218
+ label_details: label_details,
214
219
  text_edit: Interface::TextEdit.new(
215
220
  range: range_from_location(location),
216
221
  new_text: variable_name,
@@ -283,19 +288,29 @@ module RubyLsp
283
288
  range = if method_name
284
289
  range_from_location(T.must(node.message_loc))
285
290
  else
286
- loc = T.must(node.call_operator_loc)
287
- Interface::Range.new(
288
- start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
289
- end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
290
- )
291
+ loc = node.call_operator_loc
292
+
293
+ if loc
294
+ Interface::Range.new(
295
+ start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
296
+ end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
297
+ )
298
+ end
291
299
  end
292
300
 
301
+ return unless range
302
+
293
303
  @index.method_completion_candidates(method_name, type).each do |entry|
294
304
  entry_name = entry.name
295
305
 
306
+ label_details = Interface::CompletionItemLabelDetails.new(
307
+ description: entry.file_name,
308
+ detail: entry.decorated_parameters,
309
+ )
296
310
  @response_builder << Interface::CompletionItem.new(
297
311
  label: entry_name,
298
312
  filter_text: entry_name,
313
+ label_details: label_details,
299
314
  text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
300
315
  kind: Constant::CompletionItemKind::METHOD,
301
316
  data: {
@@ -307,31 +322,6 @@ module RubyLsp
307
322
  # We have not indexed this namespace, so we can't provide any completions
308
323
  end
309
324
 
310
- sig do
311
- params(
312
- entry: T.any(RubyIndexer::Entry::Member, RubyIndexer::Entry::MethodAlias),
313
- node: Prism::CallNode,
314
- ).returns(Interface::CompletionItem)
315
- end
316
- def build_method_completion(entry, node)
317
- name = entry.name
318
-
319
- Interface::CompletionItem.new(
320
- label: name,
321
- filter_text: name,
322
- text_edit: Interface::TextEdit.new(range: range_from_location(T.must(node.message_loc)), new_text: name),
323
- kind: Constant::CompletionItemKind::METHOD,
324
- label_details: Interface::CompletionItemLabelDetails.new(
325
- detail: entry.decorated_parameters,
326
- description: entry.file_name,
327
- ),
328
- documentation: Interface::MarkupContent.new(
329
- kind: "markdown",
330
- value: markdown_from_index_entries(name, entry),
331
- ),
332
- )
333
- end
334
-
335
325
  sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
336
326
  def build_completion(label, node)
337
327
  # We should use the content location as we only replace the content and not the delimiters of the string
@@ -413,8 +403,14 @@ module RubyLsp
413
403
  # When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
414
404
  # For these top level references, we need to include the `::` as part of the filter text or else it won't match
415
405
  # the right entries in the index
406
+
407
+ label_details = Interface::CompletionItemLabelDetails.new(
408
+ description: entries.map(&:file_name).join(","),
409
+ )
410
+
416
411
  Interface::CompletionItem.new(
417
412
  label: real_name,
413
+ label_details: label_details,
418
414
  filter_text: filter_text,
419
415
  text_edit: Interface::TextEdit.new(
420
416
  range: range,