Skip to content
This repository has been archived by the owner on Jun 1, 2023. It is now read-only.

Add workspace symbols #106

Merged
merged 12 commits into from
May 6, 2018
2 changes: 1 addition & 1 deletion spec/scry/response_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ module Scry
io = IO::Memory.new
response = Response.new(results)
response.write(io)
io.to_s.should eq(%(Content-Length: 245\r\n\r\n{"jsonrpc":"2.0","id":32,"result":{"capabilities":{"textDocumentSync":1,"documentFormattingProvider":true,"definitionProvider":true,"documentSymbolProvider":true,"completionProvider":{"resolveProvider":true,"triggerCharacters":[".","\\\"","/"]}}}}))
io.to_s.should eq(%(Content-Length: 276\r\n\r\n{"jsonrpc":"2.0","id":32,"result":{"capabilities":{"textDocumentSync":1,"documentFormattingProvider":true,"definitionProvider":true,"documentSymbolProvider":true,"workspaceSymbolProvider":true,"completionProvider":{"resolveProvider":true,"triggerCharacters":[".","\\\"","/"]}}}}))
end
end
end
51 changes: 44 additions & 7 deletions spec/scry/symbol_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,39 @@ module Scry
text_document = TextDocument.new("uri", ["class Test; end"])
processor = SymbolProcessor.new(text_document)
response = processor.run
result = response.result.as(Array(SymbolInformation)).try(&.first)
result = response.result.as(Array(SymbolInformation)).first
result.kind.is_a?(SymbolKind::Class).should be_true
end

it "returns Struct symbols as a Class" do
text_document = TextDocument.new("uri", ["struct Test; end"])
processor = SymbolProcessor.new(text_document)
response = processor.run
result = response.result.as(Array(SymbolInformation)).try(&.first)
result = response.result.as(Array(SymbolInformation)).first
result.kind.is_a?(SymbolKind::Class).should be_true
end

it "returns Module symbols" do
text_document = TextDocument.new("uri", ["module Test; end"])
processor = SymbolProcessor.new(text_document)
response = processor.run
result = response.result.as(Array(SymbolInformation)).try(&.first)
result = response.result.as(Array(SymbolInformation)).first
result.kind.is_a?(SymbolKind::Module).should be_true
end

it "returns Method symbols" do
text_document = TextDocument.new("uri", ["def test; end"])
processor = SymbolProcessor.new(text_document)
response = processor.run
result = response.result.as(Array(SymbolInformation)).try(&.first)
result = response.result.as(Array(SymbolInformation)).first
result.kind.is_a?(SymbolKind::Method).should be_true
end

it "returns instance vars as Variable symbols" do
text_document = TextDocument.new("uri", ["@bar = nil"])
processor = SymbolProcessor.new(text_document)
response = processor.run
result = response.result.as(Array(SymbolInformation)).try(&.first)
result = response.result.as(Array(SymbolInformation)).first
result.as(SymbolInformation).kind.is_a?(SymbolKind::Variable).should be_true
end

Expand Down Expand Up @@ -86,17 +86,54 @@ module Scry
text_document = TextDocument.new("uri", [%(HELLO = "world")])
processor = SymbolProcessor.new(text_document)
response = processor.run
result = response.result.as(Array(SymbolInformation)).try(&.first)
result = response.result.as(Array(SymbolInformation)).first
result.kind.is_a?(SymbolKind::Constant).should be_true
end

it "returns alias as Constant symbols" do
text_document = TextDocument.new("uri", [%(alias Hello = World)])
processor = SymbolProcessor.new(text_document)
response = processor.run
result = response.result.as(Array(SymbolInformation)).try(&.first)
result = response.result.as(Array(SymbolInformation)).first
result.kind.is_a?(SymbolKind::Constant).should be_true
end
end

describe "WorkspaceSymbols" do
it "return empty Symbols list if no query" do
processor = WorkspaceSymbolProcessor.new(0, ROOT_PATH, "")
response = processor.run
result = response.result.as(Array(SymbolInformation))
result.empty?.should be_true
end

it "return Symbols list with query match" do
processor = WorkspaceSymbolProcessor.new(0, ROOT_PATH, "salut")
response = processor.run
result = response.result.as(Array(SymbolInformation)).first
result.kind.is_a?(SymbolKind::Method).should be_true
end

it "return Symbols list with regex query match" do
processor = WorkspaceSymbolProcessor.new(0, ROOT_PATH, "sal*")
response = processor.run
result = response.result.as(Array(SymbolInformation)).first
result.kind.is_a?(SymbolKind::Method).should be_true
end

it "return stdlib Symbol with regex query match for File" do
processor = WorkspaceSymbolProcessor.new(0, ROOT_PATH, "Fil*")
response = processor.run
result = response.result.as(Array(SymbolInformation)).first
result.kind.is_a?(SymbolKind::Class).should be_true
end

it "return stdlib symbol with regex query match for initialize" do
processor = WorkspaceSymbolProcessor.new(0, ROOT_PATH, "initializ*")
response = processor.run
result = response.result.as(Array(SymbolInformation)).first
result.kind.is_a?(SymbolKind::Method).should be_true
end
end
end
end
12 changes: 12 additions & 0 deletions src/scry/context.cr
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ module Scry
end
end

private def dispatch_request(params : WorkspaceSymbolParams, msg)
case msg.method
when "workspace/symbol"
query = params.query
root_path = TextDocument.uri_to_filename(@workspace.root_uri)
workspace_symbol_processor = WorkspaceSymbolProcessor.new(msg.id, root_path, query)
response = workspace_symbol_processor.run
Log.logger.debug(response)
response
end
end

private def dispatch_request(params : CompletionItem, msg)
case msg.method
when "completionItem/resolve"
Expand Down
2 changes: 2 additions & 0 deletions src/scry/protocol/request_message.cr
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
require "./initialize_params"
require "./text_document_position_params"
require "./workspace_symbol_params"

module Scry
struct RequestMessage
alias RequestType = (TextDocumentPositionParams |
InitializeParams |
DocumentFormattingParams |
TextDocumentParams |
WorkspaceSymbolParams |
CompletionItem)?

JSON.mapping({
Expand Down
22 changes: 9 additions & 13 deletions src/scry/protocol/server_capabilities.cr
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,27 @@ module Scry
end
end

# Specify Sever capabilities supported,
# Currently Crystal supports:
# Specify supported sever capabilities:
#
# - Go to definition or Implementation (WIP)
# - Go to Implementation (WIP)
# - Diagnostics (WIP)
# - Formatting (WIP)
# - Symbols https://github.com/crystal-lang/crystal/blob/1f3e8b0e742b55c1feb5584dc932e87034365f48/samples/compiler/visitor_example.cr
# - Rename https://github.com/crystal-lang/crystal/blob/1f3e8b0e742b55c1feb5584dc932e87034365f48/samples/compiler/transformer_example.cr
# - Completion https://github.com/TechMagister/cracker
# - Hover
# - Code Actions
# - Signature Help
# - Range Formatting
# - Symbols (WIP)
# - Completion (WIP)
# - Hover (WIP)
# - Code Actions (WIP)
#
# Features not supported by Crystal yet:
#
# - CodeLens
# - Rename
# - Find References
#
# To enable more capabilities, See: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md
struct ServerCapabilities
JSON.mapping(
textDocumentSync: TextDocumentSyncKind,
documentFormattingProvider: Bool,
definitionProvider: Bool,
documentSymbolProvider: Bool,
workspaceSymbolProvider: Bool,
completionProvider: CompletionOptions
)

Expand All @@ -53,6 +48,7 @@ module Scry
@documentFormattingProvider = true
@definitionProvider = true
@documentSymbolProvider = true
@workspaceSymbolProvider = true
@completionProvider = CompletionOptions.new
end
end
Expand Down
9 changes: 9 additions & 0 deletions src/scry/protocol/workspace_symbol_params.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "json"

module Scry
struct WorkspaceSymbolParams
JSON.mapping({
query: String,
}, true)
end
end
44 changes: 44 additions & 0 deletions src/scry/symbol.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "compiler/crystal/syntax"
require "./protocol/workspace_symbol_params"

module Scry
class SymbolVisitor < Crystal::Visitor
Expand Down Expand Up @@ -113,6 +114,49 @@ module Scry
node.accept(visitor)

ResponseMessage.new(@text_document.id, visitor.symbols)
rescue
ResponseMessage.new(@text_document.id, [] of SymbolInformation)
end
end

class WorkspaceSymbolProcessor
@@crystal_path_symbols : Array(SymbolInformation)?
@query_regex : Regex

def initialize(@msg_id : Int32 | String, @root_path : String, @query : String)
@workspace_files = Dir.glob(File.join(root_path, "**", "*.cr"))
@query_regex = Regex.new(@query)
end

def run
symbols = [] of SymbolInformation
unless @query.empty?
symbols.concat self.class.search_symbols(@workspace_files, @query_regex)
symbols.concat self.class.crystal_path_symbols.select(&.name.match(@query_regex))
end
ResponseMessage.new(@msg_id, symbols)
end

def self.crystal_path_symbols
@@crystal_path_symbols ||= begin
crystal_path_files = Dir.glob(File.join(Scry.default_crystal_path, "**", "*.cr"))
search_symbols(crystal_path_files, Regex.new(".*"))
end
end

def self.search_symbols(files, query_regex)
symbols = [] of SymbolInformation
files.each do |file|
visitor = SymbolVisitor.new("file://#{file}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there is anyway we can reuse the SymbolProcessor here since the code is so similar? Although it would probably need to be decoupled from the ResponseMessage, maybe we can refactor that later.

parser = Crystal::Parser.new(File.read(file))
parser.filename = file
node = parser.parse
node.accept(visitor)
symbols.concat visitor.symbols.select(&.name.match(query_regex))
rescue
next
end
symbols
end
end
end