Skip to content

Commit

Permalink
Merge pull request #195 from Shopify/support-document-link
Browse files Browse the repository at this point in the history
Support DocumentLink request
  • Loading branch information
st0012 authored Jul 27, 2022
2 parents 4b4b0f9 + 26be8eb commit 979a1fd
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/ruby_lsp/requests.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module RubyLsp
# Supported features
#
# - {RubyLsp::Requests::DocumentSymbol}
# - {RubyLsp::Requests::DocumentLink}
# - {RubyLsp::Requests::FoldingRanges}
# - {RubyLsp::Requests::SelectionRanges}
# - {RubyLsp::Requests::SemanticHighlighting}
Expand All @@ -15,6 +16,7 @@ module RubyLsp
module Requests
autoload :BaseRequest, "ruby_lsp/requests/base_request"
autoload :DocumentSymbol, "ruby_lsp/requests/document_symbol"
autoload :DocumentLink, "ruby_lsp/requests/document_link"
autoload :FoldingRanges, "ruby_lsp/requests/folding_ranges"
autoload :SelectionRanges, "ruby_lsp/requests/selection_ranges"
autoload :SemanticHighlighting, "ruby_lsp/requests/semantic_highlighting"
Expand Down
59 changes: 59 additions & 0 deletions lib/ruby_lsp/requests/document_link.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# typed: strict
# frozen_string_literal: true

module RubyLsp
module Requests
# ![Document link demo](../../misc/document_link.gif)
#
# The [document link](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentLink)
# makes `# source://PATH_TO_FILE:line` comments in a Ruby/RBI file clickable if the file exists.
# When the user clicks the link, it'll open that location.
#
# # Example
#
# ```ruby
# # source://syntax_tree-3.2.1/lib/syntax_tree.rb:51 <- it will be clickable and will take the user to that location
# def format(source, maxwidth = T.unsafe(nil))
# end
# ```
class DocumentLink < BaseRequest
extend T::Sig

RUBY_ROOT = "RUBY_ROOT"

sig { params(document: Document).void }
def initialize(document)
super

@links = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentLink])
end

sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentLink], Object)) }
def run
visit(@document.tree)
@links
end

sig { params(node: SyntaxTree::Comment).void }
def visit_comment(node)
match = node.value.match(%r{source://(?<path>.*):(?<line>\d+)$})
return unless match

file_path = if match[:path].start_with?(RUBY_ROOT)
match[:path].sub(RUBY_ROOT, RbConfig::CONFIG["rubylibdir"])
else
File.join(Bundler.bundle_path, "gems", match[:path])
end
return unless File.exist?(file_path)

target = "file://#{file_path}##{match[:line]}"

@links << LanguageServer::Protocol::Interface::DocumentLink.new(
range: range_from_syntax_tree_node(node),
target: target,
tooltip: "Jump to #{target.delete_prefix("file://")}"
)
end
end
end
end
11 changes: 11 additions & 0 deletions lib/ruby_lsp/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ module RubyLsp
)
end

document_link_provider = if enabled_features.include?("documentLink")
Interface::DocumentLinkOptions.new(resolve_provider: false)
end

folding_ranges_provider = if enabled_features.include?("foldingRanges")
Interface::FoldingRangeClientCapabilities.new(line_folding_only: true)
end
Expand All @@ -45,6 +49,7 @@ module RubyLsp
),
selection_range_provider: enabled_features.include?("selectionRanges"),
document_symbol_provider: document_symbol_provider,
document_link_provider: document_link_provider,
folding_range_provider: folding_ranges_provider,
semantic_tokens_provider: semantic_tokens_provider,
document_formatting_provider: enabled_features.include?("formatting"),
Expand Down Expand Up @@ -85,6 +90,12 @@ module RubyLsp
end
end

on("textDocument/documentLink") do |request|
store.cache_fetch(request.dig(:params, :textDocument, :uri), :document_link) do |document|
RubyLsp::Requests::DocumentLink.new(document).run
end
end

on("textDocument/foldingRange") do |request|
store.cache_fetch(request.dig(:params, :textDocument, :uri), :folding_ranges) do |document|
Requests::FoldingRanges.new(document).run
Expand Down
Binary file added misc/document_link.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions sorbet/rbi/shims/expectations_test_runner.rbi
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# typed: true

class ExpectationsTestRunner
sig { params(source: String).returns(Object) }
def run_expectations(source); end
end
14 changes: 14 additions & 0 deletions test/expectations/document_link/source_comment.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"result": [
{
"range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 50}},
"target": "file://BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#39",
"tooltip": "Jump to BUNDLER_PATH/gems/syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb#39"
},
{
"range": {"start": {"line": 4, "character": 0}, "end": {"line": 4, "character": 33}},
"target": "file://RUBY_ROOT/mutex_m.rb#1",
"tooltip": "Jump to RUBY_ROOT/mutex_m.rb#1"
}
]
}
7 changes: 7 additions & 0 deletions test/fixtures/source_comment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# source://syntax_tree-SYNTAX_TREE_VERSION/lib/syntax_tree.rb:39
def foo
end

# source://RUBY_ROOT/mutex_m.rb:1
def bar
end
16 changes: 16 additions & 0 deletions test/integration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
class IntegrationTest < Minitest::Test
FEATURE_TO_PROVIDER = {
"documentHighlights" => :documentHighlightProvider,
"documentLink" => :documentLinkProvider,
"documentSymbols" => :documentSymbolProvider,
"foldingRanges" => :foldingRangeProvider,
"selectionRanges" => :selectionRangeProvider,
Expand Down Expand Up @@ -83,6 +84,21 @@ def test_semantic_highlighting
assert_equal([0, 6, 3, 0, 0], response[:result][:data])
end

def test_document_link
initialize_lsp(["documentLink"])
open_file_with(<<~DOC)
# source://syntax_tree-#{Gem::Specification.find_by_name("syntax_tree").version}/lib/syntax_tree.rb:39
def foo
end
DOC

read_response("textDocument/publishDiagnostics")
assert_telemetry("textDocument/didOpen")

response = make_request("textDocument/documentLink", { textDocument: { uri: "file://#{__FILE__}" } })
assert_match(/syntax_tree/, response.dig(:result, 0, :target))
end

def test_formatting
initialize_lsp(["formatting"])
open_file_with("class Foo\nend")
Expand Down
32 changes: 32 additions & 0 deletions test/requests/document_link_expectations_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# typed: true
# frozen_string_literal: true

require "test_helper"
require "expectations/expectations_test_runner"

class DocumentLinkExpectationsTest < ExpectationsTestRunner
expectations_tests RubyLsp::Requests::DocumentLink, "document_link"

def assert_expectations(source, expected)
source = substitute_syntax_tree_version(source)
actual = T.cast(run_expectations(source), T::Array[LanguageServer::Protocol::Interface::DocumentLink])
assert_equal(map_expectations(json_expectations(expected)), JSON.parse(actual.to_json))
end

def map_expectations(expectations)
expectations.each do |expectation|
expectation["target"] = substitute(expectation["target"])
expectation["tooltip"] = substitute(expectation["tooltip"])
end
end

def substitute(original)
substitute_syntax_tree_version(original)
.sub("BUNDLER_PATH", Bundler.bundle_path.to_s)
.sub("RUBY_ROOT", RbConfig::CONFIG["rubylibdir"])
end

def substitute_syntax_tree_version(original)
original.sub("SYNTAX_TREE_VERSION", Gem::Specification.find_by_name("syntax_tree").version.to_s)
end
end

0 comments on commit 979a1fd

Please sign in to comment.