ruby-lsp 0.2.0 → 0.2.3

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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +1 -3
  5. data/lib/ruby-lsp.rb +2 -2
  6. data/lib/ruby_lsp/document.rb +10 -3
  7. data/lib/ruby_lsp/handler.rb +17 -138
  8. data/lib/ruby_lsp/internal.rb +3 -1
  9. data/lib/ruby_lsp/requests/document_highlight.rb +25 -42
  10. data/lib/ruby_lsp/requests/document_link.rb +119 -0
  11. data/lib/ruby_lsp/requests/folding_ranges.rb +5 -1
  12. data/lib/ruby_lsp/requests/formatting.rb +1 -0
  13. data/lib/ruby_lsp/requests/semantic_highlighting.rb +17 -7
  14. data/lib/ruby_lsp/requests/support/highlight_target.rb +88 -0
  15. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +9 -2
  16. data/lib/ruby_lsp/requests/support/source_uri.rb +82 -0
  17. data/lib/ruby_lsp/requests.rb +3 -0
  18. data/lib/ruby_lsp/server.rb +193 -0
  19. data/lib/ruby_lsp/store.rb +12 -5
  20. metadata +6 -70
  21. data/.github/dependabot.yml +0 -11
  22. data/.github/probots.yml +0 -2
  23. data/.github/pull_request_template.md +0 -15
  24. data/.github/workflows/ci.yml +0 -31
  25. data/.github/workflows/publish_docs.yml +0 -32
  26. data/.gitignore +0 -9
  27. data/.rubocop.yml +0 -39
  28. data/.vscode/extensions.json +0 -5
  29. data/.vscode/settings.json +0 -5
  30. data/.vscode/tasks.json +0 -25
  31. data/CODE_OF_CONDUCT.md +0 -78
  32. data/Gemfile +0 -18
  33. data/Gemfile.lock +0 -126
  34. data/Rakefile +0 -28
  35. data/bin/console +0 -19
  36. data/bin/rubocop +0 -29
  37. data/bin/tapioca +0 -29
  38. data/bin/test +0 -9
  39. data/dev.yml +0 -20
  40. data/lib/ruby_lsp/cli.rb +0 -89
  41. data/rakelib/check_docs.rake +0 -81
  42. data/ruby-lsp.gemspec +0 -27
  43. data/service.yml +0 -2
  44. data/sorbet/config +0 -4
  45. data/sorbet/rbi/.rubocop.yml +0 -8
  46. data/sorbet/rbi/gems/ansi@1.5.0.rbi +0 -338
  47. data/sorbet/rbi/gems/ast@2.4.2.rbi +0 -522
  48. data/sorbet/rbi/gems/builder@3.2.4.rbi +0 -418
  49. data/sorbet/rbi/gems/coderay@1.1.3.rbi +0 -8
  50. data/sorbet/rbi/gems/debug@1.5.0.rbi +0 -1273
  51. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +0 -867
  52. data/sorbet/rbi/gems/io-console@0.5.11.rbi +0 -8
  53. data/sorbet/rbi/gems/irb@1.4.1.rbi +0 -376
  54. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +0 -7325
  55. data/sorbet/rbi/gems/method_source@1.0.0.rbi +0 -8
  56. data/sorbet/rbi/gems/minitest-reporters@1.5.0.rbi +0 -612
  57. data/sorbet/rbi/gems/minitest@5.15.0.rbi +0 -994
  58. data/sorbet/rbi/gems/parallel@1.22.1.rbi +0 -163
  59. data/sorbet/rbi/gems/parser@3.1.2.0.rbi +0 -3968
  60. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +0 -734
  61. data/sorbet/rbi/gems/pry@0.14.1.rbi +0 -8
  62. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +0 -227
  63. data/sorbet/rbi/gems/rake@13.0.6.rbi +0 -1853
  64. data/sorbet/rbi/gems/rbi@0.0.14.rbi +0 -2337
  65. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +0 -1854
  66. data/sorbet/rbi/gems/reline@0.3.1.rbi +0 -1274
  67. data/sorbet/rbi/gems/rexml@3.2.5.rbi +0 -3852
  68. data/sorbet/rbi/gems/rubocop-ast@1.18.0.rbi +0 -4180
  69. data/sorbet/rbi/gems/rubocop-minitest@0.20.0.rbi +0 -1369
  70. data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +0 -246
  71. data/sorbet/rbi/gems/rubocop-shopify@2.6.0.rbi +0 -8
  72. data/sorbet/rbi/gems/rubocop-sorbet@0.6.8.rbi +0 -652
  73. data/sorbet/rbi/gems/rubocop@1.30.0.rbi +0 -36729
  74. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +0 -732
  75. data/sorbet/rbi/gems/spoom@1.1.11.rbi +0 -1600
  76. data/sorbet/rbi/gems/syntax_tree@2.7.1.rbi +0 -6777
  77. data/sorbet/rbi/gems/tapioca@0.8.1.rbi +0 -1972
  78. data/sorbet/rbi/gems/thor@1.2.1.rbi +0 -2921
  79. data/sorbet/rbi/gems/unicode-display_width@2.1.0.rbi +0 -27
  80. data/sorbet/rbi/gems/unparser@0.6.5.rbi +0 -2789
  81. data/sorbet/rbi/gems/webrick@1.7.0.rbi +0 -1779
  82. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +0 -289
  83. data/sorbet/rbi/gems/yard@0.9.27.rbi +0 -13048
  84. data/sorbet/rbi/shims/fiddle.rbi +0 -4
  85. data/sorbet/rbi/shims/hash.rbi +0 -6
  86. data/sorbet/rbi/shims/rdoc.rbi +0 -4
  87. data/sorbet/tapioca/config.yml +0 -13
  88. data/sorbet/tapioca/require.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f81baa8f09011e5130c955c4c5d2a33583379b0b073caee556719b874efc320e
4
- data.tar.gz: 55a664f923d93bda9ddb06624303f889123496c672c46b31ea71c629cfa96a81
3
+ metadata.gz: 96c21e90970f4cbf198609a42471cf554ce86a3b6af9296ded95994ad373317a
4
+ data.tar.gz: a28aa2c13ba4d79f53082160b0eca7e8461efad831b6c6cca2a6a4d34765a6c0
5
5
  SHA512:
6
- metadata.gz: 8a872f34699bfaa4f955c714c4e49eed52e87b009afc0a642d0ad434956f6a14690188ad9c359cc7aadd53e96fbd0d5d320e00aa263265e1ce3d3e369e8bd124
7
- data.tar.gz: 06444ea3e08b961445db6cb1a9093fc322b40ddf47daa00deb09a77f3b114014be8f525d7d391430b578b7916042bf345759d113c74e41a8ea51d65791abe69c
6
+ metadata.gz: 5ab1bf48ae4943a710f34ef0aace64c1d8991271bdca71482a2367c3a103299c52ebaef203fa32945236d689dda67d2bb1b5cc5ca9ffa698f5dd057dd62e242f
7
+ data.tar.gz: 8b9453d71b65634e1ea275f3a2552548f530dc144acd18cdcbcee493f153b824a13bc60bbbe5ac937a993d9e9c56ce8cb70773d354b5cea7acda3f4f44722ffd
data/CHANGELOG.md CHANGED
@@ -6,6 +6,33 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.3]
10
+
11
+ - Resolve generic source URIs for jump to gem source (https://github.com/Shopify/ruby-lsp/pull/237)
12
+
13
+ ## [0.2.2]
14
+
15
+ - Support document links (https://github.com/Shopify/ruby-lsp/pull/195)
16
+ - Avoid returning on request blocks (https://github.com/Shopify/ruby-lsp/pull/232)
17
+ - Better specify gemspec files (https://github.com/Shopify/ruby-lsp/pull/233)
18
+ - Include Kernel instance methods as special methods for semantic highlighting (https://github.com/Shopify/ruby-lsp/pull/231)
19
+ - Fix call processing when message is a :call symbol literal (https://github.com/Shopify/ruby-lsp/pull/236)
20
+ - Alert users about non auto-correctable diagnostics (https://github.com/Shopify/ruby-lsp/pull/230)
21
+ - Let clients pull diagnostics instead of pushing on edits (https://github.com/Shopify/ruby-lsp/pull/242)
22
+
23
+ ## [0.2.1]
24
+
25
+ - Implement the exit lifecycle request (https://github.com/Shopify/ruby-lsp/pull/198)
26
+ - Remove the Sorbet runtime from the gem's default load path (https://github.com/Shopify/ruby-lsp/pull/214)
27
+ - Return nil if the document is already formatted (https://github.com/Shopify/ruby-lsp/pull/216)
28
+ - Handle nameless keyword rest parameters in semantic highlighting (https://github.com/Shopify/ruby-lsp/pull/222)
29
+ - Display a warning on invalid RuboCop configuration (https://github.com/Shopify/ruby-lsp/pull/226)
30
+ - Centralize request handling logic in server.rb (https://github.com/Shopify/ruby-lsp/pull/221)
31
+ - Fix folding ranges for chained invocations involving an FCall (https://github.com/Shopify/ruby-lsp/pull/223)
32
+ - Fix handling of argument fowarding in semantic highlighting (https://github.com/Shopify/ruby-lsp/pull/228)
33
+ - Recover from initial syntax errors when opening documents (https://github.com/Shopify/ruby-lsp/pull/224)
34
+ - Highlight occurrences and definitions in document highlight (https://github.com/Shopify/ruby-lsp/pull/187)
35
+
9
36
  ## [0.2.0]
10
37
 
11
38
  - Add semantic token for keyword and keyword rest params (https://github.com/Shopify/ruby-lsp/pull/142)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.2.3
data/exe/ruby-lsp CHANGED
@@ -18,6 +18,4 @@ rescue
18
18
  nil
19
19
  end
20
20
 
21
- require_relative "../lib/ruby_lsp/internal"
22
-
23
- RubyLsp::Cli.start
21
+ require_relative "../lib/ruby_lsp/server"
data/lib/ruby-lsp.rb CHANGED
@@ -1,6 +1,6 @@
1
- # typed: strict
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
- VERSION = T.let(File.read(File.expand_path("../VERSION", __dir__)).strip, String)
5
+ VERSION = File.read(File.expand_path("../VERSION", __dir__)).strip
6
6
  end
@@ -9,7 +9,7 @@ module RubyLsp
9
9
  RangeShape = T.type_alias { { start: PositionShape, end: PositionShape } }
10
10
  EditShape = T.type_alias { { range: RangeShape, text: String } }
11
11
 
12
- sig { returns(SyntaxTree::Node) }
12
+ sig { returns(T.nilable(SyntaxTree::Node)) }
13
13
  attr_reader :tree
14
14
 
15
15
  sig { returns(String) }
@@ -20,11 +20,13 @@ module RubyLsp
20
20
 
21
21
  sig { params(source: String).void }
22
22
  def initialize(source)
23
- @tree = T.let(SyntaxTree.parse(source), SyntaxTree::Node)
24
23
  @cache = T.let({}, T::Hash[Symbol, T.untyped])
25
24
  @syntax_error_edits = T.let([], T::Array[EditShape])
26
- @source = source
25
+ @source = T.let(source, String)
27
26
  @parsable_source = T.let(source.dup, String)
27
+ @tree = T.let(SyntaxTree.parse(@source), T.nilable(SyntaxTree::Node))
28
+ rescue SyntaxTree::Parser::ParseError
29
+ # Do not raise if we failed to parse
28
30
  end
29
31
 
30
32
  sig { params(other: Document).returns(T::Boolean) }
@@ -67,6 +69,11 @@ module RubyLsp
67
69
  @syntax_error_edits.any?
68
70
  end
69
71
 
72
+ sig { returns(T::Boolean) }
73
+ def parsed?
74
+ !@tree.nil?
75
+ end
76
+
70
77
  private
71
78
 
72
79
  sig { params(edits: T::Array[EditShape]).void }
@@ -6,17 +6,24 @@ require "ruby_lsp/store"
6
6
  require "benchmark"
7
7
 
8
8
  module RubyLsp
9
+ Interface = LanguageServer::Protocol::Interface
10
+ Constant = LanguageServer::Protocol::Constant
11
+ Transport = LanguageServer::Protocol::Transport
12
+
9
13
  class Handler
10
14
  extend T::Sig
11
15
  VOID = T.let(Object.new.freeze, Object)
12
16
 
17
+ sig { params(blk: T.proc.bind(Handler).params(arg0: T.untyped).void).void }
18
+ def self.start(&blk)
19
+ handler = new
20
+ handler.instance_exec(&blk)
21
+ handler.start
22
+ end
23
+
13
24
  sig { returns(Store) }
14
25
  attr_reader :store
15
26
 
16
- Interface = LanguageServer::Protocol::Interface
17
- Constant = LanguageServer::Protocol::Constant
18
- Transport = LanguageServer::Protocol::Transport
19
-
20
27
  sig { void }
21
28
  def initialize
22
29
  @writer = T.let(Transport::Stdio::Writer.new, Transport::Stdio::Writer)
@@ -31,11 +38,6 @@ module RubyLsp
31
38
  @reader.read { |request| handle(request) }
32
39
  end
33
40
 
34
- sig { params(blk: T.proc.bind(Handler).params(arg0: T.untyped).void).void }
35
- def config(&blk)
36
- instance_exec(&blk)
37
- end
38
-
39
41
  private
40
42
 
41
43
  sig do
@@ -84,118 +86,6 @@ module RubyLsp
84
86
  store.clear
85
87
  end
86
88
 
87
- sig { params(enabled_features: T::Array[String]).returns(Interface::InitializeResult) }
88
- def respond_with_capabilities(enabled_features)
89
- document_symbol_provider = if enabled_features.include?("documentSymbols")
90
- Interface::DocumentSymbolClientCapabilities.new(
91
- hierarchical_document_symbol_support: true,
92
- symbol_kind: {
93
- value_set: Requests::DocumentSymbol::SYMBOL_KIND.values,
94
- }
95
- )
96
- end
97
-
98
- folding_ranges_provider = if enabled_features.include?("foldingRanges")
99
- Interface::FoldingRangeClientCapabilities.new(line_folding_only: true)
100
- end
101
-
102
- semantic_tokens_provider = if enabled_features.include?("semanticHighlighting")
103
- Interface::SemanticTokensRegistrationOptions.new(
104
- document_selector: { scheme: "file", language: "ruby" },
105
- legend: Interface::SemanticTokensLegend.new(
106
- token_types: Requests::SemanticHighlighting::TOKEN_TYPES.keys,
107
- token_modifiers: Requests::SemanticHighlighting::TOKEN_MODIFIERS.keys
108
- ),
109
- range: false,
110
- full: {
111
- delta: true,
112
- }
113
- )
114
- end
115
-
116
- Interface::InitializeResult.new(
117
- capabilities: Interface::ServerCapabilities.new(
118
- text_document_sync: Interface::TextDocumentSyncOptions.new(
119
- change: Constant::TextDocumentSyncKind::INCREMENTAL,
120
- open_close: true,
121
- ),
122
- selection_range_provider: enabled_features.include?("selectionRanges"),
123
- document_symbol_provider: document_symbol_provider,
124
- folding_range_provider: folding_ranges_provider,
125
- semantic_tokens_provider: semantic_tokens_provider,
126
- document_formatting_provider: enabled_features.include?("formatting"),
127
- document_highlight_provider: enabled_features.include?("documentHighlights"),
128
- code_action_provider: enabled_features.include?("codeActions")
129
- )
130
- )
131
- end
132
-
133
- sig { params(uri: String).returns(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol]) }
134
- def respond_with_document_symbol(uri)
135
- store.cache_fetch(uri, :document_symbol) do |document|
136
- RubyLsp::Requests::DocumentSymbol.new(document).run
137
- end
138
- end
139
-
140
- sig { params(uri: String).returns(T::Array[LanguageServer::Protocol::Interface::FoldingRange]) }
141
- def respond_with_folding_ranges(uri)
142
- store.cache_fetch(uri, :folding_ranges) do |document|
143
- Requests::FoldingRanges.new(document).run
144
- end
145
- end
146
-
147
- sig do
148
- params(
149
- uri: String,
150
- positions: T::Array[Document::PositionShape]
151
- ).returns(T::Array[T.nilable(RubyLsp::Requests::Support::SelectionRange)])
152
- end
153
- def respond_with_selection_ranges(uri, positions)
154
- ranges = store.cache_fetch(uri, :selection_ranges) do |document|
155
- Requests::SelectionRanges.new(document).run
156
- end
157
-
158
- # Per the selection range request spec (https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange),
159
- # every position in the positions array should have an element at the same index in the response
160
- # array. For positions without a valid selection range, the corresponding element in the response
161
- # array will be nil.
162
- positions.map do |position|
163
- ranges.find do |range|
164
- range.cover?(position)
165
- end
166
- end
167
- end
168
-
169
- sig { params(uri: String).returns(LanguageServer::Protocol::Interface::SemanticTokens) }
170
- def respond_with_semantic_highlighting(uri)
171
- store.cache_fetch(uri, :semantic_highlighting) do |document|
172
- T.cast(
173
- Requests::SemanticHighlighting.new(document, encoder: Requests::Support::SemanticTokenEncoder.new).run,
174
- LanguageServer::Protocol::Interface::SemanticTokens
175
- )
176
- end
177
- end
178
-
179
- sig { params(uri: String).returns(T.nilable(T::Array[LanguageServer::Protocol::Interface::TextEdit])) }
180
- def respond_with_formatting(uri)
181
- Requests::Formatting.new(uri, store.get(uri)).run
182
- end
183
-
184
- sig { params(uri: String).void }
185
- def send_diagnostics(uri)
186
- response = store.cache_fetch(uri, :diagnostics) do |document|
187
- Requests::Diagnostics.new(uri, document).run
188
- end
189
-
190
- @writer.write(
191
- method: "textDocument/publishDiagnostics",
192
- params: Interface::PublishDiagnosticsParams.new(
193
- uri: uri,
194
- diagnostics: response.map(&:to_lsp_diagnostic)
195
- )
196
- )
197
- end
198
-
199
89
  sig { params(uri: String).void }
200
90
  def clear_diagnostics(uri)
201
91
  @writer.write(
@@ -204,23 +94,12 @@ module RubyLsp
204
94
  )
205
95
  end
206
96
 
207
- sig do
208
- params(uri: String, range: T::Range[Integer]).returns(T::Array[LanguageServer::Protocol::Interface::CodeAction])
209
- end
210
- def respond_with_code_actions(uri, range)
211
- store.cache_fetch(uri, :code_actions) do |document|
212
- Requests::CodeActions.new(uri, document, range).run
213
- end
214
- end
215
-
216
- sig do
217
- params(
218
- uri: String,
219
- position: Document::PositionShape
220
- ).returns(T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
221
- end
222
- def respond_with_document_highlight(uri, position)
223
- Requests::DocumentHighlight.new(store.get(uri), position).run
97
+ sig { params(type: Integer, message: String).void }
98
+ def show_message(type, message)
99
+ @writer.write(
100
+ method: "window/showMessage",
101
+ params: Interface::ShowMessageParams.new(type: type, message: message)
102
+ )
224
103
  end
225
104
 
226
105
  sig do
@@ -3,5 +3,7 @@
3
3
 
4
4
  require "sorbet-runtime"
5
5
  require "syntax_tree"
6
+ require "language_server-protocol"
7
+
6
8
  require "ruby-lsp"
7
- require "ruby_lsp/cli"
9
+ require "ruby_lsp/handler"
@@ -25,21 +25,11 @@ module RubyLsp
25
25
  class DocumentHighlight < BaseRequest
26
26
  extend T::Sig
27
27
 
28
- VarNodes = T.type_alias do
29
- T.any(
30
- SyntaxTree::GVar,
31
- SyntaxTree::Ident,
32
- SyntaxTree::IVar,
33
- SyntaxTree::Const,
34
- SyntaxTree::CVar
35
- )
36
- end
37
-
38
28
  sig { params(document: Document, position: Document::PositionShape).void }
39
29
  def initialize(document, position)
40
30
  @highlights = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
41
31
  position = Document::Scanner.new(document.source).find_position(position)
42
- @target = T.let(find(document.tree, position), T.nilable(VarNodes))
32
+ @target = T.let(find(T.must(document.tree), position), T.nilable(Support::HighlightTarget))
43
33
 
44
34
  super(document)
45
35
  end
@@ -53,33 +43,24 @@ module RubyLsp
53
43
  @highlights
54
44
  end
55
45
 
56
- sig { params(node: SyntaxTree::VarField).void }
57
- def visit_var_field(node)
58
- if matches_target?(node.value)
59
- add_highlight(
60
- node.value,
61
- LanguageServer::Protocol::Constant::DocumentHighlightKind::WRITE
62
- )
63
- end
64
-
65
- super
66
- end
46
+ sig { params(node: T.nilable(SyntaxTree::Node)).void }
47
+ def visit(node)
48
+ return if node.nil?
67
49
 
68
- sig { params(node: SyntaxTree::VarRef).void }
69
- def visit_var_ref(node)
70
- if matches_target?(node.value)
71
- add_highlight(
72
- node.value,
73
- LanguageServer::Protocol::Constant::DocumentHighlightKind::READ
74
- )
75
- end
50
+ match = T.must(@target).highlight_type(node)
51
+ add_highlight(match) if match
76
52
 
77
53
  super
78
54
  end
79
55
 
80
56
  private
81
57
 
82
- sig { params(node: SyntaxTree::Node, position: Integer).returns(T.nilable(VarNodes)) }
58
+ sig do
59
+ params(
60
+ node: SyntaxTree::Node,
61
+ position: Integer,
62
+ ).returns(T.nilable(Support::HighlightTarget))
63
+ end
83
64
  def find(node, position)
84
65
  matched =
85
66
  node.child_nodes.compact.bsearch do |child|
@@ -91,22 +72,24 @@ module RubyLsp
91
72
  end
92
73
 
93
74
  case matched
94
- when SyntaxTree::GVar, SyntaxTree::Ident, SyntaxTree::IVar, SyntaxTree::Const, SyntaxTree::CVar
95
- matched
75
+ when SyntaxTree::GVar,
76
+ SyntaxTree::IVar,
77
+ SyntaxTree::Const,
78
+ SyntaxTree::CVar,
79
+ SyntaxTree::VarField
80
+ Support::HighlightTarget.new(matched)
81
+ when SyntaxTree::Ident
82
+ relevant_node = node.is_a?(SyntaxTree::Params) ? matched : node
83
+ Support::HighlightTarget.new(relevant_node)
96
84
  when SyntaxTree::Node
97
85
  find(matched, position)
98
86
  end
99
87
  end
100
88
 
101
- sig { params(node: SyntaxTree::Node).returns(T::Boolean) }
102
- def matches_target?(node)
103
- node.is_a?(@target.class) && T.cast(node, VarNodes).value == T.must(@target).value
104
- end
105
-
106
- sig { params(node: SyntaxTree::Node, kind: Integer).void }
107
- def add_highlight(node, kind)
108
- range = range_from_syntax_tree_node(node)
109
- @highlights << LanguageServer::Protocol::Interface::DocumentHighlight.new(range: range, kind: kind)
89
+ sig { params(match: Support::HighlightTarget::HighlightMatch).void }
90
+ def add_highlight(match)
91
+ range = range_from_syntax_tree_node(match.node)
92
+ @highlights << LanguageServer::Protocol::Interface::DocumentHighlight.new(range: range, kind: match.type)
110
93
  end
111
94
  end
112
95
  end
@@ -0,0 +1,119 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "ruby_lsp/requests/support/source_uri"
5
+
6
+ module RubyLsp
7
+ module Requests
8
+ # ![Document link demo](../../misc/document_link.gif)
9
+ #
10
+ # The [document link](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentLink)
11
+ # makes `# source://PATH_TO_FILE#line` comments in a Ruby/RBI file clickable if the file exists.
12
+ # When the user clicks the link, it'll open that location.
13
+ #
14
+ # # Example
15
+ #
16
+ # ```ruby
17
+ # # source://syntax_tree/3.2.1/lib/syntax_tree.rb#51 <- it will be clickable and will take the user to that location
18
+ # def format(source, maxwidth = T.unsafe(nil))
19
+ # end
20
+ # ```
21
+ class DocumentLink < BaseRequest
22
+ extend T::Sig
23
+
24
+ GEM_TO_VERSION_MAP = T.let(
25
+ [*::Gem::Specification.default_stubs, *::Gem::Specification.stubs].map! do |s|
26
+ [s.name, s.version.to_s]
27
+ end.to_h.freeze,
28
+ T::Hash[String, String]
29
+ )
30
+
31
+ class << self
32
+ extend T::Sig
33
+
34
+ sig { returns(T::Hash[String, T::Array[String]]) }
35
+ def gem_paths
36
+ @gem_paths ||= T.let(begin
37
+ lookup = {}
38
+
39
+ Gem::Specification.stubs.each do |stub|
40
+ spec = stub.to_spec
41
+ lookup[spec.name] = {}
42
+ lookup[spec.name][spec.version.to_s] = {}
43
+
44
+ Dir.glob("**/*.rb", base: "#{spec.full_gem_path}/").each do |path|
45
+ lookup[spec.name][spec.version.to_s][path] = "#{spec.full_gem_path}/#{path}"
46
+ end
47
+ end
48
+
49
+ Gem::Specification.default_stubs.each do |stub|
50
+ spec = stub.to_spec
51
+ lookup[spec.name] = {}
52
+ lookup[spec.name][spec.version.to_s] = {}
53
+ prefix_matchers = [//]
54
+ prefix_matchers.concat(spec.require_paths.map { |rp| Regexp.new("^#{rp}/") })
55
+ prefix_matcher = Regexp.union(prefix_matchers)
56
+
57
+ spec.files.each do |file|
58
+ path = file.sub(prefix_matcher, "")
59
+ lookup[spec.name][spec.version.to_s][path] = "#{RbConfig::CONFIG["rubylibdir"]}/#{path}"
60
+ end
61
+ end
62
+
63
+ lookup
64
+ end, T.nilable(T::Hash[String, T::Array[String]]))
65
+ end
66
+ end
67
+
68
+ sig { params(uri: String, document: Document).void }
69
+ def initialize(uri, document)
70
+ super(document)
71
+
72
+ # Match the version based on the version in the RBI file name. Notice that the `@` symbol is sanitized to `%40`
73
+ # in the URI
74
+ version_match = /(?<=%40)[\d.]+(?=\.rbi$)/.match(uri)
75
+ @gem_version = T.let(version_match && version_match[0], T.nilable(String))
76
+ @links = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentLink])
77
+ end
78
+
79
+ sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentLink], Object)) }
80
+ def run
81
+ visit(@document.tree)
82
+ @links
83
+ end
84
+
85
+ sig { params(node: SyntaxTree::Comment).void }
86
+ def visit_comment(node)
87
+ match = node.value.match(%r{source://.*#\d+$})
88
+ return unless match
89
+
90
+ uri = T.cast(URI(match[0]), URI::Source)
91
+ gem_version = resolve_version(uri)
92
+ file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, uri.path)
93
+ return if file_path.nil?
94
+
95
+ @links << LanguageServer::Protocol::Interface::DocumentLink.new(
96
+ range: range_from_syntax_tree_node(node),
97
+ target: "file://#{file_path}##{uri.line_number}",
98
+ tooltip: "Jump to #{file_path}##{uri.line_number}"
99
+ )
100
+ end
101
+
102
+ private
103
+
104
+ # Try to figure out the gem version for a source:// link. The order of precedence is:
105
+ # 1. The version in the URI
106
+ # 2. The version in the RBI file name
107
+ # 3. The version from the gemspec
108
+ sig { params(uri: URI::Source).returns(T.nilable(String)) }
109
+ def resolve_version(uri)
110
+ version = uri.gem_version
111
+ return version unless version.nil? || version.empty?
112
+
113
+ return @gem_version unless @gem_version.nil? || @gem_version.empty?
114
+
115
+ GEM_TO_VERSION_MAP[uri.gem_name]
116
+ end
117
+ end
118
+ end
119
+ end
@@ -195,7 +195,11 @@ module RubyLsp
195
195
  receiver = receiver.receiver
196
196
  when SyntaxTree::MethodAddBlock
197
197
  visit(receiver.block)
198
- receiver = receiver.call.receiver
198
+ receiver = receiver.call
199
+
200
+ if receiver.is_a?(SyntaxTree::Call) || receiver.is_a?(SyntaxTree::CommandCall)
201
+ receiver = receiver.receiver
202
+ end
199
203
  else
200
204
  break
201
205
  end
@@ -34,6 +34,7 @@ module RubyLsp
34
34
  return unless formatted_text
35
35
 
36
36
  size = @document.source.size
37
+ return if formatted_text.size == size && formatted_text == @document.source
37
38
 
38
39
  [
39
40
  LanguageServer::Protocol::Interface::TextEdit.new(
@@ -60,10 +60,13 @@ module RubyLsp
60
60
  default_library: 9,
61
61
  }.freeze, T::Hash[Symbol, Integer])
62
62
 
63
- SPECIAL_RUBY_METHODS = T.let((Module.instance_methods(false) +
64
- Kernel.methods(false) + Bundler::Dsl.instance_methods(false) +
65
- Module.private_instance_methods(false))
66
- .map(&:to_s), T::Array[String])
63
+ SPECIAL_RUBY_METHODS = T.let([
64
+ Module.instance_methods(false),
65
+ Kernel.instance_methods(false),
66
+ Kernel.methods(false),
67
+ Bundler::Dsl.instance_methods(false),
68
+ Module.private_instance_methods(false),
69
+ ].flatten.map(&:to_s), T::Array[String])
67
70
 
68
71
  class SemanticToken < T::Struct
69
72
  const :location, SyntaxTree::Location
@@ -78,7 +81,7 @@ module RubyLsp
78
81
 
79
82
  @encoder = encoder
80
83
  @tokens = T.let([], T::Array[SemanticToken])
81
- @tree = T.let(document.tree, SyntaxTree::Node)
84
+ @tree = T.let(T.must(document.tree), SyntaxTree::Node)
82
85
  @special_methods = T.let(nil, T.nilable(T::Array[String]))
83
86
  end
84
87
 
@@ -105,7 +108,10 @@ module RubyLsp
105
108
  sig { params(node: SyntaxTree::Call).void }
106
109
  def visit_call(node)
107
110
  visit(node.receiver)
108
- add_token(node.message.location, :method)
111
+
112
+ message = node.message
113
+ add_token(message.location, :method) if message != :call
114
+
109
115
  visit(node.arguments)
110
116
  end
111
117
 
@@ -179,7 +185,11 @@ module RubyLsp
179
185
  add_token(location_without_colon(location), :variable)
180
186
  end
181
187
 
182
- add_token(node.keyword_rest.name.location, :variable) if node.keyword_rest
188
+ rest = node.keyword_rest
189
+ return if rest.nil? || rest.is_a?(SyntaxTree::ArgsForward)
190
+
191
+ name = rest.name
192
+ add_token(name.location, :variable) if name
183
193
  end
184
194
 
185
195
  sig { params(node: SyntaxTree::VarField).void }