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

Add class and module name completion #100

Merged
merged 11 commits into from
May 30, 2018

Conversation

laginha87
Copy link
Contributor

Hi all
This adds autocomplete for class names and module names.
It doesn't show any documentation for it yet.
Will add a gif to demo.

@faustinoaq
Copy link
Member

image

Works like a charm! 🎉

@laginha87
Copy link
Contributor Author

Thanks for posting a gif example 👍

Copy link
Member

@faustinoaq faustinoaq left a comment

Choose a reason for hiding this comment

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

works!!! 🥇

it_completes("A", %w(Array Atomic ArgumentError AtExitHandlers))
it_completes("JSON::P", %w(ParseException Parser PullParser))
it_completes("JSON::Pa", %w(ParseException Parser))
it_completes("JSO", %w(JSON JSON::Any JSON::Builder JSON::Error JSON::Lexer JSON::Lexer::IOBased JSON::Lexer::StringBased JSON::ParseException JSON::Parser JSON::PullParser JSON::Token))
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a spec for completion after JSON:: ?
And after :: ? (if it works)

@@ -20,6 +20,10 @@ module Scry::Completion
end.select(&.name.starts_with? text).to_a
end

def type_match(text : String) : Array(String)
@db.keys.select(&.starts_with?(text)).reject(&.ends_with?(".class"))
Copy link
Contributor

Choose a reason for hiding this comment

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

select!
reject!

Copy link
Member

Choose a reason for hiding this comment

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

@bew Why? are mutable methods faster?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I think so, they don't allocate a new array each time.

@@ -53,39 +57,54 @@ module Scry::Completion

class Visitor < Crystal::Visitor
property classes
property class_name = ""
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's better to use nil to denote the absence of class_name, so the compiler can help you instead of doing empty checks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's easier with an empty string cause we only interpolate stuff, otherwise I'll have to use not_nil! calls

Copy link
Contributor

@bew bew May 8, 2018

Choose a reason for hiding this comment

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

I don't agree, I think it's always better to use the typing system for this kind of case.

For example in visit(node : Crystal::Def) where you use it, I'd use:

class_name = @class_name || raise CustomError.new

if node.name == "initialize"
  name = "new"
  method_receiver = "#{@class_queue.last}.class"
  return_type = @class_queue.last
  method_receiver = "#{class_name}.class"
  return_type = class_name
else
  name = node.name
  method_receiver = node.receiver ? "#{@class_queue.last}.class" : @class_queue.last
  method_receiver = node.receiver ? "#{class_name}.class" : class_name
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@class_name is assigned the value of the generate_classname which will be an empty string if the queues are empty.

Copy link
Member

Choose a reason for hiding this comment

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

property class_name : String?

and

if this_class_name = class_name
  # Use this_cass_name here...
end

WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Although I see no issues with initializing the class_name = ""

@bew Is this a bad practice or something? 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll simply say:

method_receiver = node.receiver ? "#{@class_name}.class" : @class_name

In this case, does it make sense (allowed) to have @class_name as an empty string?
I don't think so, so it must be made difficult/impossible to do.

Copy link
Contributor Author

@laginha87 laginha87 May 9, 2018

Choose a reason for hiding this comment

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

that won't happen cause if class_name is "" then class_queue is empty and in here we opt out

return false if @class_queue.empty? || node.visibility != Crystal::Visibility::Public

he idea of the classname is just to cache the output of generate_class_name which never returns nil, so initialising it as nil doesn't seem like the right way to handle it.

Copy link
Member

Choose a reason for hiding this comment

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

@laginha87 Oh, ok, I think class_name = "" is not that bad @bew WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah let's not block the PR for a tiny non-straight-forward thing like that, it's ok

@classes[node.name.to_s] = [] of MethodDBEntry
@class_queue << node.name.to_s
@class_queue << node.name
@class_name = generate_name(@module_queue + @class_queue)
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this still work if you have a module inside a class inside a module?

Why are you separating module & classes queues?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh, I didn't even know that could happen

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's seperate cause we only store methods for class, for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bew I tried to change this to use classes and modules in the same queue but started getting some tests failing not really sure why. I cut a new branch to add this and module functions as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

can you create a new issue to track and not forget this? It's quite important
thanks

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, include what you tried and your results, it might help find the actual problem ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I got a pull request https://github.com/crystal-lang-tools/scry/pull/117/files
Should there be a matching issue ?

@@ -136,8 +136,11 @@ module Scry
context "classname completion" do
_kind_ = CompletionItemKind::Class
it_completes("A", %w(Array Atomic ArgumentError AtExitHandlers))
it_completes(" ::A", %w(Array Atomic ArgumentError AtExitHandlers))
it_completes(" ::", %w())
Copy link
Contributor

@bew bew May 9, 2018

Choose a reason for hiding this comment

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

why nothing? It should give every top level classes & constants

Copy link
Member

Choose a reason for hiding this comment

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

@bew I think listing all classes would be very slow 😅 , no?

By example on Workspace symbols, if query is empty, you get this:

screenshot_20180509_193607

Copy link
Contributor

@bew bew May 10, 2018

Choose a reason for hiding this comment

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

@faustinoaq you're showing symbols here, not completion.

Slow? I don't know.

So for now it only gives an empty list? Maybe that's ok

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I couldn't figure out why it didn't return anything :(

Copy link
Member

Choose a reason for hiding this comment

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

No problem, I think is ok 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

@laginha87 let's not block this pr as it's not so important, but please open an issue to keep track of :: completion, and fix it one day, or decide what we do about it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I actually wanna tackle doing a context aware comlpetion for this at some point .
for example:

module A
     ::

should complete with modules and classes inside A as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bew added #118

Copy link
Contributor

Choose a reason for hiding this comment

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

@laginha87 there is no need for contextual completion, :: completions will ALWAYS be the top level things, that's what it exists for, there is no contextual things going on for types that starts with ::

@faustinoaq
Copy link
Member

@crystal-lang-tools/scry Can we get another review here?

@laginha87
Copy link
Contributor Author

@bew, thanks for reviewing the code, can you approve this or is there something I missed?

Copy link
Contributor

@bew bew left a comment

Choose a reason for hiding this comment

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

Looks good overall 😃

a.", %w(method)
end
end

context "classname completion" do
Copy link
Contributor

Choose a reason for hiding this comment

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

Note: it can also complete module names (and structs), not only class names, maybe change the spec name?


context "classname completion" do
_kind_ = CompletionItemKind::Class
it_completes("A", %w(Array Atomic ArgumentError AtExitHandlers))
Copy link
Contributor

Choose a reason for hiding this comment

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

This means that this spec rely on external code... A change to the stdlib, an addition/removal of a class starting with A will break this spec...

There are other problems with how the specs currently works, like:

  • You can't add a description of what that spec is actually spec-ing
  • They rely on external code (see above)
  • They are not self-contained: by just reading the spec you don't directly have all the data to understand it (you have to always lookup other files, AND they are shared between many specs)

This should probably go to a new issue though. I'm just too tired right now ^^

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. The test will always break with changes to stdlib, we are testing the autocomplete on it I don't think it's something we should worry about.
  2. What other files do you have to check to understand the specs? My idea for this file was to have tests that almost did an end-to-end test on the completion features and not dependant on the actual implementation.

def find
target_header_index = @target.rindex(":")
@method_db.type_match(@target).map do |label|
label = label[(target_header_index + 1)..-1] if target_header_index
Copy link
Contributor

Choose a reason for hiding this comment

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

just a style comment: At first I didn't saw the if at the end of the line, and was wondering 'how the hell does that compile..."

I think that line is too 'complicated' / 'long' for a suffix if, and would suggest to make it a multi-line if.

label: label,
insert_text: label,
kind: CompletionItemKind::Class,
detail: label,
Copy link
Contributor

Choose a reason for hiding this comment

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

would it be better to have the full path (e.g: JSON::Any, not just Any) in details? (can someone test this, see how it goes?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you mean in the CompletionItem class ?

@@ -20,6 +20,10 @@ module Scry::Completion
end.select(&.name.starts_with? text).to_a
end

def type_match(text : String) : Array(String)
@db.keys.select!(&.starts_with?(text)).reject!(&.ends_with?(".class"))
Copy link
Contributor

Choose a reason for hiding this comment

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

@db.keys.select! { |k| k.starts_with?(text) && !k.ends_with?(".class") }

? (less allocations / simpler logic / ...)

@laginha87 laginha87 force-pushed the laginha87-add-classname-completion branch from 0d319ea to 8e91d84 Compare May 26, 2018 10:51
@laginha87
Copy link
Contributor Author

@bew thanks for the review 👍
I addressed most of the issues you pointed out.
What you brought up about the test is valid but think it should be taken care of in another PR.
Didn't understand what you meant by "would it be better to have the full path (e.g: JSON::Any, not just Any) in details? (can someone test this, see how it goes?)"
Can you take another look and maybe approve 😅?

@faustinoaq faustinoaq requested a review from bew May 29, 2018 12:18
@bew
Copy link
Contributor

bew commented May 30, 2018

Actually it's not important

@@ -1,6 +1,6 @@
require "./tree"

class A
class B
Copy link
Contributor

Choose a reason for hiding this comment

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

By "looking another file to understand the specs" I meant this file!

@faustinoaq faustinoaq merged commit 7ea71f9 into master May 30, 2018
@faustinoaq faustinoaq deleted the laginha87-add-classname-completion branch May 30, 2018 19:17
@faustinoaq
Copy link
Member

@laginha87 Thank you! 🎉

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