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.
- 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,
|