ruby-lsp 0.17.4 → 0.17.5

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