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.
- checksums.yaml +4 -4
- data/README.md +4 -0
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +40 -39
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +112 -25
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +266 -68
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +13 -32
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +33 -3
- data/lib/ruby_indexer/test/index_test.rb +242 -7
- data/lib/ruby_indexer/test/method_test.rb +21 -1
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +11 -0
- data/lib/ruby_indexer/test/test_case.rb +1 -5
- data/lib/ruby_lsp/addon.rb +13 -1
- data/lib/ruby_lsp/document.rb +13 -15
- data/lib/ruby_lsp/erb_document.rb +125 -0
- data/lib/ruby_lsp/global_state.rb +4 -1
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/completion.rb +26 -30
- data/lib/ruby_lsp/listeners/definition.rb +24 -17
- data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -2
- data/lib/ruby_lsp/requests/completion.rb +1 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +2 -7
- data/lib/ruby_lsp/requests/definition.rb +4 -3
- data/lib/ruby_lsp/requests/formatting.rb +2 -0
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +2 -6
- data/lib/ruby_lsp/ruby_document.rb +10 -0
- data/lib/ruby_lsp/server.rb +41 -11
- data/lib/ruby_lsp/store.rb +23 -8
- data/lib/ruby_lsp/test_helper.rb +2 -0
- metadata +7 -6
@@ -313,12 +313,12 @@ module RubyIndexer
|
|
313
313
|
@index.index_single(indexable_path)
|
314
314
|
end
|
315
315
|
|
316
|
-
refute_empty(@index
|
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.
|
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.
|
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.
|
48
|
+
refute(@index.indexed?(entry), "Expected '#{entry}' to not be indexed")
|
53
49
|
end
|
54
50
|
end
|
55
51
|
end
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -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[
|
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,
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -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) }
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -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 =
|
287
|
-
|
288
|
-
|
289
|
-
|
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,
|