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
Merged

Add workspace symbols #106

merged 12 commits into from
May 6, 2018

Conversation

faustinoaq
Copy link
Member

Fixes #102 🎉

screenshot_20180505_122258


class WorkspaceSymbolProcessor
def initialize(@msg_id : Int32 | String, @root_path : String, @query : String)
@all_files = Dir.glob("#{root_path}/**/*.cr")
Copy link
Contributor

Choose a reason for hiding this comment

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

Why doesn't this use the workspace, and find files based on prelude and requires?

Copy link
Member Author

Choose a reason for hiding this comment

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

and find files based on prelude and requires?

What do you mean? 😅 This will get all "workspace" files to analyze all "workspace" symbols

Copy link
Contributor

@bew bew May 5, 2018

Choose a reason for hiding this comment

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

Oh ok, so this symbol feature is just to search in your project (your own code), not everywhere (stdlib, other shards, etc) ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, just your code 😅

Although this should work on shards as well since lib is analyzed too.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok it's fine then. I think I'd even restrict to src, to not show implementation detail symbols from shards

Copy link
Member Author

Choose a reason for hiding this comment

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

@bew Well, I guess I like your idea of listing stdlib symbols, so, this feature will support listing symbols from:

  1. Your code
  2. Your shards
  3. Crystal stdlib

screenshot_20180505_181554

And works very fast!!! 🎉 and stdlib is memoized ✨

it "return Symbols list with query match" do
processor = WorkspaceSymbolProcessor.new(0, "#{ROOT_PATH}/src", "salut")
response = processor.run
result = response.result.as(Array(SymbolInformation)).try(&.first)
Copy link
Contributor

Choose a reason for hiding this comment

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

try is uselesw here as you can the object to a non-nilable type juste before.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done! 👍

end

it "return Symbols list with query match" do
processor = WorkspaceSymbolProcessor.new(0, "#{ROOT_PATH}/src", "salut")
Copy link
Contributor

Choose a reason for hiding this comment

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

Use regex here? Or add another spec for a regex query?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done! 👍

Copy link
Contributor

@bew bew May 6, 2018

Choose a reason for hiding this comment

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

A little Use File.join commit ? 😃

visitor = SymbolVisitor.new("file://#{file}")
parser = Crystal::Parser.new(File.read(file))
parser.filename = file
node = parser.parse
Copy link
Contributor

Choose a reason for hiding this comment

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

what if there is a syntax error in one of the file? I think the exception needs to be rescued and the file skipped.

Is it possible to send errors in addition to valid results? (say, here are the results for files X & Y, but there is an error with file Z)

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess I can handle the error, but the response should still be managed by diagnostic provider

I think I can return an empty array if syntax error is found

Copy link
Contributor

@bew bew May 5, 2018

Choose a reason for hiding this comment

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

Looking at https://microsoft.github.io/language-server-protocol/specification#workspace-symbols-request-leftwards_arrow_with_hook you should be able to return null and an error in case an exception happens during the workspace symbol request.

I think it could be possible to return a result & an error? but how the valid_result/error is handled would vastly depend on the language client implementation..

Copy link
Member Author

@faustinoaq faustinoaq May 5, 2018

Choose a reason for hiding this comment

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

Well, Actually we're managing that via response_error.cr, although I think for better user experience we should ignore parsing errors here since the diagnostics already do this work.

I don't want to see a popup saying "LSP server has an error" every time I try to list symbols on files with syntax errors 😅

@faustinoaq
Copy link
Member Author

screenshot_20180505_181554
screenshot_20180505_182214

stdlib symbols has been added as well 🎉

@workspace_files = Dir.glob(File.join(root_path, "**", "*.cr"))
if @@crystal_path_files.empty?
# Memoize crystal stdlib
@@crystal_path_files = search_symbols(Dir.glob(File.join(Scry.default_crystal_path, "**", "*.cr")), ".*")
Copy link
Contributor

Choose a reason for hiding this comment

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

rename to crystal_path_symbols as it stores symbols

Copy link
Member Author

Choose a reason for hiding this comment

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

Done 👍

symbols.concat visitor.symbols.select(&.name.match(Regex.new(@query)))
end
symbols.concat search_symbols(@workspace_files, @query)
symbols.concat @@crystal_path_files.select(&.name.match(Regex.new(@query)))
Copy link
Contributor

Choose a reason for hiding this comment

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

You could store the Regex object in @query in the initialize, instead of storing the String and re-create/re-compile the regex on each usage.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!

@bew
Copy link
Contributor

bew commented May 6, 2018

Also, why in you last screenshot some entries have non-absolute path even though it's not in the current workspace (e.g: pow_f64 or FileModule) ?

@faustinoaq
Copy link
Member Author

faustinoaq commented May 6, 2018

@bew Interesting I didn't realize of that 👍

I think SymbolInformation is too long and VSCode just hide it 😅

Although, If you test it, still works

end
ResponseMessage.new(@msg_id, symbols)
end

def search_symbols(files, query)
def search_symbols(files, query_regex)
Copy link
Contributor

@bew bew May 6, 2018

Choose a reason for hiding this comment

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

Not sure if it's a good idea, but you could have search_symbols as a class method (as it doesn't depend on any ivars), and use special getter for @@crystal_path_symbols:

@@crystal_path_symbols : Array(SymbolInformation)?

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

# and when you need crystal_path_symbols, you just call the getter, which will return
# `@@crystal_path_symbols` if it's not nil, and will search the symbols if it's nil.

def self.search_symbols(x, y)
end

This way you can remove the cvar init from the initiailizer and keep it clean 😃

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!

@query_regex = Regex.new(@query)
end

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

Choose a reason for hiding this comment

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

Weird I thought it was simpler to call..

Another way is WorkspaceSymbolProcessor.search_symbols or {{@type}}.search_symbols (as you like, self.class is fine too)

Copy link
Member Author

Choose a reason for hiding this comment

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

I think I prefer self.class 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

no prob'!

@faustinoaq faustinoaq requested a review from bew May 6, 2018 06:41
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.

@faustinoaq faustinoaq merged commit 81a7171 into master May 6, 2018
@faustinoaq faustinoaq deleted the fa/workspace-symbols branch May 6, 2018 19:41
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants