ruby-lsp 0.7.6 → 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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +41 -33
  4. data/exe/ruby-lsp-check +2 -2
  5. data/lib/core_ext/uri.rb +40 -0
  6. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +91 -0
  7. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +122 -0
  8. data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +121 -0
  9. data/lib/ruby_indexer/ruby_indexer.rb +19 -0
  10. data/lib/ruby_indexer/test/classes_and_modules_test.rb +204 -0
  11. data/lib/ruby_indexer/test/configuration_test.rb +35 -0
  12. data/lib/ruby_indexer/test/constant_test.rb +108 -0
  13. data/lib/ruby_indexer/test/index_test.rb +94 -0
  14. data/lib/ruby_indexer/test/test_case.rb +42 -0
  15. data/lib/ruby_lsp/document.rb +3 -3
  16. data/lib/ruby_lsp/executor.rb +131 -24
  17. data/lib/ruby_lsp/extension.rb +24 -0
  18. data/lib/ruby_lsp/internal.rb +4 -0
  19. data/lib/ruby_lsp/listener.rb +15 -14
  20. data/lib/ruby_lsp/requests/code_actions.rb +3 -3
  21. data/lib/ruby_lsp/requests/code_lens.rb +10 -24
  22. data/lib/ruby_lsp/requests/definition.rb +55 -8
  23. data/lib/ruby_lsp/requests/diagnostics.rb +3 -2
  24. data/lib/ruby_lsp/requests/document_link.rb +4 -3
  25. data/lib/ruby_lsp/requests/formatting.rb +3 -2
  26. data/lib/ruby_lsp/requests/hover.rb +4 -18
  27. data/lib/ruby_lsp/requests/on_type_formatting.rb +4 -6
  28. data/lib/ruby_lsp/requests/support/dependency_detector.rb +5 -0
  29. data/lib/ruby_lsp/requests/support/formatter_runner.rb +1 -1
  30. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +2 -2
  31. data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +2 -3
  32. data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +2 -3
  33. data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +3 -2
  34. data/lib/ruby_lsp/server.rb +10 -2
  35. data/lib/ruby_lsp/setup_bundler.rb +28 -14
  36. data/lib/ruby_lsp/store.rb +20 -13
  37. data/lib/ruby_lsp/utils.rb +1 -1
  38. metadata +27 -3
@@ -58,7 +58,7 @@ module RubyLsp
58
58
  def initialize(document, formatter: "auto")
59
59
  super(document)
60
60
 
61
- @uri = T.let(document.uri, String)
61
+ @uri = T.let(document.uri, URI::Generic)
62
62
  @formatter = formatter
63
63
  end
64
64
 
@@ -67,7 +67,8 @@ module RubyLsp
67
67
  return if @formatter == "none"
68
68
 
69
69
  # Don't try to format files outside the current working directory
70
- return unless @uri.sub("file://", "").start_with?(Dir.pwd)
70
+ path = @uri.to_standardized_path
71
+ return unless path.nil? || path.start_with?(T.must(WORKSPACE_URI.to_standardized_path))
71
72
 
72
73
  return if @document.syntax_error?
73
74
 
@@ -39,29 +39,15 @@ module RubyLsp
39
39
  def initialize(emitter, message_queue)
40
40
  super
41
41
 
42
- @external_listeners = T.let([], T::Array[RubyLsp::Listener[ResponseType]])
42
+ @external_listeners.concat(
43
+ Extension.extensions.filter_map { |ext| ext.create_hover_listener(emitter, message_queue) },
44
+ )
43
45
  @response = T.let(nil, ResponseType)
44
46
  emitter.register(self, :on_command, :on_const_path_ref, :on_call)
45
-
46
- register_external_listeners!
47
- end
48
-
49
- sig { void }
50
- def register_external_listeners!
51
- self.class.listeners.each do |l|
52
- @external_listeners << T.unsafe(l).new(@emitter, @message_queue)
53
- end
54
- end
55
-
56
- sig { void }
57
- def merge_external_listeners_responses!
58
- @external_listeners.each do |l|
59
- merge_response!(l)
60
- end
61
47
  end
62
48
 
63
49
  # Merges responses from other hover listeners
64
- sig { params(other: Listener[ResponseType]).returns(T.self_type) }
50
+ sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
65
51
  def merge_response!(other)
66
52
  other_response = other.response
67
53
  return self unless other_response
@@ -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 } &&
@@ -10,7 +10,7 @@ module RubyLsp
10
10
 
11
11
  interface!
12
12
 
13
- sig { abstract.params(uri: String, document: Document).returns(T.nilable(String)) }
13
+ sig { abstract.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
14
14
  def run(uri, document); end
15
15
  end
16
16
  end
@@ -19,7 +19,7 @@ module RubyLsp
19
19
  T::Hash[Symbol, Integer],
20
20
  )
21
21
 
22
- sig { params(offense: RuboCop::Cop::Offense, uri: String).void }
22
+ sig { params(offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
23
23
  def initialize(offense, uri)
24
24
  @offense = offense
25
25
  @uri = uri
@@ -34,7 +34,7 @@ module RubyLsp
34
34
  document_changes: [
35
35
  Interface::TextDocumentEdit.new(
36
36
  text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
37
- uri: @uri,
37
+ uri: @uri.to_s,
38
38
  version: nil,
39
39
  ),
40
40
  edits: @offense.correctable? ? offense_replacements : [],
@@ -3,7 +3,6 @@
3
3
 
4
4
  return unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
5
5
 
6
- require "cgi"
7
6
  require "singleton"
8
7
 
9
8
  module RubyLsp
@@ -19,9 +18,9 @@ module RubyLsp
19
18
  @runner = T.let(RuboCopRunner.new, RuboCopRunner)
20
19
  end
21
20
 
22
- sig { params(uri: String, document: Document).returns(T::Array[Support::RuboCopDiagnostic]) }
21
+ sig { params(uri: URI::Generic, document: Document).returns(T::Array[Support::RuboCopDiagnostic]) }
23
22
  def run(uri, document)
24
- filename = CGI.unescape(URI.parse(uri).path)
23
+ filename = T.must(uri.to_standardized_path || uri.opaque)
25
24
  # Invoke RuboCop with just this file in `paths`
26
25
  @runner.run(filename, document.source)
27
26
 
@@ -3,7 +3,6 @@
3
3
 
4
4
  return unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
5
5
 
6
- require "cgi"
7
6
  require "singleton"
8
7
 
9
8
  module RubyLsp
@@ -21,9 +20,9 @@ module RubyLsp
21
20
  @runner = T.let(RuboCopRunner.new("-a"), RuboCopRunner)
22
21
  end
23
22
 
24
- sig { override.params(uri: String, document: Document).returns(String) }
23
+ sig { override.params(uri: URI::Generic, document: Document).returns(String) }
25
24
  def run(uri, document)
26
- filename = CGI.unescape(URI.parse(uri).path)
25
+ filename = T.must(uri.to_standardized_path || uri.opaque)
27
26
 
28
27
  # Invoke RuboCop with just this file in `paths`
29
28
  @runner.run(filename, document.source)
@@ -26,9 +26,10 @@ module RubyLsp
26
26
  )
27
27
  end
28
28
 
29
- sig { override.params(uri: String, document: Document).returns(T.nilable(String)) }
29
+ sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
30
30
  def run(uri, document)
31
- relative_path = Pathname.new(URI(uri).path).relative_path_from(T.must(WORKSPACE_URI.path))
31
+ relative_path = Pathname.new(T.must(uri.to_standardized_path || uri.opaque))
32
+ .relative_path_from(T.must(WORKSPACE_URI.to_standardized_path))
32
33
  return if @options.ignore_files.any? { |pattern| File.fnmatch(pattern, relative_path) }
33
34
 
34
35
  SyntaxTree.format(
@@ -86,6 +86,7 @@ module RubyLsp
86
86
  @message_dispatcher.join
87
87
  @store.clear
88
88
 
89
+ Extension.extensions.each(&:deactivate)
89
90
  finalize_request(Result.new(response: nil), request)
90
91
  when "exit"
91
92
  # We return zero if shutdown has already been received or one otherwise as per the recommendation in the spec
@@ -105,7 +106,7 @@ module RubyLsp
105
106
  # source. Altering the source reference during parsing will put the parser in an invalid internal state,
106
107
  # since it started parsing with one source but then it changed in the middle
107
108
  uri = request.dig(:params, :textDocument, :uri)
108
- @store.get(uri).parse if uri
109
+ @store.get(URI(uri)).parse if uri
109
110
  end
110
111
 
111
112
  @job_queue << job
@@ -192,7 +193,14 @@ module RubyLsp
192
193
  params[:backtrace] = backtrace.map { |bt| bt.sub(/^#{Dir.home}/, "~") }.join("\n") if backtrace
193
194
  end
194
195
 
195
- params[:uri] = uri.sub(%r{.*://#{Dir.home}}, "~") if uri
196
+ if uri
197
+ home = URI::Generic.from_path(path: Dir.home)
198
+
199
+ parsed_uri = URI(uri)
200
+ path = parsed_uri.path
201
+ params[:uri] = path ? path.sub(T.must(home.path), "~") : parsed_uri.opaque
202
+ end
203
+
196
204
  params
197
205
  end
198
206
  end
@@ -5,6 +5,7 @@ require "sorbet-runtime"
5
5
  require "bundler"
6
6
  require "fileutils"
7
7
  require "pathname"
8
+ require "digest"
8
9
 
9
10
  # This file is a script that will configure a custom bundle for the Ruby LSP. The custom bundle allows developers to use
10
11
  # the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to the
@@ -16,14 +17,16 @@ module RubyLsp
16
17
 
17
18
  class BundleNotLocked < StandardError; end
18
19
 
19
- sig { params(project_path: String).void }
20
- def initialize(project_path)
20
+ sig { params(project_path: String, branch: T.nilable(String)).void }
21
+ def initialize(project_path, branch: nil)
21
22
  @project_path = project_path
23
+ @branch = branch
22
24
 
23
25
  # Custom bundle paths
24
26
  @custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(Dir.pwd), Pathname)
25
27
  @custom_gemfile = T.let(@custom_dir + "Gemfile", Pathname)
26
28
  @custom_lockfile = T.let(@custom_dir + "Gemfile.lock", Pathname)
29
+ @lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
27
30
 
28
31
  # Regular bundle paths
29
32
  @gemfile = T.let(
@@ -37,7 +40,6 @@ module RubyLsp
37
40
  @lockfile = T.let(@gemfile ? Bundler.default_lockfile : nil, T.nilable(Pathname))
38
41
 
39
42
  @dependencies = T.let(load_dependencies, T::Hash[String, T.untyped])
40
- @custom_bundle_dependencies = T.let(custom_bundle_dependencies, T::Hash[String, T.untyped])
41
43
  end
42
44
 
43
45
  # Setups up the custom bundle and returns the `BUNDLE_GEMFILE` and `BUNDLE_PATH` that should be used for running the
@@ -69,14 +71,16 @@ module RubyLsp
69
71
  return run_bundle_install(@custom_gemfile)
70
72
  end
71
73
 
72
- # If .ruby-lsp/Gemfile.lock already exists and the top level Gemfile.lock hasn't been modified since it was last
73
- # updated, then we're ready to boot the server
74
- if @custom_lockfile.exist? && @custom_lockfile.stat.mtime > @lockfile.stat.mtime
74
+ lockfile_contents = @lockfile.read
75
+ current_lockfile_hash = Digest::SHA256.hexdigest(lockfile_contents)
76
+
77
+ if @custom_lockfile.exist? && @lockfile_hash_path.exist? && @lockfile_hash_path.read == current_lockfile_hash
75
78
  warn("Ruby LSP> Skipping custom bundle setup since #{@custom_lockfile} already exists and is up to date")
76
79
  return run_bundle_install(@custom_gemfile)
77
80
  end
78
81
 
79
82
  FileUtils.cp(@lockfile.to_s, @custom_lockfile.to_s)
83
+ @lockfile_hash_path.write(current_lockfile_hash)
80
84
  run_bundle_install(@custom_gemfile)
81
85
  end
82
86
 
@@ -84,10 +88,17 @@ module RubyLsp
84
88
 
85
89
  sig { returns(T::Hash[String, T.untyped]) }
86
90
  def custom_bundle_dependencies
87
- return {} unless @custom_lockfile.exist?
88
-
89
- ENV["BUNDLE_GEMFILE"] = @custom_gemfile.to_s
90
- Bundler::LockfileParser.new(@custom_lockfile.read).dependencies
91
+ @custom_bundle_dependencies ||= T.let(
92
+ begin
93
+ if @custom_lockfile.exist?
94
+ ENV["BUNDLE_GEMFILE"] = @custom_gemfile.to_s
95
+ Bundler::LockfileParser.new(@custom_lockfile.read).dependencies
96
+ else
97
+ {}
98
+ end
99
+ end,
100
+ T.nilable(T::Hash[String, T.untyped]),
101
+ )
91
102
  ensure
92
103
  ENV.delete("BUNDLE_GEMFILE")
93
104
  end
@@ -109,7 +120,9 @@ module RubyLsp
109
120
  end
110
121
 
111
122
  unless @dependencies["ruby-lsp"]
112
- 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
113
126
  end
114
127
 
115
128
  unless @dependencies["debug"]
@@ -146,11 +159,12 @@ module RubyLsp
146
159
  # `.ruby-lsp` folder, which is not the user's intention. For example, if the path is configured as `vendor`, we
147
160
  # want to install it in the top level `vendor` and not `.ruby-lsp/vendor`
148
161
  path = Bundler.settings["path"]
162
+ expanded_path = File.expand_path(path, Dir.pwd) if path
149
163
 
150
164
  # Use the absolute `BUNDLE_PATH` to prevent accidentally creating unwanted folders under `.ruby-lsp`
151
165
  env = {}
152
166
  env["BUNDLE_GEMFILE"] = bundle_gemfile.to_s
153
- env["BUNDLE_PATH"] = File.expand_path(path, Dir.pwd) if path
167
+ env["BUNDLE_PATH"] = expanded_path if expanded_path
154
168
 
155
169
  # If both `ruby-lsp` and `debug` are already in the Gemfile, then we shouldn't try to upgrade them or else we'll
156
170
  # produce undesired source control changes. If the custom bundle was just created and either `ruby-lsp` or `debug`
@@ -160,7 +174,7 @@ module RubyLsp
160
174
  command = +""
161
175
 
162
176
  if (@dependencies["ruby-lsp"] && @dependencies["debug"]) ||
163
- @custom_bundle_dependencies["ruby-lsp"].nil? || @custom_bundle_dependencies["debug"].nil?
177
+ custom_bundle_dependencies["ruby-lsp"].nil? || custom_bundle_dependencies["debug"].nil?
164
178
  # Install gems using the custom bundle
165
179
  command << "bundle install "
166
180
  else
@@ -177,7 +191,7 @@ module RubyLsp
177
191
  # Add bundle update
178
192
  warn("Ruby LSP> Running bundle install for the custom bundle. This may take a while...")
179
193
  system(env, command)
180
- [bundle_gemfile.to_s, path]
194
+ [bundle_gemfile.to_s, expanded_path]
181
195
  end
182
196
  end
183
197
  end
@@ -1,8 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "cgi"
5
- require "uri"
6
4
  require "ruby_lsp/document"
7
5
 
8
6
  module RubyLsp
@@ -15,31 +13,40 @@ module RubyLsp
15
13
  sig { returns(String) }
16
14
  attr_accessor :formatter
17
15
 
16
+ sig { returns(T::Boolean) }
17
+ attr_accessor :supports_progress
18
+
19
+ sig { returns(T::Boolean) }
20
+ attr_accessor :experimental_features
21
+
18
22
  sig { void }
19
23
  def initialize
20
24
  @state = T.let({}, T::Hash[String, Document])
21
25
  @encoding = T.let(Constant::PositionEncodingKind::UTF8, String)
22
26
  @formatter = T.let("auto", String)
27
+ @supports_progress = T.let(true, T::Boolean)
28
+ @experimental_features = T.let(false, T::Boolean)
23
29
  end
24
30
 
25
- sig { params(uri: String).returns(Document) }
31
+ sig { params(uri: URI::Generic).returns(Document) }
26
32
  def get(uri)
27
- document = @state[uri]
33
+ document = @state[uri.to_s]
28
34
  return document unless document.nil?
29
35
 
30
- set(uri: uri, source: File.binread(CGI.unescape(URI.parse(uri).path)), version: 0)
31
- T.must(@state[uri])
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])
32
39
  end
33
40
 
34
- sig { params(uri: String, source: String, version: Integer).void }
41
+ sig { params(uri: URI::Generic, source: String, version: Integer).void }
35
42
  def set(uri:, source:, version:)
36
43
  document = Document.new(source: source, version: version, uri: uri, encoding: @encoding)
37
- @state[uri] = document
44
+ @state[uri.to_s] = document
38
45
  end
39
46
 
40
- sig { params(uri: String, edits: T::Array[Document::EditShape], version: Integer).void }
47
+ sig { params(uri: URI::Generic, edits: T::Array[Document::EditShape], version: Integer).void }
41
48
  def push_edits(uri:, edits:, version:)
42
- T.must(@state[uri]).push_edits(edits, version: version)
49
+ T.must(@state[uri.to_s]).push_edits(edits, version: version)
43
50
  end
44
51
 
45
52
  sig { void }
@@ -52,15 +59,15 @@ module RubyLsp
52
59
  @state.empty?
53
60
  end
54
61
 
55
- sig { params(uri: String).void }
62
+ sig { params(uri: URI::Generic).void }
56
63
  def delete(uri)
57
- @state.delete(uri)
64
+ @state.delete(uri.to_s)
58
65
  end
59
66
 
60
67
  sig do
61
68
  type_parameters(:T)
62
69
  .params(
63
- uri: String,
70
+ uri: URI::Generic,
64
71
  request_name: String,
65
72
  block: T.proc.params(document: Document).returns(T.type_parameter(:T)),
66
73
  ).returns(T.type_parameter(:T))
@@ -6,7 +6,7 @@ module RubyLsp
6
6
  VOID = T.let(Object.new.freeze, Object)
7
7
 
8
8
  # This freeze is not redundant since the interpolated string is mutable
9
- WORKSPACE_URI = T.let(URI("file://#{Dir.pwd}".freeze), URI::Generic)
9
+ WORKSPACE_URI = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
10
10
 
11
11
  # A notification to be sent to the client
12
12
  class Message
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.7.6
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-02 00:00:00.000000000 Z
11
+ date: 2023-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -58,6 +58,20 @@ dependencies:
58
58
  - - "<"
59
59
  - !ruby/object:Gem::Version
60
60
  version: '7'
61
+ - !ruby/object:Gem::Dependency
62
+ name: yarp
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.6.0
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 0.6.0
61
75
  description: An opinionated language server for Ruby
62
76
  email:
63
77
  - ruby@shopify.com
@@ -72,8 +86,18 @@ files:
72
86
  - VERSION
73
87
  - exe/ruby-lsp
74
88
  - exe/ruby-lsp-check
89
+ - lib/core_ext/uri.rb
75
90
  - lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
76
91
  - lib/ruby-lsp.rb
92
+ - lib/ruby_indexer/lib/ruby_indexer/configuration.rb
93
+ - lib/ruby_indexer/lib/ruby_indexer/index.rb
94
+ - lib/ruby_indexer/lib/ruby_indexer/visitor.rb
95
+ - lib/ruby_indexer/ruby_indexer.rb
96
+ - lib/ruby_indexer/test/classes_and_modules_test.rb
97
+ - lib/ruby_indexer/test/configuration_test.rb
98
+ - lib/ruby_indexer/test/constant_test.rb
99
+ - lib/ruby_indexer/test/index_test.rb
100
+ - lib/ruby_indexer/test/test_case.rb
77
101
  - lib/ruby_lsp/check_docs.rb
78
102
  - lib/ruby_lsp/document.rb
79
103
  - lib/ruby_lsp/event_emitter.rb
@@ -140,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
164
  - !ruby/object:Gem::Version
141
165
  version: '0'
142
166
  requirements: []
143
- rubygems_version: 3.4.17
167
+ rubygems_version: 3.4.18
144
168
  signing_key:
145
169
  specification_version: 4
146
170
  summary: An opinionated language server for Ruby