ruby-lsp-rails 0.3.15 → 0.3.17

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8864e641f204eaafb608b703228bd98d5a2234413f48ee1d1232f277b7385144
4
- data.tar.gz: 3db424bb7e32fbd1a1dce231d56b9d98d92e23b0c9ef70f56cbab86b878f9133
3
+ metadata.gz: ba36df033667789339419a3ed19740d35ad9b91a3463cf2438bd390c4580aeb4
4
+ data.tar.gz: 7aa58db6217944f945ca3bb9bcdefeb4f018759acf8673e222ae8fb3b5c523ee
5
5
  SHA512:
6
- metadata.gz: 3820483222f6b082c3f939e9b797a71e775776fd3e5a3d9422a99ab1c3af992dce883a69781cf4b2a07e8492c87e3054a351df4f22c398fd12cb20c6a4472ff8
7
- data.tar.gz: 72e7715cefc65926b7d8011933e0152f90ee896b6530cea5edec0b41c70be701ae55619ea9c73dbb60e4becaf73df9f48ce122448ab2f6688204eda1d85edfb8
6
+ metadata.gz: a4ddf589e0065c32ee9087a4cfd0347d4e3dbdf5d1d667f1ca6a440ac1c6b2d1c18f41acfa2264f72114ecc4bfafec4c3c40748a3f6639a80c86093ca8e51cef
7
+ data.tar.gz: 2dbb86bd0f701865f3d833fb13079cfd9bd27310cdca2410664c7112db73e9915c9b633de1f527220a64055d3956009a738c367c956c3c08cd5c3ae71e04d7f1
data/README.md CHANGED
@@ -1,35 +1,20 @@
1
1
  [![Ruby DX Slack](https://img.shields.io/badge/Slack-Ruby%20DX-success?logo=slack)](https://join.slack.com/t/ruby-dx/shared_invite/zt-2c8zjlir6-uUDJl8oIwcen_FS_aA~b6Q)
2
2
 
3
- # Ruby LSP Rails
3
+ # Rails add-on
4
4
 
5
- Ruby LSP Rails is a [Ruby LSP](https://github.com/Shopify/ruby-lsp) addon for extra [Rails editor features](https://shopify.github.io/ruby-lsp-rails/FEATURES_md.html).
5
+ The Rails add-on is a [Ruby LSP](https://github.com/Shopify/ruby-lsp) [add-on](https://shopify.github.io/ruby-lsp/add-ons.html) for extra [Rails editor features](https://shopify.github.io/ruby-lsp/rails-add-on.html).
6
6
 
7
7
  ## Installation
8
8
 
9
- If you haven't already done so, you'll need to first [set up Ruby LSP](https://github.com/Shopify/ruby-lsp#usage).
9
+ If you haven't already done so, you'll need to first [set up Ruby LSP](https://shopify.github.io/ruby-lsp/#usage).
10
10
 
11
- As of v0.3.0, Ruby LSP will automatically include the Ruby LSP Rails addon in its custom bundle when a Rails app is detected.
11
+ As of v0.3.0, Ruby LSP will automatically include the Ruby LSP Rails add-on in its custom bundle when a Rails app is detected.
12
12
  There is no need to add the gem to your bundle.
13
13
 
14
14
  ## Documentation
15
15
 
16
- See the [documentation](https://shopify.github.io/ruby-lsp-rails) for more in-depth details about the
17
- [supported features](https://shopify.github.io/ruby-lsp-rails/FEATURES_md.html).
18
-
19
- ## How Runtime Introspection Works
20
-
21
- LSP tooling is typically based on static analysis, but `ruby-lsp-rails` actually communicates with your Rails app for
22
- some features.
23
-
24
- When Ruby LSP Rails starts, it spawns a `rails runner` instance which runs
25
- [`server.rb`](https://github.com/Shopify/ruby-lsp-rails/blob/main/lib/ruby_lsp/ruby_lsp_rails/server.rb).
26
- The addon communicates with this process over a pipe (i.e. `stdin` and `stdout`) to fetch runtime information about the application.
27
-
28
- When extension is stopped (e.g. by quitting the editor), the server instance is shut down.
29
-
30
- > [!NOTE]
31
- > Prior to v0.3.0, `ruby-lsp-rails` used a different approach which involved mounting a Rack application within the Rails app.
32
- > That approach was brittle and susceptible to the application's configuration, such as routing and middleware.
16
+ See the [documentation](https://shopify.github.io/ruby-lsp/rails-add-on.html) for more in-depth details about the
17
+ [supported features](https://shopify.github.io/ruby-lsp/rails-add-on.html#features).
33
18
 
34
19
  ## Contributing
35
20
 
data/Rakefile CHANGED
@@ -9,8 +9,6 @@ load "rails/tasks/statistics.rake"
9
9
 
10
10
  require "bundler/gem_tasks"
11
11
  require "rake/testtask"
12
- require "ruby_lsp/check_docs"
13
- require "rdoc/task"
14
12
 
15
13
  Rake::TestTask.new(:test) do |t|
16
14
  t.libs << "test"
@@ -18,17 +16,4 @@ Rake::TestTask.new(:test) do |t|
18
16
  t.test_files = FileList["test/**/*_test.rb"]
19
17
  end
20
18
 
21
- RDoc::Task.new do |rdoc|
22
- rdoc.main = "README.md"
23
- rdoc.rdoc_files.include("*.md", "lib/**/*.rb")
24
- rdoc.rdoc_dir = "docs"
25
- rdoc.markup = "markdown"
26
- rdoc.title = "Ruby LSP Rails"
27
- rdoc.generator = "snapper"
28
- rdoc.options.push("--copy-files", "misc")
29
- rdoc.options.push("--copy-files", "LICENSE.txt")
30
- end
31
-
32
- RubyLsp::CheckDocs.new(FileList["#{__dir__}/lib/ruby_lsp/**/*.rb"], FileList["#{__dir__}/misc/**/*.gif"])
33
-
34
19
  task default: [:"db:setup", :test]
@@ -28,20 +28,32 @@ module RubyLsp
28
28
  # the real client is initialized, features that depend on it will not be blocked by using the NullClient
29
29
  @rails_runner_client = T.let(NullClient.new, RunnerClient)
30
30
  @global_state = T.let(nil, T.nilable(GlobalState))
31
+ @addon_mutex = T.let(Mutex.new, Mutex)
32
+ @client_mutex = T.let(Mutex.new, Mutex)
33
+ @client_mutex.lock
34
+
35
+ Thread.new do
36
+ @addon_mutex.synchronize do
37
+ # We need to ensure the Rails client is fully loaded before we activate the server addons
38
+ @client_mutex.synchronize { @rails_runner_client = RunnerClient.create_client }
39
+ end
40
+ end
31
41
  end
32
42
 
33
43
  sig { returns(RunnerClient) }
34
- attr_reader :rails_runner_client
44
+ def rails_runner_client
45
+ @addon_mutex.synchronize { @rails_runner_client }
46
+ end
35
47
 
36
48
  sig { override.params(global_state: GlobalState, message_queue: Thread::Queue).void }
37
49
  def activate(global_state, message_queue)
38
50
  @global_state = global_state
39
- $stderr.puts("Activating Ruby LSP Rails addon v#{VERSION}")
40
- # Start booting the real client in a background thread. Until this completes, the client will be a NullClient
41
- Thread.new { @rails_runner_client = RunnerClient.create_client }
51
+ $stderr.puts("Activating Ruby LSP Rails add-on v#{version}")
42
52
  register_additional_file_watchers(global_state: global_state, message_queue: message_queue)
43
-
44
53
  @global_state.index.register_enhancement(IndexingEnhancement.new)
54
+
55
+ # Start booting the real client in a background thread. Until this completes, the client will be a NullClient
56
+ @client_mutex.unlock
45
57
  end
46
58
 
47
59
  sig { override.void }
@@ -49,6 +61,11 @@ module RubyLsp
49
61
  @rails_runner_client.shutdown
50
62
  end
51
63
 
64
+ sig { override.returns(String) }
65
+ def version
66
+ VERSION
67
+ end
68
+
52
69
  # Creates a new CodeLens listener. This method is invoked on every CodeLens request
53
70
  sig do
54
71
  override.params(
@@ -198,9 +198,13 @@ module RubyLsp
198
198
  .gsub("::", "/")
199
199
  .downcase
200
200
 
201
- view_uris = Dir.glob("#{@client.rails_root}/app/views/#{controller_name}/#{action_name}*").map! do |path|
201
+ view_uris = Dir.glob("#{@client.rails_root}/app/views/#{controller_name}/#{action_name}*").filter_map do |path|
202
+ # it's possible we could have a directory with the same name as the action, so we need to skip those
203
+ next if File.directory?(path)
204
+
202
205
  URI::Generic.from_path(path: path).to_s
203
206
  end
207
+
204
208
  return if view_uris.empty?
205
209
 
206
210
  @response_builder << create_code_lens(
@@ -59,7 +59,8 @@ module RubyLsp
59
59
  file_path,
60
60
  name_arg.location,
61
61
  name_arg.location,
62
- [],
62
+ nil,
63
+ index.configuration.encoding,
63
64
  [RubyIndexer::Entry::Signature.new([])],
64
65
  RubyIndexer::Entry::Visibility::PUBLIC,
65
66
  owner,
@@ -71,7 +72,8 @@ module RubyLsp
71
72
  file_path,
72
73
  name_arg.location,
73
74
  name_arg.location,
74
- [],
75
+ nil,
76
+ index.configuration.encoding,
75
77
  [RubyIndexer::Entry::Signature.new([RubyIndexer::Entry::RequiredParameter.new(name: name.to_sym)])],
76
78
  RubyIndexer::Entry::Visibility::PUBLIC,
77
79
  owner,
@@ -96,6 +96,14 @@ module RubyLsp
96
96
  raise InitializationError, @stderr.read
97
97
  end
98
98
 
99
+ sig { params(server_addon_path: String).void }
100
+ def register_server_addon(server_addon_path)
101
+ send_notification("server_addon/register", server_addon_path: server_addon_path)
102
+ rescue IncompleteMessageError
103
+ $stderr.puts("Ruby LSP Rails failed to register server addon #{server_addon_path}")
104
+ nil
105
+ end
106
+
99
107
  sig { params(name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
100
108
  def model(name)
101
109
  make_request("model", name: name)
@@ -8,13 +8,56 @@ require "json"
8
8
 
9
9
  module RubyLsp
10
10
  module Rails
11
- class Server
12
- VOID = Object.new
11
+ class ServerAddon
12
+ @server_addon_classes = []
13
+ @server_addons = {}
14
+
15
+ class << self
16
+ # We keep track of runtime server add-ons the same way we track other add-ons, by storing classes that inherit
17
+ # from the base one
18
+ def inherited(child)
19
+ @server_addon_classes << child
20
+ super
21
+ end
22
+
23
+ # Delegate `request` with `params` to the server add-on with the given `name`
24
+ def delegate(name, request, params)
25
+ @server_addons[name]&.execute(request, params)
26
+ end
27
+
28
+ # Instantiate all server addons and store them in a hash for easy access after we have discovered the classes
29
+ def finalize_registrations!(stdout)
30
+ until @server_addon_classes.empty?
31
+ addon = @server_addon_classes.shift.new(stdout)
32
+ @server_addons[addon.name] = addon
33
+ end
34
+ end
35
+ end
36
+
37
+ def initialize(stdout)
38
+ @stdout = stdout
39
+ end
13
40
 
14
- def initialize
41
+ # Write a response back. Can be used for sending notifications to the editor
42
+ def write_response(response)
43
+ json_response = response.to_json
44
+ @stdout.write("Content-Length: #{json_response.length}\r\n\r\n#{json_response}")
45
+ end
46
+
47
+ def name
48
+ raise NotImplementedError, "Not implemented!"
49
+ end
50
+
51
+ def execute(request, params)
52
+ raise NotImplementedError, "Not implemented!"
53
+ end
54
+ end
55
+
56
+ class Server
57
+ def initialize(stdout: $stdout, override_default_output_device: true)
15
58
  # Grab references to the original pipes so that we can change the default output device further down
16
59
  @stdin = $stdin
17
- @stdout = $stdout
60
+ @stdout = stdout
18
61
  @stderr = $stderr
19
62
  @stdin.sync = true
20
63
  @stdout.sync = true
@@ -26,7 +69,7 @@ module RubyLsp
26
69
  # # Set the default output device to be $stderr. This means that using `puts` by itself will default to printing
27
70
  # # to $stderr and only explicit `$stdout.puts` will go to $stdout. This reduces the chance that output coming
28
71
  # # from the Rails app will be accidentally sent to the client
29
- $> = $stderr
72
+ $> = $stderr if override_default_output_device
30
73
 
31
74
  @running = true
32
75
  end
@@ -44,11 +87,7 @@ module RubyLsp
44
87
  json = @stdin.read(headers[/Content-Length: (\d+)/i, 1].to_i)
45
88
 
46
89
  request = JSON.parse(json, symbolize_names: true)
47
- response = execute(request.fetch(:method), request[:params])
48
- next if response == VOID
49
-
50
- json_response = response.to_json
51
- @stdout.write("Content-Length: #{json_response.length}\r\n\r\n#{json_response}")
90
+ execute(request.fetch(:method), request[:params])
52
91
  end
53
92
  end
54
93
 
@@ -56,27 +95,35 @@ module RubyLsp
56
95
  case request
57
96
  when "shutdown"
58
97
  @running = false
59
- VOID
60
98
  when "model"
61
- resolve_database_info_from_model(params.fetch(:name))
99
+ write_response(resolve_database_info_from_model(params.fetch(:name)))
62
100
  when "association_target_location"
63
- resolve_association_target(params)
101
+ write_response(resolve_association_target(params))
64
102
  when "reload"
65
103
  ::Rails.application.reloader.reload!
66
- VOID
67
104
  when "route_location"
68
- route_location(params.fetch(:name))
105
+ write_response(route_location(params.fetch(:name)))
69
106
  when "route_info"
70
- resolve_route_info(params)
71
- else
72
- VOID
107
+ write_response(resolve_route_info(params))
108
+ when "server_addon/register"
109
+ require params[:server_addon_path]
110
+ ServerAddon.finalize_registrations!(@stdout)
111
+ when "server_addon/delegate"
112
+ server_addon_name = params.delete(:server_addon_name)
113
+ request_name = params.delete(:request_name)
114
+ ServerAddon.delegate(server_addon_name, request_name, params)
73
115
  end
74
116
  rescue => e
75
- { error: e.full_message(highlight: false) }
117
+ write_response({ error: e.full_message(highlight: false) })
76
118
  end
77
119
 
78
120
  private
79
121
 
122
+ def write_response(response)
123
+ json_response = response.to_json
124
+ @stdout.write("Content-Length: #{json_response.length}\r\n\r\n#{json_response}")
125
+ end
126
+
80
127
  def resolve_route_info(requirements)
81
128
  if requirements[:controller]
82
129
  requirements[:controller] = requirements.fetch(:controller).underscore.delete_suffix("_controller")
@@ -3,6 +3,6 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Rails
6
- VERSION = "0.3.15"
6
+ VERSION = "0.3.17"
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.15
4
+ version: 0.3.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-16 00:00:00.000000000 Z
11
+ date: 2024-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-lsp
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.17.12
19
+ version: 0.19.0
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: 0.18.0
22
+ version: 0.20.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 0.17.12
29
+ version: 0.19.0
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: 0.18.0
32
+ version: 0.20.0
33
33
  description: A Ruby LSP addon that adds extra editor functionality for Rails applications
34
34
  email:
35
35
  - ruby@shopify.com
@@ -65,6 +65,7 @@ metadata:
65
65
  homepage_uri: https://github.com/Shopify/ruby-lsp-rails
66
66
  source_code_uri: https://github.com/Shopify/ruby-lsp-rails
67
67
  changelog_uri: https://github.com/Shopify/ruby-lsp-rails/releases
68
+ documentation_uri: https://shopify.github.io/ruby-lsp/rails-add-on.html
68
69
  post_install_message:
69
70
  rdoc_options: []
70
71
  require_paths:
@@ -80,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
81
  - !ruby/object:Gem::Version
81
82
  version: '0'
82
83
  requirements: []
83
- rubygems_version: 3.5.18
84
+ rubygems_version: 3.5.20
84
85
  signing_key:
85
86
  specification_version: 4
86
87
  summary: A Ruby LSP addon for Rails