ruby-lsp 0.8.0 → 0.8.1

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.
@@ -0,0 +1,35 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "test_helper"
5
+
6
+ module RubyIndexer
7
+ class ConfigurationTest < Minitest::Test
8
+ def setup
9
+ @config = Configuration.new
10
+ end
11
+
12
+ def test_load_configuration_executes_configure_block
13
+ @config.load_config
14
+ files_to_index = @config.files_to_index
15
+
16
+ assert(files_to_index.none? { |path| path.include?("test/fixtures") })
17
+ assert(files_to_index.none? { |path| path.include?("minitest-reporters") })
18
+ end
19
+
20
+ def test_paths_are_unique
21
+ @config.load_config
22
+ files_to_index = @config.files_to_index
23
+
24
+ assert_equal(files_to_index.uniq.length, files_to_index.length)
25
+ end
26
+
27
+ def test_configuration_raises_for_unknown_keys
28
+ Psych::Nodes::Document.any_instance.expects(:to_ruby).returns({ "unknown_config" => 123 })
29
+
30
+ assert_raises(ArgumentError) do
31
+ @config.load_config
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,108 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "test_case"
5
+
6
+ module RubyIndexer
7
+ class ConstantTest < TestCase
8
+ def test_constant_writes
9
+ index(<<~RUBY)
10
+ FOO = 1
11
+
12
+ class ::Bar
13
+ FOO = 2
14
+ end
15
+ RUBY
16
+
17
+ assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-6")
18
+ assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-8")
19
+ end
20
+
21
+ def test_constant_or_writes
22
+ index(<<~RUBY)
23
+ FOO ||= 1
24
+
25
+ class ::Bar
26
+ FOO ||= 2
27
+ end
28
+ RUBY
29
+
30
+ assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-8")
31
+ assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-10")
32
+ end
33
+
34
+ def test_constant_path_writes
35
+ index(<<~RUBY)
36
+ class A
37
+ FOO = 1
38
+ ::BAR = 1
39
+
40
+ module B
41
+ FOO = 1
42
+ end
43
+ end
44
+
45
+ A::BAZ = 1
46
+ RUBY
47
+
48
+ assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-8")
49
+ assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-10")
50
+ assert_entry("A::B::FOO", Index::Entry::Constant, "/fake/path/foo.rb:5-4:5-10")
51
+ assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:9-0:9-9")
52
+ end
53
+
54
+ def test_constant_path_or_writes
55
+ index(<<~RUBY)
56
+ class A
57
+ FOO ||= 1
58
+ ::BAR ||= 1
59
+ end
60
+
61
+ A::BAZ ||= 1
62
+ RUBY
63
+
64
+ assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-10")
65
+ assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-12")
66
+ assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:5-0:5-11")
67
+ end
68
+
69
+ def test_comments_for_constants
70
+ index(<<~RUBY)
71
+ # FOO comment
72
+ FOO = 1
73
+
74
+ class A
75
+ # A::FOO comment
76
+ FOO = 1
77
+
78
+ # ::BAR comment
79
+ ::BAR = 1
80
+ end
81
+
82
+ # A::BAZ comment
83
+ A::BAZ = 1
84
+ RUBY
85
+
86
+ foo_comment = @index["FOO"].first.comments.join("\n")
87
+ assert_equal("# FOO comment\n", foo_comment)
88
+
89
+ a_foo_comment = @index["A::FOO"].first.comments.join("\n")
90
+ assert_equal("# A::FOO comment\n", a_foo_comment)
91
+
92
+ bar_comment = @index["BAR"].first.comments.join("\n")
93
+ assert_equal("# ::BAR comment\n", bar_comment)
94
+
95
+ a_baz_comment = @index["A::BAZ"].first.comments.join("\n")
96
+ assert_equal("# A::BAZ comment\n", a_baz_comment)
97
+ end
98
+
99
+ def test_variable_path_constants_are_ignored
100
+ index(<<~RUBY)
101
+ var::FOO = 1
102
+ self.class::FOO = 1
103
+ RUBY
104
+
105
+ assert_no_entry
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,94 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "test_case"
5
+
6
+ module RubyIndexer
7
+ class IndexTest < TestCase
8
+ def test_deleting_one_entry_for_a_class
9
+ @index.index_single("/fake/path/foo.rb", <<~RUBY)
10
+ class Foo
11
+ end
12
+ RUBY
13
+ @index.index_single("/fake/path/other_foo.rb", <<~RUBY)
14
+ class Foo
15
+ end
16
+ RUBY
17
+
18
+ entries = @index["Foo"]
19
+ assert_equal(2, entries.length)
20
+
21
+ @index.delete("/fake/path/other_foo.rb")
22
+ entries = @index["Foo"]
23
+ assert_equal(1, entries.length)
24
+ end
25
+
26
+ def test_deleting_all_entries_for_a_class
27
+ @index.index_single("/fake/path/foo.rb", <<~RUBY)
28
+ class Foo
29
+ end
30
+ RUBY
31
+
32
+ entries = @index["Foo"]
33
+ assert_equal(1, entries.length)
34
+
35
+ @index.delete("/fake/path/foo.rb")
36
+ entries = @index["Foo"]
37
+ assert_nil(entries)
38
+ end
39
+
40
+ def test_index_resolve
41
+ @index.index_single("/fake/path/foo.rb", <<~RUBY)
42
+ class Bar; end
43
+
44
+ module Foo
45
+ class Bar
46
+ end
47
+
48
+ class Baz
49
+ class Something
50
+ end
51
+ end
52
+ end
53
+ RUBY
54
+
55
+ entries = @index.resolve("Something", ["Foo", "Baz"])
56
+ refute_empty(entries)
57
+ assert_equal("Foo::Baz::Something", entries.first.name)
58
+
59
+ entries = @index.resolve("Bar", ["Foo"])
60
+ refute_empty(entries)
61
+ assert_equal("Foo::Bar", entries.first.name)
62
+
63
+ entries = @index.resolve("Bar", ["Foo", "Baz"])
64
+ refute_empty(entries)
65
+ assert_equal("Foo::Bar", entries.first.name)
66
+
67
+ entries = @index.resolve("Foo::Bar", ["Foo", "Baz"])
68
+ refute_empty(entries)
69
+ assert_equal("Foo::Bar", entries.first.name)
70
+
71
+ assert_nil(@index.resolve("DoesNotExist", ["Foo"]))
72
+ end
73
+
74
+ def test_accessing_with_colon_colon_prefix
75
+ @index.index_single("/fake/path/foo.rb", <<~RUBY)
76
+ class Bar; end
77
+
78
+ module Foo
79
+ class Bar
80
+ end
81
+
82
+ class Baz
83
+ class Something
84
+ end
85
+ end
86
+ end
87
+ RUBY
88
+
89
+ entries = @index["::Foo::Baz::Something"]
90
+ refute_empty(entries)
91
+ assert_equal("Foo::Baz::Something", entries.first.name)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,42 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "test_helper"
5
+
6
+ module RubyIndexer
7
+ class TestCase < Minitest::Test
8
+ def setup
9
+ @index = Index.new
10
+ end
11
+
12
+ private
13
+
14
+ def index(source)
15
+ @index.index_single("/fake/path/foo.rb", source)
16
+ end
17
+
18
+ def assert_entry(expected_name, type, expected_location)
19
+ entries = @index[expected_name]
20
+ refute_empty(entries, "Expected #{expected_name} to be indexed")
21
+
22
+ entry = entries.first
23
+ assert_instance_of(type, entry, "Expected #{expected_name} to be a #{type}")
24
+
25
+ location = entry.location
26
+ location_string =
27
+ "#{entry.file_path}:#{location.start_line - 1}-#{location.start_column}" \
28
+ ":#{location.end_line - 1}-#{location.end_column}"
29
+
30
+ assert_equal(expected_location, location_string)
31
+ end
32
+
33
+ def refute_entry(expected_name)
34
+ entries = @index[expected_name]
35
+ assert_nil(entries, "Expected #{expected_name} to not be indexed")
36
+ end
37
+
38
+ def assert_no_entry
39
+ assert_empty(@index.instance_variable_get(:@entries), "Expected nothing to be indexed")
40
+ end
41
+ end
42
+ end
@@ -15,6 +15,7 @@ module RubyLsp
15
15
  @store = store
16
16
  @test_library = T.let(DependencyDetector.detected_test_library, String)
17
17
  @message_queue = message_queue
18
+ @index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
18
19
  end
19
20
 
20
21
  sig { params(request: T::Hash[Symbol, T.untyped]).returns(Result) }
@@ -57,6 +58,14 @@ module RubyLsp
57
58
  warn(errored_extensions.map(&:backtraces).join("\n\n"))
58
59
  end
59
60
 
61
+ if @store.experimental_features
62
+ # The begin progress invocation happens during `initialize`, so that the notification is sent before we are
63
+ # stuck indexing files
64
+ RubyIndexer.configuration.load_config
65
+ @index.index_all
66
+ end_progress("indexing-progress")
67
+ end
68
+
60
69
  check_formatter_is_available
61
70
 
62
71
  warn("Ruby LSP is ready")
@@ -169,25 +178,59 @@ module RubyLsp
169
178
  completion(uri, request.dig(:params, :position))
170
179
  when "textDocument/definition"
171
180
  definition(uri, request.dig(:params, :position))
181
+ when "workspace/didChangeWatchedFiles"
182
+ did_change_watched_files(request.dig(:params, :changes))
172
183
  when "rubyLsp/textDocument/showSyntaxTree"
173
184
  show_syntax_tree(uri, request.dig(:params, :range))
174
185
  end
175
186
  end
176
187
 
188
+ sig { params(changes: T::Array[{ uri: String, type: Integer }]).returns(Object) }
189
+ def did_change_watched_files(changes)
190
+ changes.each do |change|
191
+ # File change events include folders, but we're only interested in files
192
+ uri = URI(change[:uri])
193
+ file_path = uri.to_standardized_path
194
+ next if file_path.nil? || File.directory?(file_path)
195
+
196
+ case change[:type]
197
+ when Constant::FileChangeType::CREATED
198
+ @index.index_single(file_path)
199
+ when Constant::FileChangeType::CHANGED
200
+ @index.delete(file_path)
201
+ @index.index_single(file_path)
202
+ when Constant::FileChangeType::DELETED
203
+ @index.delete(file_path)
204
+ end
205
+ end
206
+
207
+ VOID
208
+ end
209
+
177
210
  sig { params(uri: URI::Generic, range: T.nilable(Document::RangeShape)).returns({ ast: String }) }
178
211
  def show_syntax_tree(uri, range)
179
212
  { ast: Requests::ShowSyntaxTree.new(@store.get(uri), range).run }
180
213
  end
181
214
 
182
- sig { params(uri: URI::Generic, position: Document::PositionShape).returns(T.nilable(Interface::Location)) }
215
+ sig do
216
+ params(
217
+ uri: URI::Generic,
218
+ position: Document::PositionShape,
219
+ ).returns(T.nilable(T.any(T::Array[Interface::Location], Interface::Location)))
220
+ end
183
221
  def definition(uri, position)
184
222
  document = @store.get(uri)
185
223
  return if document.syntax_error?
186
224
 
187
- target, _parent = document.locate_node(position, node_types: [SyntaxTree::Command])
225
+ target, parent, nesting = document.locate_node(
226
+ position,
227
+ node_types: [SyntaxTree::Command, SyntaxTree::Const, SyntaxTree::ConstPathRef],
228
+ )
229
+
230
+ target = parent if target.is_a?(SyntaxTree::Const) && parent.is_a?(SyntaxTree::ConstPathRef)
188
231
 
189
232
  emitter = EventEmitter.new
190
- base_listener = Requests::Definition.new(uri, emitter, @message_queue)
233
+ base_listener = Requests::Definition.new(uri, nesting, @index, emitter, @message_queue)
191
234
  emitter.emit_for_target(target)
192
235
  base_listener.response
193
236
  end
@@ -436,6 +479,37 @@ module RubyLsp
436
479
  listener.response
437
480
  end
438
481
 
482
+ sig { params(id: String, title: String).void }
483
+ def begin_progress(id, title)
484
+ return unless @store.supports_progress
485
+
486
+ @message_queue << Request.new(
487
+ message: "window/workDoneProgress/create",
488
+ params: Interface::WorkDoneProgressCreateParams.new(token: id),
489
+ )
490
+
491
+ @message_queue << Notification.new(
492
+ message: "$/progress",
493
+ params: Interface::ProgressParams.new(
494
+ token: id,
495
+ value: Interface::WorkDoneProgressBegin.new(kind: "begin", title: title),
496
+ ),
497
+ )
498
+ end
499
+
500
+ sig { params(id: String).void }
501
+ def end_progress(id)
502
+ return unless @store.supports_progress
503
+
504
+ @message_queue << Notification.new(
505
+ message: "$/progress",
506
+ params: Interface::ProgressParams.new(
507
+ token: id,
508
+ value: Interface::WorkDoneProgressEnd.new(kind: "end"),
509
+ ),
510
+ )
511
+ end
512
+
439
513
  sig { params(options: T::Hash[Symbol, T.untyped]).returns(Interface::InitializeResult) }
440
514
  def initialize_request(options)
441
515
  @store.clear
@@ -449,6 +523,7 @@ module RubyLsp
449
523
  encodings.first
450
524
  end
451
525
 
526
+ @store.supports_progress = options.dig(:capabilities, :window, :workDoneProgress) || true
452
527
  formatter = options.dig(:initializationOptions, :formatter) || "auto"
453
528
  @store.formatter = if formatter == "auto"
454
529
  DependencyDetector.detected_formatter
@@ -457,9 +532,7 @@ module RubyLsp
457
532
  end
458
533
 
459
534
  configured_features = options.dig(:initializationOptions, :enabledFeatures)
460
-
461
- # Uncomment the line below and use the variable to gate features behind the experimental flag
462
- # experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled)
535
+ @store.experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
463
536
 
464
537
  enabled_features = case configured_features
465
538
  when Array
@@ -541,6 +614,37 @@ module RubyLsp
541
614
  )
542
615
  end
543
616
 
617
+ if @store.experimental_features
618
+ # Dynamically registered capabilities
619
+ file_watching_caps = options.dig(:capabilities, :workspace, :didChangeWatchedFiles)
620
+
621
+ # Not every client supports dynamic registration or file watching
622
+ if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
623
+ @message_queue << Request.new(
624
+ message: "client/registerCapability",
625
+ params: Interface::RegistrationParams.new(
626
+ registrations: [
627
+ # Register watching Ruby files
628
+ Interface::Registration.new(
629
+ id: "workspace/didChangeWatchedFiles",
630
+ method: "workspace/didChangeWatchedFiles",
631
+ register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new(
632
+ watchers: [
633
+ Interface::FileSystemWatcher.new(
634
+ glob_pattern: "**/*.rb",
635
+ kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE,
636
+ ),
637
+ ],
638
+ ),
639
+ ),
640
+ ],
641
+ ),
642
+ )
643
+ end
644
+
645
+ begin_progress("indexing-progress", "Ruby LSP: indexing files")
646
+ end
647
+
544
648
  Interface::InitializeResult.new(
545
649
  capabilities: Interface::ServerCapabilities.new(
546
650
  text_document_sync: Interface::TextDocumentSyncOptions.new(
@@ -3,6 +3,7 @@
3
3
 
4
4
  require "sorbet-runtime"
5
5
  require "syntax_tree"
6
+ require "yarp"
6
7
  require "language_server-protocol"
7
8
  require "benchmark"
8
9
  require "bundler"
@@ -10,6 +11,7 @@ require "uri"
10
11
  require "cgi"
11
12
 
12
13
  require "ruby-lsp"
14
+ require "ruby_indexer/ruby_indexer"
13
15
  require "core_ext/uri"
14
16
  require "ruby_lsp/utils"
15
17
  require "ruby_lsp/server"
@@ -20,18 +20,41 @@ module RubyLsp
20
20
  extend T::Sig
21
21
  extend T::Generic
22
22
 
23
- ResponseType = type_member { { fixed: T.nilable(Interface::Location) } }
23
+ ResponseType = type_member { { fixed: T.nilable(T.any(T::Array[Interface::Location], Interface::Location)) } }
24
24
 
25
25
  sig { override.returns(ResponseType) }
26
26
  attr_reader :response
27
27
 
28
- sig { params(uri: URI::Generic, emitter: EventEmitter, message_queue: Thread::Queue).void }
29
- def initialize(uri, emitter, message_queue)
28
+ HAS_TYPECHECKER = T.let(DependencyDetector.typechecker?, T::Boolean)
29
+
30
+ sig do
31
+ params(
32
+ uri: URI::Generic,
33
+ nesting: T::Array[String],
34
+ index: RubyIndexer::Index,
35
+ emitter: EventEmitter,
36
+ message_queue: Thread::Queue,
37
+ ).void
38
+ end
39
+ def initialize(uri, nesting, index, emitter, message_queue)
30
40
  super(emitter, message_queue)
31
41
 
32
42
  @uri = uri
43
+ @nesting = nesting
44
+ @index = index
33
45
  @response = T.let(nil, ResponseType)
34
- emitter.register(self, :on_command)
46
+ emitter.register(self, :on_command, :on_const, :on_const_path_ref)
47
+ end
48
+
49
+ sig { params(node: SyntaxTree::ConstPathRef).void }
50
+ def on_const_path_ref(node)
51
+ name = full_constant_name(node)
52
+ find_in_index(name)
53
+ end
54
+
55
+ sig { params(node: SyntaxTree::Const).void }
56
+ def on_const(node)
57
+ find_in_index(node.value)
35
58
  end
36
59
 
37
60
  sig { params(node: SyntaxTree::Command).void }
@@ -79,6 +102,30 @@ module RubyLsp
79
102
 
80
103
  private
81
104
 
105
+ sig { params(value: String).void }
106
+ def find_in_index(value)
107
+ entries = @index.resolve(value, @nesting)
108
+ return unless entries
109
+
110
+ workspace_path = T.must(WORKSPACE_URI.to_standardized_path)
111
+
112
+ @response = entries.filter_map do |entry|
113
+ location = entry.location
114
+ # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
115
+ # additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
116
+ # in the project, even if the files are typed false
117
+ next if HAS_TYPECHECKER && entry.file_path.start_with?(workspace_path)
118
+
119
+ Interface::Location.new(
120
+ uri: URI::Generic.from_path(path: entry.file_path).to_s,
121
+ range: Interface::Range.new(
122
+ start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
123
+ end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
124
+ ),
125
+ )
126
+ end
127
+ end
128
+
82
129
  sig { params(file: String).returns(T.nilable(String)) }
83
130
  def find_file_in_load_path(file)
84
131
  return unless file.include?("/")
@@ -67,12 +67,10 @@ module RubyLsp
67
67
 
68
68
  line = T.must(current_line)
69
69
 
70
- # If the current character is a pipe and both previous ones are pipes too, then we autocompleted a pipe and the
71
- # user inserted a third one. In this case, we need to avoid adding a fourth and remove the previous one
72
- if line[@position[:character] - 2] == "|" &&
73
- line[@position[:character] - 1] == "|" &&
74
- line[@position[:character]] == "|"
75
-
70
+ # If the user inserts the closing pipe manually to the end of the block argument, we need to avoid adding
71
+ # an additional one and remove the previous one. This also helps to remove the user who accidentally
72
+ # inserts another pipe after the autocompleted one.
73
+ if line[..@position[:character]] =~ /(do|{)\s+\|[^|]*\|\|$/
76
74
  @edits << Interface::TextEdit.new(
77
75
  range: Interface::Range.new(
78
76
  start: Interface::Position.new(
@@ -36,6 +36,11 @@ module RubyLsp
36
36
  end
37
37
  end
38
38
 
39
+ sig { returns(T::Boolean) }
40
+ def typechecker?
41
+ direct_dependency?(/^sorbet/) || direct_dependency?(/^sorbet-static-and-runtime/)
42
+ end
43
+
39
44
  sig { params(gem_pattern: Regexp).returns(T::Boolean) }
40
45
  def direct_dependency?(gem_pattern)
41
46
  Bundler.with_original_env { Bundler.default_gemfile } &&
@@ -17,9 +17,10 @@ module RubyLsp
17
17
 
18
18
  class BundleNotLocked < StandardError; end
19
19
 
20
- sig { params(project_path: String).void }
21
- def initialize(project_path)
20
+ sig { params(project_path: String, branch: T.nilable(String)).void }
21
+ def initialize(project_path, branch: nil)
22
22
  @project_path = project_path
23
+ @branch = branch
23
24
 
24
25
  # Custom bundle paths
25
26
  @custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(Dir.pwd), Pathname)
@@ -119,7 +120,9 @@ module RubyLsp
119
120
  end
120
121
 
121
122
  unless @dependencies["ruby-lsp"]
122
- parts << 'gem "ruby-lsp", require: false, group: :development'
123
+ ruby_lsp_entry = +'gem "ruby-lsp", require: false, group: :development'
124
+ ruby_lsp_entry << ", github: \"Shopify/ruby-lsp\", branch: \"#{@branch}\"" if @branch
125
+ parts << ruby_lsp_entry
123
126
  end
124
127
 
125
128
  unless @dependencies["debug"]
@@ -156,11 +159,12 @@ module RubyLsp
156
159
  # `.ruby-lsp` folder, which is not the user's intention. For example, if the path is configured as `vendor`, we
157
160
  # want to install it in the top level `vendor` and not `.ruby-lsp/vendor`
158
161
  path = Bundler.settings["path"]
162
+ expanded_path = File.expand_path(path, Dir.pwd) if path
159
163
 
160
164
  # Use the absolute `BUNDLE_PATH` to prevent accidentally creating unwanted folders under `.ruby-lsp`
161
165
  env = {}
162
166
  env["BUNDLE_GEMFILE"] = bundle_gemfile.to_s
163
- env["BUNDLE_PATH"] = File.expand_path(path, Dir.pwd) if path
167
+ env["BUNDLE_PATH"] = expanded_path if expanded_path
164
168
 
165
169
  # If both `ruby-lsp` and `debug` are already in the Gemfile, then we shouldn't try to upgrade them or else we'll
166
170
  # produce undesired source control changes. If the custom bundle was just created and either `ruby-lsp` or `debug`
@@ -187,7 +191,7 @@ module RubyLsp
187
191
  # Add bundle update
188
192
  warn("Ruby LSP> Running bundle install for the custom bundle. This may take a while...")
189
193
  system(env, command)
190
- [bundle_gemfile.to_s, path]
194
+ [bundle_gemfile.to_s, expanded_path]
191
195
  end
192
196
  end
193
197
  end
@@ -13,34 +13,40 @@ module RubyLsp
13
13
  sig { returns(String) }
14
14
  attr_accessor :formatter
15
15
 
16
+ sig { returns(T::Boolean) }
17
+ attr_accessor :supports_progress
18
+
19
+ sig { returns(T::Boolean) }
20
+ attr_accessor :experimental_features
21
+
16
22
  sig { void }
17
23
  def initialize
18
24
  @state = T.let({}, T::Hash[String, Document])
19
25
  @encoding = T.let(Constant::PositionEncodingKind::UTF8, String)
20
26
  @formatter = T.let("auto", String)
27
+ @supports_progress = T.let(true, T::Boolean)
28
+ @experimental_features = T.let(false, T::Boolean)
21
29
  end
22
30
 
23
31
  sig { params(uri: URI::Generic).returns(Document) }
24
32
  def get(uri)
25
- path = uri.to_standardized_path
26
- return T.must(@state[T.must(uri.opaque)]) unless path
27
-
28
- document = @state[path]
33
+ document = @state[uri.to_s]
29
34
  return document unless document.nil?
30
35
 
31
- set(uri: uri, source: File.binread(CGI.unescape(path)), version: 0)
32
- T.must(@state[path])
36
+ path = T.must(uri.to_standardized_path)
37
+ set(uri: uri, source: File.binread(path), version: 0)
38
+ T.must(@state[uri.to_s])
33
39
  end
34
40
 
35
41
  sig { params(uri: URI::Generic, source: String, version: Integer).void }
36
42
  def set(uri:, source:, version:)
37
43
  document = Document.new(source: source, version: version, uri: uri, encoding: @encoding)
38
- @state[uri.storage_key] = document
44
+ @state[uri.to_s] = document
39
45
  end
40
46
 
41
47
  sig { params(uri: URI::Generic, edits: T::Array[Document::EditShape], version: Integer).void }
42
48
  def push_edits(uri:, edits:, version:)
43
- T.must(@state[uri.storage_key]).push_edits(edits, version: version)
49
+ T.must(@state[uri.to_s]).push_edits(edits, version: version)
44
50
  end
45
51
 
46
52
  sig { void }
@@ -55,7 +61,7 @@ module RubyLsp
55
61
 
56
62
  sig { params(uri: URI::Generic).void }
57
63
  def delete(uri)
58
- @state.delete(uri.storage_key)
64
+ @state.delete(uri.to_s)
59
65
  end
60
66
 
61
67
  sig do