ruby-lsp 0.8.0 → 0.9.0
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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +41 -33
- data/lib/core_ext/uri.rb +9 -14
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +166 -0
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +147 -0
- data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +123 -0
- data/lib/ruby_indexer/ruby_indexer.rb +20 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +220 -0
- data/lib/ruby_indexer/test/configuration_test.rb +114 -0
- data/lib/ruby_indexer/test/constant_test.rb +108 -0
- data/lib/ruby_indexer/test/index_test.rb +129 -0
- data/lib/ruby_indexer/test/test_case.rb +42 -0
- data/lib/ruby_lsp/executor.rb +144 -10
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/requests/definition.rb +60 -5
- data/lib/ruby_lsp/requests/hover.rb +53 -30
- data/lib/ruby_lsp/requests/on_type_formatting.rb +4 -6
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +7 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +86 -0
- data/lib/ruby_lsp/requests.rb +1 -0
- data/lib/ruby_lsp/setup_bundler.rb +24 -8
- data/lib/ruby_lsp/store.rb +15 -9
- metadata +33 -4
- data/lib/ruby_lsp/requests/support/rails_document_client.rb +0 -122
@@ -6,6 +6,7 @@ require "bundler"
|
|
6
6
|
require "fileutils"
|
7
7
|
require "pathname"
|
8
8
|
require "digest"
|
9
|
+
require "time"
|
9
10
|
|
10
11
|
# This file is a script that will configure a custom bundle for the Ruby LSP. The custom bundle allows developers to use
|
11
12
|
# the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to the
|
@@ -17,15 +18,19 @@ module RubyLsp
|
|
17
18
|
|
18
19
|
class BundleNotLocked < StandardError; end
|
19
20
|
|
20
|
-
|
21
|
-
|
21
|
+
FOUR_HOURS = T.let(4 * 60 * 60, Integer)
|
22
|
+
|
23
|
+
sig { params(project_path: String, branch: T.nilable(String)).void }
|
24
|
+
def initialize(project_path, branch: nil)
|
22
25
|
@project_path = project_path
|
26
|
+
@branch = branch
|
23
27
|
|
24
28
|
# Custom bundle paths
|
25
29
|
@custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(Dir.pwd), Pathname)
|
26
30
|
@custom_gemfile = T.let(@custom_dir + "Gemfile", Pathname)
|
27
31
|
@custom_lockfile = T.let(@custom_dir + "Gemfile.lock", Pathname)
|
28
32
|
@lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
|
33
|
+
@last_updated_path = T.let(@custom_dir + "last_updated", Pathname)
|
29
34
|
|
30
35
|
# Regular bundle paths
|
31
36
|
@gemfile = T.let(
|
@@ -119,7 +124,9 @@ module RubyLsp
|
|
119
124
|
end
|
120
125
|
|
121
126
|
unless @dependencies["ruby-lsp"]
|
122
|
-
|
127
|
+
ruby_lsp_entry = +'gem "ruby-lsp", require: false, group: :development'
|
128
|
+
ruby_lsp_entry << ", github: \"Shopify/ruby-lsp\", branch: \"#{@branch}\"" if @branch
|
129
|
+
parts << ruby_lsp_entry
|
123
130
|
end
|
124
131
|
|
125
132
|
unless @dependencies["debug"]
|
@@ -156,11 +163,12 @@ module RubyLsp
|
|
156
163
|
# `.ruby-lsp` folder, which is not the user's intention. For example, if the path is configured as `vendor`, we
|
157
164
|
# want to install it in the top level `vendor` and not `.ruby-lsp/vendor`
|
158
165
|
path = Bundler.settings["path"]
|
166
|
+
expanded_path = File.expand_path(path, Dir.pwd) if path
|
159
167
|
|
160
168
|
# Use the absolute `BUNDLE_PATH` to prevent accidentally creating unwanted folders under `.ruby-lsp`
|
161
169
|
env = {}
|
162
170
|
env["BUNDLE_GEMFILE"] = bundle_gemfile.to_s
|
163
|
-
env["BUNDLE_PATH"] =
|
171
|
+
env["BUNDLE_PATH"] = expanded_path if expanded_path
|
164
172
|
|
165
173
|
# If both `ruby-lsp` and `debug` are already in the Gemfile, then we shouldn't try to upgrade them or else we'll
|
166
174
|
# produce undesired source control changes. If the custom bundle was just created and either `ruby-lsp` or `debug`
|
@@ -169,15 +177,16 @@ module RubyLsp
|
|
169
177
|
# custom `.ruby-lsp/Gemfile.lock` already exists and includes both gems
|
170
178
|
command = +""
|
171
179
|
|
172
|
-
if
|
173
|
-
custom_bundle_dependencies["ruby-lsp"].nil? || custom_bundle_dependencies["debug"].nil?
|
180
|
+
if should_bundle_install?
|
174
181
|
# Install gems using the custom bundle
|
175
|
-
command << "bundle install "
|
182
|
+
command << "bundle check || bundle install "
|
176
183
|
else
|
177
184
|
# If ruby-lsp or debug are not in the Gemfile, try to update them to the latest version
|
178
185
|
command << "bundle update "
|
179
186
|
command << "ruby-lsp " unless @dependencies["ruby-lsp"]
|
180
187
|
command << "debug " unless @dependencies["debug"]
|
188
|
+
|
189
|
+
@last_updated_path.write(Time.now.iso8601)
|
181
190
|
end
|
182
191
|
|
183
192
|
# Redirect stdout to stderr to prevent going into an infinite loop. The extension might confuse stdout output with
|
@@ -187,7 +196,14 @@ module RubyLsp
|
|
187
196
|
# Add bundle update
|
188
197
|
warn("Ruby LSP> Running bundle install for the custom bundle. This may take a while...")
|
189
198
|
system(env, command)
|
190
|
-
[bundle_gemfile.to_s,
|
199
|
+
[bundle_gemfile.to_s, expanded_path]
|
200
|
+
end
|
201
|
+
|
202
|
+
sig { returns(T::Boolean) }
|
203
|
+
def should_bundle_install?
|
204
|
+
(!@dependencies["ruby-lsp"].nil? && !@dependencies["debug"].nil?) ||
|
205
|
+
custom_bundle_dependencies["ruby-lsp"].nil? || custom_bundle_dependencies["debug"].nil? ||
|
206
|
+
(@last_updated_path.exist? && Time.parse(@last_updated_path.read) > (Time.now - FOUR_HOURS))
|
191
207
|
end
|
192
208
|
end
|
193
209
|
end
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
32
|
-
|
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.
|
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.
|
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.
|
64
|
+
@state.delete(uri.to_s)
|
59
65
|
end
|
60
66
|
|
61
67
|
sig do
|
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.
|
4
|
+
version: 0.9.0
|
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-
|
11
|
+
date: 2023-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -58,6 +58,26 @@ 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.9'
|
68
|
+
- - "<"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0.10'
|
71
|
+
type: :runtime
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0.9'
|
78
|
+
- - "<"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0.10'
|
61
81
|
description: An opinionated language server for Ruby
|
62
82
|
email:
|
63
83
|
- ruby@shopify.com
|
@@ -75,6 +95,15 @@ files:
|
|
75
95
|
- lib/core_ext/uri.rb
|
76
96
|
- lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
|
77
97
|
- lib/ruby-lsp.rb
|
98
|
+
- lib/ruby_indexer/lib/ruby_indexer/configuration.rb
|
99
|
+
- lib/ruby_indexer/lib/ruby_indexer/index.rb
|
100
|
+
- lib/ruby_indexer/lib/ruby_indexer/visitor.rb
|
101
|
+
- lib/ruby_indexer/ruby_indexer.rb
|
102
|
+
- lib/ruby_indexer/test/classes_and_modules_test.rb
|
103
|
+
- lib/ruby_indexer/test/configuration_test.rb
|
104
|
+
- lib/ruby_indexer/test/constant_test.rb
|
105
|
+
- lib/ruby_indexer/test/index_test.rb
|
106
|
+
- lib/ruby_indexer/test/test_case.rb
|
78
107
|
- lib/ruby_lsp/check_docs.rb
|
79
108
|
- lib/ruby_lsp/document.rb
|
80
109
|
- lib/ruby_lsp/event_emitter.rb
|
@@ -107,7 +136,6 @@ files:
|
|
107
136
|
- lib/ruby_lsp/requests/support/formatter_runner.rb
|
108
137
|
- lib/ruby_lsp/requests/support/highlight_target.rb
|
109
138
|
- lib/ruby_lsp/requests/support/prefix_tree.rb
|
110
|
-
- lib/ruby_lsp/requests/support/rails_document_client.rb
|
111
139
|
- lib/ruby_lsp/requests/support/rubocop_diagnostic.rb
|
112
140
|
- lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb
|
113
141
|
- lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb
|
@@ -117,6 +145,7 @@ files:
|
|
117
145
|
- lib/ruby_lsp/requests/support/sorbet.rb
|
118
146
|
- lib/ruby_lsp/requests/support/source_uri.rb
|
119
147
|
- lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb
|
148
|
+
- lib/ruby_lsp/requests/workspace_symbol.rb
|
120
149
|
- lib/ruby_lsp/server.rb
|
121
150
|
- lib/ruby_lsp/setup_bundler.rb
|
122
151
|
- lib/ruby_lsp/store.rb
|
@@ -141,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
170
|
- !ruby/object:Gem::Version
|
142
171
|
version: '0'
|
143
172
|
requirements: []
|
144
|
-
rubygems_version: 3.4.
|
173
|
+
rubygems_version: 3.4.18
|
145
174
|
signing_key:
|
146
175
|
specification_version: 4
|
147
176
|
summary: An opinionated language server for Ruby
|
@@ -1,122 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "net/http"
|
5
|
-
|
6
|
-
module RubyLsp
|
7
|
-
module Requests
|
8
|
-
module Support
|
9
|
-
class RailsDocumentClient
|
10
|
-
RAILS_DOC_HOST = "https://api.rubyonrails.org"
|
11
|
-
SUPPORTED_RAILS_DOC_NAMESPACES = T.let(
|
12
|
-
Regexp.union(
|
13
|
-
/ActionDispatch/,
|
14
|
-
/ActionController/,
|
15
|
-
/AbstractController/,
|
16
|
-
/ActiveRecord/,
|
17
|
-
/ActiveModel/,
|
18
|
-
/ActiveStorage/,
|
19
|
-
/ActionText/,
|
20
|
-
/ActiveJob/,
|
21
|
-
).freeze,
|
22
|
-
Regexp,
|
23
|
-
)
|
24
|
-
|
25
|
-
RAILTIES_VERSION = T.let(
|
26
|
-
[*::Gem::Specification.default_stubs, *::Gem::Specification.stubs].find do |s|
|
27
|
-
s.name == "railties"
|
28
|
-
end&.version&.to_s,
|
29
|
-
T.nilable(String),
|
30
|
-
)
|
31
|
-
|
32
|
-
class << self
|
33
|
-
extend T::Sig
|
34
|
-
sig do
|
35
|
-
params(name: String).returns(T::Array[String])
|
36
|
-
end
|
37
|
-
def generate_rails_document_urls(name)
|
38
|
-
docs = search_index&.fetch(name, nil)
|
39
|
-
|
40
|
-
return [] unless docs
|
41
|
-
|
42
|
-
docs.map do |doc|
|
43
|
-
owner = doc[:owner]
|
44
|
-
|
45
|
-
link_name =
|
46
|
-
# class/module name
|
47
|
-
if owner == name
|
48
|
-
name
|
49
|
-
else
|
50
|
-
"#{owner}##{name}"
|
51
|
-
end
|
52
|
-
|
53
|
-
"[Rails Document: `#{link_name}`](#{doc[:url]})"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
sig { returns(T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, String]]])) }
|
58
|
-
private def search_index
|
59
|
-
@rails_documents ||= T.let(
|
60
|
-
build_search_index,
|
61
|
-
T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, String]]]),
|
62
|
-
)
|
63
|
-
end
|
64
|
-
|
65
|
-
sig { returns(T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, String]]])) }
|
66
|
-
private def build_search_index
|
67
|
-
return unless RAILTIES_VERSION
|
68
|
-
|
69
|
-
warn("Fetching Rails Documents...")
|
70
|
-
|
71
|
-
response = Net::HTTP.get_response(URI("#{RAILS_DOC_HOST}/v#{RAILTIES_VERSION}/js/search_index.js"))
|
72
|
-
|
73
|
-
if response.code == "302"
|
74
|
-
# If the version's doc is not found, e.g. Rails main, it'll be redirected
|
75
|
-
# In this case, we just fetch the latest doc
|
76
|
-
response = Net::HTTP.get_response(URI("#{RAILS_DOC_HOST}/js/search_index.js"))
|
77
|
-
end
|
78
|
-
|
79
|
-
if response.code == "200"
|
80
|
-
process_search_index(response.body)
|
81
|
-
else
|
82
|
-
warn("Response failed: #{response.inspect}")
|
83
|
-
nil
|
84
|
-
end
|
85
|
-
rescue StandardError => e
|
86
|
-
warn("Exception occurred when fetching Rails document index: #{e.inspect}")
|
87
|
-
end
|
88
|
-
|
89
|
-
sig { params(js: String).returns(T::Hash[String, T::Array[T::Hash[Symbol, String]]]) }
|
90
|
-
private def process_search_index(js)
|
91
|
-
raw_data = js.sub("var search_data = ", "")
|
92
|
-
info = JSON.parse(raw_data).dig("index", "info")
|
93
|
-
|
94
|
-
# An entry looks like this:
|
95
|
-
#
|
96
|
-
# ["belongs_to", # method or module/class
|
97
|
-
# "ActiveRecord::Associations::ClassMethods", # method owner
|
98
|
-
# "classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to", # path to the document
|
99
|
-
# "(name, scope = nil, **options)", # method's parameters
|
100
|
-
# "<p>Specifies a one-to-one association with another class..."] # document preview
|
101
|
-
#
|
102
|
-
info.each_with_object({}) do |(method_or_class, method_owner, doc_path, _, doc_preview), table|
|
103
|
-
# If a method doesn't have documentation, there's no need to generate the link to it.
|
104
|
-
next if doc_preview.nil? || doc_preview.empty?
|
105
|
-
|
106
|
-
# If the method or class/module is not from the supported namespace, reject it
|
107
|
-
next unless [method_or_class, method_owner].any? do |elem|
|
108
|
-
elem.match?(SUPPORTED_RAILS_DOC_NAMESPACES)
|
109
|
-
end
|
110
|
-
|
111
|
-
owner = method_owner.empty? ? method_or_class : method_owner
|
112
|
-
table[method_or_class] ||= []
|
113
|
-
# It's possible to have multiple modules defining the same method name. For example,
|
114
|
-
# both `ActiveRecord::FinderMethods` and `ActiveRecord::Associations::CollectionProxy` defines `#find`
|
115
|
-
table[method_or_class] << { owner: owner, url: "#{RAILS_DOC_HOST}/v#{RAILTIES_VERSION}/#{doc_path}" }
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|