Skip to content

Commit

Permalink
LSP: Support formatting RBS file
Browse files Browse the repository at this point in the history
To keep RBS files clean, this adds a new feature to format RBS file to
Steep LSP.
  • Loading branch information
tk0miya committed Aug 18, 2024
1 parent 77a88f8 commit 931086d
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 2 deletions.
49 changes: 49 additions & 0 deletions lib/steep/server/interaction_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class InteractionWorker < BaseWorker
ApplyChangeJob = _ = Class.new()
HoverJob = _ = Struct.new(:id, :path, :line, :column, keyword_init: true)
CompletionJob = _ = Struct.new(:id, :path, :line, :column, :trigger, keyword_init: true)
FormattingJob = _ = Struct.new(:id, :path, keyword_init: true)
SignatureHelpJob = _ = Struct.new(:id, :path, :line, :column, keyword_init: true)

LSP = LanguageServer::Protocol
Expand Down Expand Up @@ -54,6 +55,13 @@ def handle_job(job)
result: process_latest_job(job) { process_signature_help(job) }
}
)
when FormattingJob
writer.write(
{
id: job.id,
result: process_latest_job(job) { process_formatting(job) }
}
)
end
end
end
Expand Down Expand Up @@ -125,6 +133,12 @@ def handle_request(request)
line, column = params[:position].yield_self {|hash| [hash[:line]+1, hash[:character]] }

queue_job SignatureHelpJob.new(id: id, path: path, line: line, column: column)
when "textDocument/formatting"
id = request[:id]
params = request[:params]
path = project.relative_path(PathHelper.to_pathname!(params[:textDocument][:uri]))

queue_job FormattingJob.new(id: id, path: path)
end
end

Expand Down Expand Up @@ -494,6 +508,41 @@ def process_signature_help(job)
# Reuse the latest result to keep SignatureHelp opened while typing
@last_signature_help_result if @last_signature_help_line == job.line
end

def process_formatting(job)
Steep.logger.tagged("#response_to_formatting") do
Steep.measure "Generating response" do
if (targets = project.targets_for_path(job.path)).is_a?(Array)
target = targets[0] or raise
sig_service = service.signature_services[target.name] or raise
file = sig_service.files[job.path]

parsed = RBS::Parser.parse_signature(file.content)
new_text = StringIO.new.tap do |out|
RBS::Writer.new(out: out).write(parsed[1] + parsed[2])
end.string

[
LSP::Interface::TextEdit.new(
range: LSP::Interface::Range.new(
start: LSP::Interface::Position.new(
line: 0,
character: 0
),
end: LSP::Interface::Position.new(
line: file.content.count("\n") + 1,
character: 0
)
),
new_text: new_text
)
]
else
nil
end
end
end
end
end
end
end
26 changes: 25 additions & 1 deletion lib/steep/server/master.rb
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,8 @@ def process_message_from_client(message)
definition_provider: true,
declaration_provider: false,
implementation_provider: true,
type_definition_provider: true
type_definition_provider: true,
document_formatting_provider: true
)
)
}
Expand Down Expand Up @@ -828,6 +829,29 @@ def process_message_from_client(message)
)
end

when "textDocument/formatting"
if interaction_worker
if message[:params][:textDocument][:uri].to_s =~ /\.rbs$/i
result_controller << send_request(method: message[:method], params: message[:params], worker: interaction_worker) do |handler|
handler.on_completion do |response|
job_queue << SendMessageJob.to_client(
message: {
id: message[:id],
result: response[:result]
}
)
end
end
else
job_queue << SendMessageJob.to_client(
message: {
id: message[:id],
result: nil
}
)
end
end

when "$/typecheck"
request = controller.make_request(
guid: message[:params][:guid],
Expand Down
12 changes: 11 additions & 1 deletion sig/steep/server/interaction_worker.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ module Steep
def initialize: (id: String, path: Pathname, line: Integer, column: Integer, trigger: String) -> void
end

class FormattingJob
attr_reader id: String

attr_reader path: Pathname

def initialize: (id: String, path: Pathname) -> void
end

class SignatureHelpJob
attr_reader id: String

Expand All @@ -48,7 +56,7 @@ module Steep
def initialize: (id: String, path: Pathname, line: Integer, column: Integer) -> void
end

type job = ApplyChangeJob | HoverJob | CompletionJob | SignatureHelpJob
type job = ApplyChangeJob | HoverJob | CompletionJob | FormattingJob | SignatureHelpJob

module LSP = LanguageServer::Protocol

Expand Down Expand Up @@ -85,6 +93,8 @@ module Steep
def format_completion_item_for_rbs: (Services::SignatureService, RBS::TypeName, CompletionJob job, String complete_text, Integer prefix_size) -> LanguageServer::Protocol::Interface::CompletionItem

def format_completion_item: (CompletionProvider::item item) -> LanguageServer::Protocol::Interface::CompletionItem

def process_formatting: (FormattingJob job) -> Array[LanguageServer::Protocol::Interface::TextEdit]?
end
end
end
32 changes: 32 additions & 0 deletions test/interaction_worker_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -442,5 +442,37 @@ class Qux
end
end
end

def test_handle_formatting_request
in_tmpdir do
project = Project.new(steepfile_path: current_dir + "Steepfile")
Project::DSL.parse(project, <<EOF)
target :lib do
check "lib"
signature "sig"
end
EOF
worker = Server::InteractionWorker.new(project: project, reader: worker_reader, writer: worker_writer, queue: [])

worker.service.update(
changes: {
Pathname("sig/hello.rbs") => [ContentChange.string(<<RUBY)]
class Foo
def baz: () -> String | () -> Integer
end
RUBY
}
) {}

response = worker.process_formatting(
InteractionWorker::FormattingJob.new(
path: Pathname("sig/hello.rbs")
)
)

assert_instance_of Array, response
assert_equal 1, response.size
assert_instance_of LanguageServer::Protocol::Interface::TextEdit, response[0]
end
end
end

0 comments on commit 931086d

Please sign in to comment.