ruby-lsp 0.14.6 → 0.16.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +1 -16
  5. data/exe/ruby-lsp-check +13 -22
  6. data/exe/ruby-lsp-doctor +9 -0
  7. data/lib/ruby_indexer/lib/ruby_indexer/collector.rb +14 -1
  8. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +11 -23
  9. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +32 -8
  10. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +26 -0
  11. data/lib/ruby_indexer/ruby_indexer.rb +1 -0
  12. data/lib/ruby_indexer/test/classes_and_modules_test.rb +46 -0
  13. data/lib/ruby_indexer/test/configuration_test.rb +2 -11
  14. data/lib/ruby_lsp/addon.rb +18 -9
  15. data/lib/ruby_lsp/base_server.rb +149 -0
  16. data/lib/ruby_lsp/document.rb +6 -11
  17. data/lib/ruby_lsp/global_state.rb +180 -0
  18. data/lib/ruby_lsp/internal.rb +4 -1
  19. data/lib/ruby_lsp/listeners/code_lens.rb +22 -13
  20. data/lib/ruby_lsp/listeners/completion.rb +13 -14
  21. data/lib/ruby_lsp/listeners/definition.rb +14 -6
  22. data/lib/ruby_lsp/listeners/document_symbol.rb +91 -3
  23. data/lib/ruby_lsp/listeners/hover.rb +6 -5
  24. data/lib/ruby_lsp/listeners/signature_help.rb +7 -4
  25. data/lib/ruby_lsp/load_sorbet.rb +62 -0
  26. data/lib/ruby_lsp/requests/code_lens.rb +3 -2
  27. data/lib/ruby_lsp/requests/completion.rb +15 -4
  28. data/lib/ruby_lsp/requests/completion_resolve.rb +56 -0
  29. data/lib/ruby_lsp/requests/definition.rb +11 -4
  30. data/lib/ruby_lsp/requests/diagnostics.rb +8 -11
  31. data/lib/ruby_lsp/requests/document_symbol.rb +3 -3
  32. data/lib/ruby_lsp/requests/formatting.rb +7 -43
  33. data/lib/ruby_lsp/requests/hover.rb +4 -4
  34. data/lib/ruby_lsp/requests/request.rb +2 -0
  35. data/lib/ruby_lsp/requests/semantic_highlighting.rb +7 -4
  36. data/lib/ruby_lsp/requests/signature_help.rb +4 -3
  37. data/lib/ruby_lsp/requests/support/common.rb +16 -5
  38. data/lib/ruby_lsp/requests/support/formatter.rb +26 -0
  39. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +1 -1
  40. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +47 -0
  41. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
  42. data/lib/ruby_lsp/requests/support/{syntax_tree_formatting_runner.rb → syntax_tree_formatter.rb} +13 -6
  43. data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
  44. data/lib/ruby_lsp/requests.rb +3 -1
  45. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +36 -13
  46. data/lib/ruby_lsp/server.rb +763 -142
  47. data/lib/ruby_lsp/setup_bundler.rb +13 -1
  48. data/lib/ruby_lsp/store.rb +3 -15
  49. data/lib/ruby_lsp/test_helper.rb +52 -0
  50. data/lib/ruby_lsp/utils.rb +68 -33
  51. metadata +16 -13
  52. data/lib/ruby_lsp/executor.rb +0 -614
  53. data/lib/ruby_lsp/requests/support/dependency_detector.rb +0 -93
  54. data/lib/ruby_lsp/requests/support/formatter_runner.rb +0 -18
  55. data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +0 -34
  56. data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +0 -35
@@ -86,13 +86,16 @@ module RubyLsp
86
86
  params(
87
87
  title: String,
88
88
  entries: T.any(T::Array[RubyIndexer::Entry], RubyIndexer::Entry),
89
+ max_entries: T.nilable(Integer),
89
90
  ).returns(T::Hash[Symbol, String])
90
91
  end
91
- def categorized_markdown_from_index_entries(title, entries)
92
+ def categorized_markdown_from_index_entries(title, entries, max_entries = nil)
92
93
  markdown_title = "```ruby\n#{title}\n```"
93
94
  definitions = []
94
95
  content = +""
95
- Array(entries).each do |entry|
96
+ entries = Array(entries)
97
+ entries_to_format = max_entries ? entries.take(max_entries) : entries
98
+ entries_to_format.each do |entry|
96
99
  loc = entry.location
97
100
 
98
101
  # We always handle locations as zero based. However, for file links in Markdown we need them to be one
@@ -108,9 +111,16 @@ module RubyLsp
108
111
  content << "\n\n#{entry.comments.join("\n")}" unless entry.comments.empty?
109
112
  end
110
113
 
114
+ additional_entries_text = if max_entries && entries.length > max_entries
115
+ additional = entries.length - max_entries
116
+ " | #{additional} other#{additional > 1 ? "s" : ""}"
117
+ else
118
+ ""
119
+ end
120
+
111
121
  {
112
122
  title: markdown_title,
113
- links: "**Definitions**: #{definitions.join(" | ")}",
123
+ links: "**Definitions**: #{definitions.join(" | ")}#{additional_entries_text}",
114
124
  documentation: content,
115
125
  }
116
126
  end
@@ -119,10 +129,11 @@ module RubyLsp
119
129
  params(
120
130
  title: String,
121
131
  entries: T.any(T::Array[RubyIndexer::Entry], RubyIndexer::Entry),
132
+ max_entries: T.nilable(Integer),
122
133
  ).returns(String)
123
134
  end
124
- def markdown_from_index_entries(title, entries)
125
- categorized_markdown = categorized_markdown_from_index_entries(title, entries)
135
+ def markdown_from_index_entries(title, entries, max_entries = nil)
136
+ categorized_markdown = categorized_markdown_from_index_entries(title, entries, max_entries)
126
137
 
127
138
  <<~MARKDOWN.chomp
128
139
  #{categorized_markdown[:title]}
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ module Support
7
+ module Formatter
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ interface!
12
+
13
+ sig { abstract.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
14
+ def run_formatting(uri, document); end
15
+
16
+ sig do
17
+ abstract.params(
18
+ uri: URI::Generic,
19
+ document: Document,
20
+ ).returns(T.nilable(T::Array[Interface::Diagnostic]))
21
+ end
22
+ def run_diagnostic(uri, document); end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -163,7 +163,7 @@ module RubyLsp
163
163
 
164
164
  sig { params(line: String).returns(Integer) }
165
165
  def length_of_line(line)
166
- if @document.encoding == Constant::PositionEncodingKind::UTF16
166
+ if @document.encoding == Encoding::UTF_16LE
167
167
  line_length = 0
168
168
  line.codepoints.each do |codepoint|
169
169
  line_length += 1
@@ -0,0 +1,47 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ return unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
5
+
6
+ module RubyLsp
7
+ module Requests
8
+ module Support
9
+ class RuboCopFormatter
10
+ extend T::Sig
11
+ include Formatter
12
+
13
+ sig { void }
14
+ def initialize
15
+ @diagnostic_runner = T.let(RuboCopRunner.new, RuboCopRunner)
16
+ # -a is for "--auto-correct" (or "--autocorrect" on newer versions of RuboCop)
17
+ @format_runner = T.let(RuboCopRunner.new("-a"), RuboCopRunner)
18
+ end
19
+
20
+ sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
21
+ def run_formatting(uri, document)
22
+ filename = T.must(uri.to_standardized_path || uri.opaque)
23
+
24
+ # Invoke RuboCop with just this file in `paths`
25
+ @format_runner.run(filename, document.source)
26
+ @format_runner.formatted_source
27
+ end
28
+
29
+ sig do
30
+ override.params(
31
+ uri: URI::Generic,
32
+ document: Document,
33
+ ).returns(T.nilable(T::Array[Interface::Diagnostic]))
34
+ end
35
+ def run_diagnostic(uri, document)
36
+ filename = T.must(uri.to_standardized_path || uri.opaque)
37
+ # Invoke RuboCop with just this file in `paths`
38
+ @diagnostic_runner.run(filename, document.source)
39
+
40
+ @diagnostic_runner.offenses.map do |offense|
41
+ Support::RuboCopDiagnostic.new(document, offense, uri).to_lsp_diagnostic
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -92,6 +92,10 @@ module RubyLsp
92
92
  @options[:stdin] = contents
93
93
 
94
94
  super([path])
95
+
96
+ # RuboCop rescues interrupts and then sets the `@aborting` variable to true. We don't want them to be rescued,
97
+ # so here we re-raise in case RuboCop received an interrupt.
98
+ raise Interrupt if aborting?
95
99
  rescue RuboCop::Runner::InfiniteCorrectionLoop => error
96
100
  raise Formatting::Error, error.message
97
101
  rescue RuboCop::ValidationError => error
@@ -8,16 +8,13 @@ rescue LoadError
8
8
  return
9
9
  end
10
10
 
11
- require "singleton"
12
-
13
11
  module RubyLsp
14
12
  module Requests
15
13
  module Support
16
14
  # :nodoc:
17
- class SyntaxTreeFormattingRunner
15
+ class SyntaxTreeFormatter
18
16
  extend T::Sig
19
- include Singleton
20
- include Support::FormatterRunner
17
+ include Support::Formatter
21
18
 
22
19
  sig { void }
23
20
  def initialize
@@ -33,12 +30,22 @@ module RubyLsp
33
30
  end
34
31
 
35
32
  sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
36
- def run(uri, document)
33
+ def run_formatting(uri, document)
37
34
  path = uri.to_standardized_path
38
35
  return if path && @options.ignore_files.any? { |pattern| File.fnmatch?("*/#{pattern}", path) }
39
36
 
40
37
  SyntaxTree.format(document.source, @options.print_width, options: @options.formatter_options)
41
38
  end
39
+
40
+ sig do
41
+ override.params(
42
+ uri: URI::Generic,
43
+ document: Document,
44
+ ).returns(T.nilable(T::Array[Interface::Diagnostic]))
45
+ end
46
+ def run_diagnostic(uri, document)
47
+ nil
48
+ end
42
49
  end
43
50
  end
44
51
  end
@@ -22,11 +22,12 @@ module RubyLsp
22
22
  extend T::Sig
23
23
  include Support::Common
24
24
 
25
- sig { params(query: T.nilable(String), index: RubyIndexer::Index).void }
26
- def initialize(query, index)
25
+ sig { params(global_state: GlobalState, query: T.nilable(String)).void }
26
+ def initialize(global_state, query)
27
27
  super()
28
+ @global_state = global_state
28
29
  @query = query
29
- @index = index
30
+ @index = T.let(global_state.index, RubyIndexer::Index)
30
31
  end
31
32
 
32
33
  sig { override.returns(T::Array[Interface::WorkspaceSymbol]) }
@@ -35,7 +36,7 @@ module RubyLsp
35
36
  # If the project is using Sorbet, we let Sorbet handle symbols defined inside the project itself and RBIs, but
36
37
  # we still return entries defined in gems to allow developers to jump directly to the source
37
38
  file_path = entry.file_path
38
- next if DependencyDetector.instance.typechecker && not_in_dependencies?(file_path)
39
+ next if @global_state.typechecker && not_in_dependencies?(file_path)
39
40
 
40
41
  # We should never show private symbols when searching the entire workspace
41
42
  next if entry.visibility == :private
@@ -18,6 +18,7 @@ module RubyLsp
18
18
  # - [DocumentHighlight](rdoc-ref:RubyLsp::Requests::DocumentHighlight)
19
19
  # - [InlayHint](rdoc-ref:RubyLsp::Requests::InlayHints)
20
20
  # - [Completion](rdoc-ref:RubyLsp::Requests::Completion)
21
+ # - [CompletionResolve](rdoc-ref:RubyLsp::Requests::CompletionResolve)
21
22
  # - [CodeLens](rdoc-ref:RubyLsp::Requests::CodeLens)
22
23
  # - [Definition](rdoc-ref:RubyLsp::Requests::Definition)
23
24
  # - [ShowSyntaxTree](rdoc-ref:RubyLsp::Requests::ShowSyntaxTree)
@@ -40,6 +41,7 @@ module RubyLsp
40
41
  autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
41
42
  autoload :InlayHints, "ruby_lsp/requests/inlay_hints"
42
43
  autoload :Completion, "ruby_lsp/requests/completion"
44
+ autoload :CompletionResolve, "ruby_lsp/requests/completion_resolve"
43
45
  autoload :CodeLens, "ruby_lsp/requests/code_lens"
44
46
  autoload :Definition, "ruby_lsp/requests/definition"
45
47
  autoload :ShowSyntaxTree, "ruby_lsp/requests/show_syntax_tree"
@@ -54,7 +56,7 @@ module RubyLsp
54
56
  autoload :Sorbet, "ruby_lsp/requests/support/sorbet"
55
57
  autoload :RailsDocumentClient, "ruby_lsp/requests/support/rails_document_client"
56
58
  autoload :Common, "ruby_lsp/requests/support/common"
57
- autoload :FormatterRunner, "ruby_lsp/requests/support/formatter_runner"
59
+ autoload :Formatter, "ruby_lsp/requests/support/formatter"
58
60
  end
59
61
  end
60
62
  end
@@ -55,19 +55,21 @@ module RubyLsp
55
55
 
56
56
  ResponseType = type_member { { fixed: Interface::SemanticTokens } }
57
57
 
58
- sig { void }
59
- def initialize
60
- super
58
+ sig { params(encoding: Encoding).void }
59
+ def initialize(encoding)
60
+ super()
61
+ @encoding = encoding
61
62
  @stack = T.let([], T::Array[SemanticToken])
62
63
  end
63
64
 
64
65
  sig { params(location: Prism::Location, type: Symbol, modifiers: T::Array[Symbol]).void }
65
66
  def add_token(location, type, modifiers = [])
66
- length = location.end_offset - location.start_offset
67
+ length = location.end_code_units_offset(@encoding) - location.start_code_units_offset(@encoding)
67
68
  modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] }
68
69
  @stack.push(
69
70
  SemanticToken.new(
70
- location: location,
71
+ start_line: location.start_line,
72
+ start_code_unit_column: location.start_code_units_column(@encoding),
71
73
  length: length,
72
74
  type: T.must(TOKEN_TYPES[type]),
73
75
  modifier: modifiers_indices,
@@ -75,6 +77,15 @@ module RubyLsp
75
77
  )
76
78
  end
77
79
 
80
+ sig { params(location: Prism::Location).returns(T::Boolean) }
81
+ def last_token_matches?(location)
82
+ token = @stack.last
83
+ return false unless token
84
+
85
+ token.start_line == location.start_line &&
86
+ token.start_code_unit_column == location.start_code_units_column(@encoding)
87
+ end
88
+
78
89
  sig { returns(T.nilable(SemanticToken)) }
79
90
  def last
80
91
  @stack.last
@@ -88,8 +99,11 @@ module RubyLsp
88
99
  class SemanticToken
89
100
  extend T::Sig
90
101
 
91
- sig { returns(Prism::Location) }
92
- attr_reader :location
102
+ sig { returns(Integer) }
103
+ attr_reader :start_line
104
+
105
+ sig { returns(Integer) }
106
+ attr_reader :start_code_unit_column
93
107
 
94
108
  sig { returns(Integer) }
95
109
  attr_reader :length
@@ -100,9 +114,18 @@ module RubyLsp
100
114
  sig { returns(T::Array[Integer]) }
101
115
  attr_reader :modifier
102
116
 
103
- sig { params(location: Prism::Location, length: Integer, type: Integer, modifier: T::Array[Integer]).void }
104
- def initialize(location:, length:, type:, modifier:)
105
- @location = location
117
+ sig do
118
+ params(
119
+ start_line: Integer,
120
+ start_code_unit_column: Integer,
121
+ length: Integer,
122
+ type: Integer,
123
+ modifier: T::Array[Integer],
124
+ ).void
125
+ end
126
+ def initialize(start_line:, start_code_unit_column:, length:, type:, modifier:)
127
+ @start_line = start_line
128
+ @start_code_unit_column = start_code_unit_column
106
129
  @length = length
107
130
  @type = type
108
131
  @modifier = modifier
@@ -146,7 +169,7 @@ module RubyLsp
146
169
  # Enumerable#sort_by is not deterministic when the compared values are equal.
147
170
  # When that happens, we need to use the index as a tie breaker to ensure
148
171
  # that the order of the tokens is always the same.
149
- [token.location.start_line, token.location.start_column, index]
172
+ [token.start_line, token.start_code_unit_column, index]
150
173
  end
151
174
 
152
175
  delta = sorted_tokens.flat_map do |token|
@@ -167,8 +190,8 @@ module RubyLsp
167
190
  # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
168
191
  sig { params(token: SemanticToken).returns(T::Array[Integer]) }
169
192
  def compute_delta(token)
170
- row = token.location.start_line - 1
171
- column = token.location.start_column
193
+ row = token.start_line - 1
194
+ column = token.start_code_unit_column
172
195
 
173
196
  begin
174
197
  delta_line = row - @current_row