ruby-lsp 0.16.4 → 0.16.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94d4dfcda701bdf2ea1dd04e2a271572a139fe3d716e1bd535e2c05bcc9b3d23
4
- data.tar.gz: b79dfc816bf027e733fa5f44db3e2d1039ae860f9dee88fe996ab1a27089d467
3
+ metadata.gz: d725a0a2fbd58820e2a87efae85564498221feb8410292055b0caafd889284e6
4
+ data.tar.gz: 5376ca8a9d08ca0883fca3aa19a1a178102eb60f49ab3fe5f07bb1a58ba63ddf
5
5
  SHA512:
6
- metadata.gz: 9de1f73feba2284c75294542a0e9ae1196f092004fe83ef815b2e08a1b404daceebc0b3f6f8f59149f936c8f7414821942556bee522776a909c071765dcc3443
7
- data.tar.gz: b7617a6d508a94e5a06b8d39acb4d2bcc28d02832b19ab8567ed58b6b297aa3fc450dd0a116257fe91fc9545683550456c0d712fc32dc3f3ff716e2cdca2985e
6
+ metadata.gz: 81fbb2e5018ad55d8888c98e277e7ac6ed8b45314627b1a3e3dabde2cb8aac0284dc70487a694226e5a08817feb82b64daadfcb955e9642b288e575189a56015
7
+ data.tar.gz: f32ef084e4412752cc32df55da0dd7b985d3826db110bf91ba3473773043c50345ceb597432483a0a75f1790cabfca796fa9581d3ba86edff96a8642fb2312f2
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.16.4
1
+ 0.16.6
data/exe/ruby-lsp CHANGED
@@ -75,14 +75,18 @@ require "ruby_lsp/internal"
75
75
 
76
76
  if options[:debug]
77
77
  if ["x64-mingw-ucrt", "x64-mingw32"].include?(RUBY_PLATFORM)
78
- puts "Debugging is not supported on Windows"
78
+ $stderr.puts "Debugging is not supported on Windows"
79
79
  exit 1
80
80
  end
81
81
 
82
82
  begin
83
+ original_stdout = $stdout
84
+ $stdout = $stderr
83
85
  require "debug/open_nonstop"
84
86
  rescue LoadError
85
- warn("You need to install the debug gem to use the --debug flag")
87
+ $stderr.puts("You need to install the debug gem to use the --debug flag")
88
+ ensure
89
+ $stdout = original_stdout
86
90
  end
87
91
  end
88
92
 
@@ -56,6 +56,7 @@ module RubyIndexer
56
56
  load_path_entry = T.let(nil, T.nilable(String))
57
57
 
58
58
  Dir.glob(pattern, File::FNM_PATHNAME | File::FNM_EXTGLOB).map! do |path|
59
+ path = File.expand_path(path)
59
60
  # All entries for the same pattern match the same $LOAD_PATH entry. Since searching the $LOAD_PATH for every
60
61
  # entry is expensive, we memoize it until we find a path that doesn't belong to that $LOAD_PATH. This happens
61
62
  # on repositories that define multiple gems, like Rails. All frameworks are defined inside the Dir.pwd, but
@@ -11,7 +11,7 @@ module RubyIndexer
11
11
  sig { returns(String) }
12
12
  attr_reader :file_path
13
13
 
14
- sig { returns(Prism::Location) }
14
+ sig { returns(RubyIndexer::Location) }
15
15
  attr_reader :location
16
16
 
17
17
  sig { returns(T::Array[String]) }
@@ -20,13 +20,33 @@ module RubyIndexer
20
20
  sig { returns(Symbol) }
21
21
  attr_accessor :visibility
22
22
 
23
- sig { params(name: String, file_path: String, location: Prism::Location, comments: T::Array[String]).void }
23
+ sig do
24
+ params(
25
+ name: String,
26
+ file_path: String,
27
+ location: T.any(Prism::Location, RubyIndexer::Location),
28
+ comments: T::Array[String],
29
+ ).void
30
+ end
24
31
  def initialize(name, file_path, location, comments)
25
32
  @name = name
26
33
  @file_path = file_path
27
- @location = location
28
34
  @comments = comments
29
35
  @visibility = T.let(:public, Symbol)
36
+
37
+ @location = T.let(
38
+ if location.is_a?(Prism::Location)
39
+ Location.new(
40
+ location.start_line,
41
+ location.end_line,
42
+ location.start_column,
43
+ location.end_column,
44
+ )
45
+ else
46
+ location
47
+ end,
48
+ RubyIndexer::Location,
49
+ )
30
50
  end
31
51
 
32
52
  sig { returns(String) }
@@ -41,28 +61,13 @@ module RubyIndexer
41
61
  abstract!
42
62
 
43
63
  sig { returns(T::Array[String]) }
44
- attr_accessor :included_modules
45
-
46
- sig { returns(T::Array[String]) }
47
- attr_accessor :prepended_modules
48
-
49
- sig do
50
- params(
51
- name: String,
52
- file_path: String,
53
- location: Prism::Location,
54
- comments: T::Array[String],
55
- ).void
56
- end
57
- def initialize(name, file_path, location, comments)
58
- super(name, file_path, location, comments)
59
- @included_modules = T.let([], T::Array[String])
60
- @prepended_modules = T.let([], T::Array[String])
64
+ def included_modules
65
+ @included_modules ||= T.let([], T.nilable(T::Array[String]))
61
66
  end
62
67
 
63
- sig { returns(String) }
64
- def short_name
65
- T.must(@name.split("::").last)
68
+ sig { returns(T::Array[String]) }
69
+ def prepended_modules
70
+ @prepended_modules ||= T.let([], T.nilable(T::Array[String]))
66
71
  end
67
72
  end
68
73
 
@@ -81,7 +86,7 @@ module RubyIndexer
81
86
  params(
82
87
  name: String,
83
88
  file_path: String,
84
- location: Prism::Location,
89
+ location: T.any(Prism::Location, RubyIndexer::Location),
85
90
  comments: T::Array[String],
86
91
  parent_class: T.nilable(String),
87
92
  ).void
@@ -181,7 +186,7 @@ module RubyIndexer
181
186
  params(
182
187
  name: String,
183
188
  file_path: String,
184
- location: Prism::Location,
189
+ location: T.any(Prism::Location, RubyIndexer::Location),
185
190
  comments: T::Array[String],
186
191
  owner: T.nilable(Entry::Namespace),
187
192
  ).void
@@ -219,7 +224,7 @@ module RubyIndexer
219
224
  params(
220
225
  name: String,
221
226
  file_path: String,
222
- location: Prism::Location,
227
+ location: T.any(Prism::Location, RubyIndexer::Location),
223
228
  comments: T::Array[String],
224
229
  parameters_node: T.nilable(Prism::ParametersNode),
225
230
  owner: T.nilable(Entry::Namespace),
@@ -349,7 +354,7 @@ module RubyIndexer
349
354
  nesting: T::Array[String],
350
355
  name: String,
351
356
  file_path: String,
352
- location: Prism::Location,
357
+ location: T.any(Prism::Location, RubyIndexer::Location),
353
358
  comments: T::Array[String],
354
359
  ).void
355
360
  end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyIndexer
5
+ class Location
6
+ extend T::Sig
7
+
8
+ sig { returns(Integer) }
9
+ attr_reader :start_line, :end_line, :start_column, :end_column
10
+
11
+ sig do
12
+ params(
13
+ start_line: Integer,
14
+ end_line: Integer,
15
+ start_column: Integer,
16
+ end_column: Integer,
17
+ ).void
18
+ end
19
+ def initialize(start_line, end_line, start_column, end_column)
20
+ @start_line = start_line
21
+ @end_line = end_line
22
+ @start_column = start_column
23
+ @end_column = end_column
24
+ end
25
+ end
26
+ end
@@ -10,6 +10,7 @@ require "ruby_indexer/lib/ruby_indexer/index"
10
10
  require "ruby_indexer/lib/ruby_indexer/entry"
11
11
  require "ruby_indexer/lib/ruby_indexer/configuration"
12
12
  require "ruby_indexer/lib/ruby_indexer/prefix_tree"
13
+ require "ruby_indexer/lib/ruby_indexer/location"
13
14
 
14
15
  module RubyIndexer
15
16
  @configuration = T.let(Configuration.new, Configuration)
@@ -20,6 +20,14 @@ module RubyIndexer
20
20
  assert(indexables.none? { |indexable| indexable.full_path == __FILE__ })
21
21
  end
22
22
 
23
+ def test_indexables_have_expanded_full_paths
24
+ @config.apply_config({ "included_patterns" => ["**/*.rb"] })
25
+ indexables = @config.indexables
26
+
27
+ # All paths should be expanded
28
+ assert(indexables.none? { |indexable| indexable.full_path.start_with?("lib/") })
29
+ end
30
+
23
31
  def test_indexables_only_includes_gem_require_paths
24
32
  indexables = @config.indexables
25
33
 
@@ -62,7 +62,7 @@ module RubyLsp
62
62
  # The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
63
63
  # else is pushed into the incoming queue
64
64
  case method
65
- when "initialize", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
65
+ when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
66
66
  process_message(message)
67
67
  when "shutdown"
68
68
  $stderr.puts("Shutting down Ruby LSP...")
@@ -29,8 +29,9 @@ module RubyLsp
29
29
  @encoding = T.let(Encoding::UTF_8, Encoding)
30
30
 
31
31
  @formatter = T.let("auto", String)
32
- @test_library = T.let(detect_test_library, String)
33
- @typechecker = T.let(detect_typechecker, T::Boolean)
32
+ @linters = T.let([], T::Array[String])
33
+ @test_library = T.let("minitest", String)
34
+ @typechecker = T.let(true, T::Boolean)
34
35
  @index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
35
36
  @supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
36
37
  @supports_watching_files = T.let(false, T::Boolean)
@@ -46,14 +47,25 @@ module RubyLsp
46
47
  @supported_formatters[@formatter]
47
48
  end
48
49
 
50
+ sig { returns(T::Array[Requests::Support::Formatter]) }
51
+ def active_linters
52
+ @linters.filter_map { |name| @supported_formatters[name] }
53
+ end
54
+
49
55
  sig { params(options: T::Hash[Symbol, T.untyped]).void }
50
56
  def apply_options(options)
57
+ dependencies = gather_dependencies
51
58
  workspace_uri = options.dig(:workspaceFolders, 0, :uri)
52
59
  @workspace_uri = URI(workspace_uri) if workspace_uri
53
60
 
54
61
  specified_formatter = options.dig(:initializationOptions, :formatter)
55
62
  @formatter = specified_formatter if specified_formatter
56
- @formatter = detect_formatter if @formatter == "auto"
63
+ @formatter = detect_formatter(dependencies) if @formatter == "auto"
64
+
65
+ specified_linters = options.dig(:initializationOptions, :linters)
66
+ @linters = specified_linters || detect_linters(dependencies)
67
+ @test_library = detect_test_library(dependencies)
68
+ @typechecker = detect_typechecker(dependencies)
57
69
 
58
70
  encodings = options.dig(:capabilities, :general, :positionEncodings)
59
71
  @encoding = if !encodings || encodings.empty?
@@ -89,28 +101,32 @@ module RubyLsp
89
101
  end
90
102
  end
91
103
 
92
- sig { params(gem_pattern: Regexp).returns(T::Boolean) }
93
- def direct_dependency?(gem_pattern)
94
- dependencies.any?(gem_pattern)
95
- end
96
-
97
104
  private
98
105
 
99
- sig { returns(String) }
100
- def detect_formatter
106
+ sig { params(dependencies: T::Array[String]).returns(String) }
107
+ def detect_formatter(dependencies)
101
108
  # NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
102
- if direct_dependency?(/^rubocop/)
109
+ if dependencies.any?(/^rubocop/)
103
110
  "rubocop"
104
- elsif direct_dependency?(/^syntax_tree$/)
111
+ elsif dependencies.any?(/^syntax_tree$/)
105
112
  "syntax_tree"
106
113
  else
107
114
  "none"
108
115
  end
109
116
  end
110
117
 
111
- sig { returns(String) }
112
- def detect_test_library
113
- if direct_dependency?(/^rspec/)
118
+ # Try to detect if there are linters in the project's dependencies. For auto-detection, we always only consider a
119
+ # single linter. To have multiple linters running, the user must configure them manually
120
+ sig { params(dependencies: T::Array[String]).returns(T::Array[String]) }
121
+ def detect_linters(dependencies)
122
+ linters = []
123
+ linters << "rubocop" if dependencies.any?(/^rubocop/)
124
+ linters
125
+ end
126
+
127
+ sig { params(dependencies: T::Array[String]).returns(String) }
128
+ def detect_test_library(dependencies)
129
+ if dependencies.any?(/^rspec/)
114
130
  "rspec"
115
131
  # A Rails app may have a dependency on minitest, but we would instead want to use the Rails test runner provided
116
132
  # by ruby-lsp-rails. A Rails app doesn't need to depend on the rails gem itself, individual components like
@@ -119,23 +135,23 @@ module RubyLsp
119
135
  elsif File.exist?(File.join(workspace_path, "bin/rails"))
120
136
  "rails"
121
137
  # NOTE: Intentionally ends with $ to avoid mis-matching minitest-reporters, etc. in a Rails app.
122
- elsif direct_dependency?(/^minitest$/)
138
+ elsif dependencies.any?(/^minitest$/)
123
139
  "minitest"
124
- elsif direct_dependency?(/^test-unit/)
140
+ elsif dependencies.any?(/^test-unit/)
125
141
  "test-unit"
126
142
  else
127
143
  "unknown"
128
144
  end
129
145
  end
130
146
 
131
- sig { returns(T::Boolean) }
132
- def detect_typechecker
147
+ sig { params(dependencies: T::Array[String]).returns(T::Boolean) }
148
+ def detect_typechecker(dependencies)
133
149
  return false if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
134
150
 
135
151
  # We can't read the env from within `Bundle.with_original_env` so we need to set it here.
136
152
  ruby_lsp_env_is_test = (ENV["RUBY_LSP_ENV"] == "test")
137
153
  Bundler.with_original_env do
138
- sorbet_static_detected = Bundler.locked_gems.specs.any? { |spec| spec.name == "sorbet-static" }
154
+ sorbet_static_detected = dependencies.any?(/^sorbet-static/)
139
155
  # Don't show message while running tests, since it's noisy
140
156
  if sorbet_static_detected && !ruby_lsp_env_is_test
141
157
  $stderr.puts("Ruby LSP detected this is a Sorbet project so will defer to Sorbet LSP for some functionality")
@@ -147,16 +163,11 @@ module RubyLsp
147
163
  end
148
164
 
149
165
  sig { returns(T::Array[String]) }
150
- def dependencies
151
- @dependencies ||= T.let(
152
- begin
153
- Bundler.with_original_env { Bundler.default_gemfile }
154
- Bundler.locked_gems.dependencies.keys + gemspec_dependencies
155
- rescue Bundler::GemfileNotFound
156
- []
157
- end,
158
- T.nilable(T::Array[String]),
159
- )
166
+ def gather_dependencies
167
+ Bundler.with_original_env { Bundler.default_gemfile }
168
+ Bundler.locked_gems.dependencies.keys + gemspec_dependencies
169
+ rescue Bundler::GemfileNotFound
170
+ []
160
171
  end
161
172
 
162
173
  sig { returns(T::Array[String]) }
@@ -7,6 +7,8 @@ module RubyLsp
7
7
  extend T::Sig
8
8
  include Requests::Support::Common
9
9
 
10
+ MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER = 10
11
+
10
12
  sig do
11
13
  params(
12
14
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
@@ -64,12 +66,17 @@ module RubyLsp
64
66
 
65
67
  sig { params(node: Prism::CallNode).void }
66
68
  def handle_method_definition(node)
67
- return unless self_receiver?(node)
68
-
69
69
  message = node.message
70
70
  return unless message
71
71
 
72
- methods = @index.resolve_method(message, @nesting.join("::"))
72
+ methods = if self_receiver?(node)
73
+ @index.resolve_method(message, @nesting.join("::"))
74
+ else
75
+ # If the method doesn't have a receiver, then we provide a few candidates to jump to
76
+ # But we don't want to provide too many candidates, as it can be overwhelming
77
+ @index[message]&.take(MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER)
78
+ end
79
+
73
80
  return unless methods
74
81
 
75
82
  methods.each do |target_method|
@@ -43,6 +43,7 @@ module RubyLsp
43
43
  ResponseBuilders::CollectionResponseBuilder[Interface::Location].new,
44
44
  ResponseBuilders::CollectionResponseBuilder[Interface::Location],
45
45
  )
46
+ @dispatcher = dispatcher
46
47
 
47
48
  target, parent, nesting = document.locate_node(
48
49
  position,
@@ -50,33 +51,41 @@ module RubyLsp
50
51
  )
51
52
 
52
53
  if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
54
+ # If the target is part of a constant path node, we need to find the exact portion of the constant that the
55
+ # user is requesting to go to definition for
53
56
  target = determine_target(
54
57
  target,
55
58
  parent,
56
59
  position,
57
60
  )
61
+ elsif target.is_a?(Prism::CallNode) && target.name != :require && target.name != :require_relative &&
62
+ !covers_position?(target.message_loc, position)
63
+ # If the target is a method call, we need to ensure that the requested position is exactly on top of the
64
+ # method identifier. Otherwise, we risk showing definitions for unrelated things
65
+ target = nil
58
66
  end
59
67
 
60
- Listeners::Definition.new(
61
- @response_builder,
62
- global_state,
63
- document.uri,
64
- nesting,
65
- dispatcher,
66
- typechecker_enabled,
67
- )
68
+ if target
69
+ Listeners::Definition.new(
70
+ @response_builder,
71
+ global_state,
72
+ document.uri,
73
+ nesting,
74
+ dispatcher,
75
+ typechecker_enabled,
76
+ )
68
77
 
69
- Addon.addons.each do |addon|
70
- addon.create_definition_listener(@response_builder, document.uri, nesting, dispatcher)
78
+ Addon.addons.each do |addon|
79
+ addon.create_definition_listener(@response_builder, document.uri, nesting, dispatcher)
80
+ end
71
81
  end
72
82
 
73
83
  @target = T.let(target, T.nilable(Prism::Node))
74
- @dispatcher = dispatcher
75
84
  end
76
85
 
77
86
  sig { override.returns(T::Array[Interface::Location]) }
78
87
  def perform
79
- @dispatcher.dispatch_once(@target)
88
+ @dispatcher.dispatch_once(@target) if @target
80
89
  @response_builder.response
81
90
  end
82
91
  end
@@ -34,7 +34,7 @@ module RubyLsp
34
34
  sig { params(global_state: GlobalState, document: Document).void }
35
35
  def initialize(global_state, document)
36
36
  super()
37
- @active_formatter = T.let(global_state.active_formatter, T.nilable(Support::Formatter))
37
+ @active_linters = T.let(global_state.active_linters, T::Array[Support::Formatter])
38
38
  @document = document
39
39
  @uri = T.let(document.uri, URI::Generic)
40
40
  end
@@ -45,10 +45,13 @@ module RubyLsp
45
45
  diagnostics.concat(syntax_error_diagnostics, syntax_warning_diagnostics)
46
46
 
47
47
  # Running RuboCop is slow, so to avoid excessive runs we only do so if the file is syntactically valid
48
- return diagnostics if @document.syntax_error? || !@active_formatter
48
+ return diagnostics if @document.syntax_error? || @active_linters.empty?
49
+
50
+ @active_linters.each do |linter|
51
+ linter_diagnostics = linter.run_diagnostic(@uri, @document)
52
+ diagnostics.concat(linter_diagnostics) if linter_diagnostics
53
+ end
49
54
 
50
- formatter_diagnostics = @active_formatter.run_diagnostic(@uri, @document)
51
- diagnostics.concat(formatter_diagnostics) if formatter_diagnostics
52
55
  diagnostics
53
56
  end
54
57
 
@@ -41,25 +41,29 @@ module RubyLsp
41
41
  end
42
42
  def initialize(document, global_state, position, dispatcher, typechecker_enabled)
43
43
  super()
44
- @target = T.let(nil, T.nilable(Prism::Node))
45
- @target, parent, nesting = document.locate_node(
44
+ target, parent, nesting = document.locate_node(
46
45
  position,
47
46
  node_types: Listeners::Hover::ALLOWED_TARGETS,
48
47
  )
49
48
 
50
49
  if (Listeners::Hover::ALLOWED_TARGETS.include?(parent.class) &&
51
- !Listeners::Hover::ALLOWED_TARGETS.include?(@target.class)) ||
52
- (parent.is_a?(Prism::ConstantPathNode) && @target.is_a?(Prism::ConstantReadNode))
53
- @target = determine_target(
54
- T.must(@target),
50
+ !Listeners::Hover::ALLOWED_TARGETS.include?(target.class)) ||
51
+ (parent.is_a?(Prism::ConstantPathNode) && target.is_a?(Prism::ConstantReadNode))
52
+ target = determine_target(
53
+ T.must(target),
55
54
  T.must(parent),
56
55
  position,
57
56
  )
57
+ elsif target.is_a?(Prism::CallNode) && target.name != :require && target.name != :require_relative &&
58
+ !covers_position?(target.message_loc, position)
59
+
60
+ target = nil
58
61
  end
59
62
 
60
63
  # Don't need to instantiate any listeners if there's no target
61
- return unless @target
64
+ return unless target
62
65
 
66
+ @target = T.let(target, T.nilable(Prism::Node))
63
67
  uri = document.uri
64
68
  @response_builder = T.let(ResponseBuilders::Hover.new, ResponseBuilders::Hover)
65
69
  Listeners::Hover.new(@response_builder, global_state, uri, nesting, dispatcher, typechecker_enabled)
@@ -65,6 +65,20 @@ module RubyLsp
65
65
 
66
66
  target
67
67
  end
68
+
69
+ # Checks if a given location covers the position requested
70
+ sig { params(location: T.nilable(Prism::Location), position: T::Hash[Symbol, T.untyped]).returns(T::Boolean) }
71
+ def covers_position?(location, position)
72
+ return false unless location
73
+
74
+ start_line = location.start_line - 1
75
+ end_line = location.end_line - 1
76
+ line = position[:line]
77
+ character = position[:character]
78
+
79
+ (start_line < line || (start_line == line && location.start_column <= character)) &&
80
+ (end_line > line || (end_line == line && location.end_column >= character))
81
+ end
68
82
  end
69
83
  end
70
84
  end
@@ -40,10 +40,13 @@ module RubyLsp
40
40
  end
41
41
  end
42
42
 
43
- sig { params(dispatcher: Prism::Dispatcher, range: T.nilable(T::Range[Integer])).void }
44
- def initialize(dispatcher, range: nil)
43
+ sig { params(global_state: GlobalState, dispatcher: Prism::Dispatcher, range: T.nilable(T::Range[Integer])).void }
44
+ def initialize(global_state, dispatcher, range: nil)
45
45
  super()
46
- @response_builder = T.let(ResponseBuilders::SemanticHighlighting.new, ResponseBuilders::SemanticHighlighting)
46
+ @response_builder = T.let(
47
+ ResponseBuilders::SemanticHighlighting.new(global_state.encoding),
48
+ ResponseBuilders::SemanticHighlighting,
49
+ )
47
50
  Listeners::SemanticHighlighting.new(dispatcher, @response_builder, range: range)
48
51
 
49
52
  Addon.addons.each do |addon|
@@ -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
@@ -334,7 +334,7 @@ module RubyLsp
334
334
  document_link = Requests::DocumentLink.new(uri, document.comments, dispatcher)
335
335
  code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
336
336
 
337
- semantic_highlighting = Requests::SemanticHighlighting.new(dispatcher)
337
+ semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher)
338
338
  dispatcher.dispatch(document.tree)
339
339
 
340
340
  # Store all responses retrieve in this round of visits in the cache and then return the response for the request
@@ -366,7 +366,7 @@ module RubyLsp
366
366
  end_line = range.dig(:end, :line)
367
367
 
368
368
  dispatcher = Prism::Dispatcher.new
369
- request = Requests::SemanticHighlighting.new(dispatcher, range: start_line..end_line)
369
+ request = Requests::SemanticHighlighting.new(@global_state, dispatcher, range: start_line..end_line)
370
370
  dispatcher.visit(document.tree)
371
371
 
372
372
  response = request.perform
@@ -75,7 +75,7 @@ module RubyLsp
75
75
  class Request < Message
76
76
  extend T::Sig
77
77
 
78
- sig { params(id: Integer, method: String, params: Object).void }
78
+ sig { params(id: T.any(Integer, String), method: String, params: Object).void }
79
79
  def initialize(id:, method:, params:)
80
80
  @id = id
81
81
  super(method: method, params: params)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.4
4
+ version: 0.16.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-11 00:00:00.000000000 Z
11
+ date: 2024-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -30,20 +30,20 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.22.0
33
+ version: 0.23.0
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: '0.25'
36
+ version: '0.28'
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 0.22.0
43
+ version: 0.23.0
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.25'
46
+ version: '0.28'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: sorbet-runtime
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -83,6 +83,7 @@ files:
83
83
  - lib/ruby_indexer/lib/ruby_indexer/entry.rb
84
84
  - lib/ruby_indexer/lib/ruby_indexer/index.rb
85
85
  - lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb
86
+ - lib/ruby_indexer/lib/ruby_indexer/location.rb
86
87
  - lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb
87
88
  - lib/ruby_indexer/ruby_indexer.rb
88
89
  - lib/ruby_indexer/test/classes_and_modules_test.rb
@@ -177,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
178
  - !ruby/object:Gem::Version
178
179
  version: '0'
179
180
  requirements: []
180
- rubygems_version: 3.5.7
181
+ rubygems_version: 3.5.9
181
182
  signing_key:
182
183
  specification_version: 4
183
184
  summary: An opinionated language server for Ruby