Skip to content

Add code lens extension (for Rails) #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ PATH
specs:
ruby-lsp-rails (0.1.0)
rails (>= 6.0)
ruby-lsp (>= 0.5.1, < 0.7.0)
ruby-lsp (>= 0.6.2, < 0.7.0)
sorbet-runtime (>= 0.5.9897)

GEM
Expand Down Expand Up @@ -195,7 +195,7 @@ GEM
rubocop (~> 1.51)
rubocop-sorbet (0.7.0)
rubocop (>= 0.90.0)
ruby-lsp (0.6.1)
ruby-lsp (0.6.2)
language_server-protocol (~> 3.17.0)
sorbet-runtime
syntax_tree (>= 6.1.1, < 7)
Expand Down
5 changes: 1 addition & 4 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ RDoc::Task.new do |rdoc|
rdoc.options.push("--copy-files", "LICENSE.txt")
end

RubyLsp::CheckDocs.new(
FileList["#{__dir__}/lib/ruby_lsp/ruby_lsp_rails/**/*.rb"],
FileList["#{__dir__}/**/*.gif"],
)
RubyLsp::CheckDocs.new(FileList["#{__dir__}/lib/ruby_lsp/**/*.rb"], FileList["#{__dir__}/misc/**/*.gif"])

task default: :test
134 changes: 134 additions & 0 deletions lib/ruby_lsp/ruby_lsp_rails/code_lens.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# typed: strict
# frozen_string_literal: true

module RubyLsp
module Rails
# ![CodeLens demo](../../code_lens.gif)
#
# This feature adds several CodeLens features for Rails applications using Active Support test cases:
# - Run tests in the VS Terminal
# - Run tests in the VS Code Test Explorer
# - Debug tests
#
# The
# [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
# request informs the editor of runnable commands such as tests
#
# # Example:
#
# For the following code, Code Lenses will be added above the class definition above each test method.
#
# ```ruby
# Run
# class HelloTest < ActiveSupport::TestCase # <- Will show code lenses above for running or debugging the whole test
# test "outputs hello" do # <- Will show code lenses above for running or debugging this test
# # ...
# end
#
# test "outputs goodbye" do # <- Will show code lenses above for running or debugging this test
# # ...
# end
# end
# ````
#
# The code lenses will be displayed above the class and above each test method.
class CodeLens < ::RubyLsp::Listener
extend T::Sig
extend T::Generic

ResponseType = type_member { { fixed: T::Array[::RubyLsp::Interface::CodeLens] } }
BASE_COMMAND = "bin/rails test"

::RubyLsp::Requests::CodeLens.add_listener(self)

sig { override.returns(ResponseType) }
attr_reader :response

sig { params(uri: String, emitter: EventEmitter, message_queue: Thread::Queue).void }
def initialize(uri, emitter, message_queue)
@response = T.let([], ResponseType)
@path = T.let(URI(uri).path, T.nilable(String))
emitter.register(self, :on_command, :on_class, :on_def)

super(emitter, message_queue)
end

sig { params(node: SyntaxTree::Command).void }
def on_command(node)
message_value = node.message.value
return unless message_value == "test" && node.arguments.parts.any?

first_argument = node.arguments.parts.first
return unless first_argument.is_a?(SyntaxTree::StringLiteral)

test_name = first_argument.parts.first.value
return unless test_name

line_number = node.location.start_line
command = "#{BASE_COMMAND} #{@path}:#{line_number}"
add_test_code_lens(node, name: test_name, command: command, kind: :example)
end

# Although uncommon, Rails tests can be written with the classic "def test_name" syntax.
sig { params(node: SyntaxTree::DefNode).void }
def on_def(node)
method_name = node.name.value
if method_name.start_with?("test_")
line_number = node.location.start_line
command = "#{BASE_COMMAND} #{@path}:#{line_number}"
add_test_code_lens(node, name: method_name, command: command, kind: :example)
end
end

sig { params(node: SyntaxTree::ClassDeclaration).void }
def on_class(node)
class_name = node.constant.constant.value
if class_name.end_with?("Test")
command = "#{BASE_COMMAND} #{@path}"
add_test_code_lens(node, name: class_name, command: command, kind: :group)
end
end

private

sig { params(node: SyntaxTree::Node, name: String, command: String, kind: Symbol).void }
def add_test_code_lens(node, name:, command:, kind:)
arguments = [
@path,
name,
command,
{
start_line: node.location.start_line - 1,
start_column: node.location.start_column,
end_line: node.location.end_line - 1,
end_column: node.location.end_column,
},
]

@response << create_code_lens(
node,
title: "Run",
command_name: "rubyLsp.runTest",
arguments: arguments,
data: { type: "test", kind: kind },
)

@response << create_code_lens(
node,
title: "Run In Terminal",
command_name: "rubyLsp.runTestInTerminal",
arguments: arguments,
data: { type: "test_in_terminal", kind: kind },
)

@response << create_code_lens(
node,
title: "Debug",
command_name: "rubyLsp.debugTest",
arguments: arguments,
data: { type: "debug", kind: kind },
)
end
end
end
end
1 change: 1 addition & 0 deletions lib/ruby_lsp/ruby_lsp_rails/extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

require_relative "rails_client"
require_relative "hover"
require_relative "code_lens"

module RubyLsp
module Rails
Expand Down
Binary file added misc/code_lens.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion ruby-lsp-rails.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ Gem::Specification.new do |spec|
end

spec.add_dependency("rails", ">= 6.0")
spec.add_dependency("ruby-lsp", ">= 0.5.1", "< 0.7.0")
spec.add_dependency("ruby-lsp", ">= 0.6.2", "< 0.7.0")
spec.add_dependency("sorbet-runtime", ">= 0.5.9897")
end
Loading