Skip to content

Commit e5a2140

Browse files
committed
Allow extensions to register formatters
1 parent d946616 commit e5a2140

File tree

8 files changed

+78
-13
lines changed

8 files changed

+78
-13
lines changed

lib/ruby_lsp/executor.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,8 @@ def check_formatter_is_available
543543
return unless @store.formatter == "rubocop"
544544

545545
unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
546+
@store.formatter = "none"
547+
546548
@message_queue << Notification.new(
547549
message: "window/showMessage",
548550
params: Interface::ShowMessageParams.new(

lib/ruby_lsp/requests.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ module Support
4949
autoload :RailsDocumentClient, "ruby_lsp/requests/support/rails_document_client"
5050
autoload :PrefixTree, "ruby_lsp/requests/support/prefix_tree"
5151
autoload :Common, "ruby_lsp/requests/support/common"
52+
autoload :FormatterRunner, "ruby_lsp/requests/support/formatter_runner"
5253
end
5354
end
5455
end

lib/ruby_lsp/requests/formatting.rb

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,29 @@ class Formatting < BaseRequest
2929
class Error < StandardError; end
3030
class InvalidFormatter < StandardError; end
3131

32+
@formatters = T.let(
33+
{
34+
"syntax_tree" => Support::SyntaxTreeFormattingRunner.instance,
35+
},
36+
T::Hash[String, Support::FormatterRunner],
37+
)
38+
39+
class << self
40+
extend T::Sig
41+
42+
sig { returns(T::Hash[String, Support::FormatterRunner]) }
43+
attr_reader :formatters
44+
45+
sig { params(identifier: String, instance: Support::FormatterRunner).void }
46+
def register_formatter(identifier, instance)
47+
@formatters[identifier] = instance
48+
end
49+
end
50+
51+
if defined?(Support::RuboCopFormattingRunner)
52+
register_formatter("rubocop", Support::RuboCopFormattingRunner.instance)
53+
end
54+
3255
extend T::Sig
3356

3457
sig { params(document: Document, formatter: String).void }
@@ -41,6 +64,8 @@ def initialize(document, formatter: "auto")
4164

4265
sig { override.returns(T.nilable(T.all(T::Array[Interface::TextEdit], Object))) }
4366
def run
67+
return if @formatter == "none"
68+
4469
# Don't try to format files outside the current working directory
4570
return unless @uri.sub("file://", "").start_with?(Dir.pwd)
4671

@@ -67,16 +92,10 @@ def run
6792

6893
sig { returns(T.nilable(String)) }
6994
def formatted_file
70-
case @formatter
71-
when "rubocop"
72-
if defined?(Support::RuboCopFormattingRunner)
73-
Support::RuboCopFormattingRunner.instance.run(@uri, @document)
74-
end
75-
when "syntax_tree"
76-
Support::SyntaxTreeFormattingRunner.instance.run(@uri, @document)
77-
else
78-
raise InvalidFormatter, "Unknown formatter: #{@formatter}"
79-
end
95+
formatter_runner = Formatting.formatters[@formatter]
96+
raise InvalidFormatter, "Formatter is not available: #{@formatter}" unless formatter_runner
97+
98+
formatter_runner.run(@uri, @document)
8099
end
81100
end
82101
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module RubyLsp
5+
module Requests
6+
module Support
7+
module FormatterRunner
8+
extend T::Sig
9+
extend T::Helpers
10+
11+
interface!
12+
13+
sig { abstract.params(uri: String, document: Document).returns(T.nilable(String)) }
14+
def run(uri, document); end
15+
end
16+
end
17+
end
18+
end

lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ module Support
1313
class RuboCopFormattingRunner
1414
extend T::Sig
1515
include Singleton
16+
include Support::FormatterRunner
1617

1718
sig { void }
1819
def initialize
1920
# -a is for "--auto-correct" (or "--autocorrect" on newer versions of RuboCop)
2021
@runner = T.let(RuboCopRunner.new("-a"), RuboCopRunner)
2122
end
2223

23-
sig { params(uri: String, document: Document).returns(String) }
24+
sig { override.params(uri: String, document: Document).returns(String) }
2425
def run(uri, document)
2526
filename = CGI.unescape(URI.parse(uri).path)
2627

lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module Support
1111
class SyntaxTreeFormattingRunner
1212
extend T::Sig
1313
include Singleton
14+
include Support::FormatterRunner
1415

1516
sig { void }
1617
def initialize
@@ -25,7 +26,7 @@ def initialize
2526
)
2627
end
2728

28-
sig { params(uri: String, document: Document).returns(T.nilable(String)) }
29+
sig { override.params(uri: String, document: Document).returns(T.nilable(String)) }
2930
def run(uri, document)
3031
relative_path = Pathname.new(URI(uri).path).relative_path_from(T.must(WORKSPACE_URI.path))
3132
return if @options.ignore_files.any? { |pattern| File.fnmatch(pattern, relative_path) }

test/executor_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def test_shows_error_if_formatter_set_to_rubocop_but_rubocop_not_available
181181
executor.execute(method: "initialize", params: { initializationOptions: { formatter: "rubocop" } })
182182
executor.execute(method: "initialized")
183183

184-
assert_equal("rubocop", @store.formatter)
184+
assert_equal("none", @store.formatter)
185185
refute_empty(@message_queue)
186186
notification = T.must(@message_queue.pop)
187187
assert_equal("window/showMessage", notification.message)

test/requests/formatting_test.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,25 @@ def test_raises_on_invalid_formatter
137137
end
138138
end
139139

140+
def test_using_a_custom_formatter
141+
require "singleton"
142+
formatter_class = Class.new do
143+
include Singleton
144+
include RubyLsp::Requests::Support::FormatterRunner
145+
146+
def run(uri, document)
147+
"#{document.source}\n# formatter by my-custom-formatter"
148+
end
149+
end
150+
151+
RubyLsp::Requests::Formatting.register_formatter("my-custom-formatter", T.unsafe(formatter_class).instance)
152+
assert_includes(formatted_document("my-custom-formatter"), "# formatter by my-custom-formatter")
153+
end
154+
155+
def test_returns_nil_when_formatter_is_none
156+
assert_nil(formatted_document("none"))
157+
end
158+
140159
private
141160

142161
def formatted_document(formatter)
@@ -159,5 +178,9 @@ def clear_syntax_tree_runner_singleton_instance
159178
return unless defined?(RubyLsp::Requests::Support::SyntaxTreeFormattingRunner)
160179

161180
T.unsafe(Singleton).__init__(RubyLsp::Requests::Support::SyntaxTreeFormattingRunner)
181+
RubyLsp::Requests::Formatting.register_formatter(
182+
"syntax_tree",
183+
RubyLsp::Requests::Support::SyntaxTreeFormattingRunner.instance,
184+
)
162185
end
163186
end

0 commit comments

Comments
 (0)