ruby-lsp 0.2.0 → 0.2.3

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