-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(completions): add backticks when needed in completions #14594
Conversation
This PR adds in the functionality to add in backticks when needed when giving back completions and also correctly returning completions when the user has already started typing a backtick. Fixes: scala#4406, scala#14006
val content = select.source.content() | ||
content.lift(select.nameSpan.start) match | ||
case Some(char) if char == '`' => | ||
content.slice(select.nameSpan.start, select.span.end).mkString |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if there is better way to get the full span of the actual name here, since when you attempt to complete Foo.`back
it gives you a Select(Ident(Bar),<error>)
with the error being a nospan. So in order to "cheat" this a bit and actually get what was passed in to use as a prefix I get the nameSpan.start
and the select.span.end
which gives me `back
which is exactly what I want here. Is there any better way to do this?
Also note, I was working on #12514 but it introduces other issues. I'm able to detect the |
I would even remove the backtick if it's no needed. I think it's a rare scenario that someone starts with a backtick if they actually don't need it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor comments, but it looks like a good way to go. Thanks!
| case `back-tick` | ||
| case `match` | ||
| | ||
| val x = Bar.`back${m1}""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about something that contains a space? E.g.
case `has space`
...
val x = Bar.`has ${m1}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this one is tricky. Mainly because when we trigger a completion here and we look at the path that is returned for Interactive.pathTo
, it's actually empty because it no longer thinks the position you are looking at is part of anything. I have no idea how to get around that. Bar.`has${m1}
works fine and even Bar.`has s${m1}
, but not with only the preceding space.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say it's a really niche case and I would just ignore it. It's highly unlikely that someone will want a completion on a space
@@ -88,13 +91,22 @@ object Completion { | |||
completionPrefix(selector :: Nil, pos) | |||
}.getOrElse("") | |||
|
|||
// We special case Select here because we want to determine if the name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about identifiers that are available directly in the current scope?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I'm actually working with figuring this out for Ident
as well. However one difference I noticed that I don't fully get is that the name
of a Select
is a nospan when it's an <error>
, however the name of an Ident
when it's an <error>
has a span. This makes getting the prefix different for each since here in the Select it's a bit hacky. How come one <error>
is a no span and the other isn't?
I checked out to this branch and played with it a bit in REPL. Having this code defined object Foo:
val foo = 1
val `foo-bar` = 2
val `bar` = 3
val `match` = 4
val `has space` = 5
val `foo-baz` = 6 I tried completions with prefixes: |
Even if we include backticks in |
This commit addresses the situation where you are trying to use a backtick when it's not needed. For example if you have: ```scala object Foo: case `bar` case foo Foo.`b ``` You still expect to get a completion bar being backticked. This also ensure that if you try ``Foo.`fo`` you also still get a completion.
So I've addressed most of these but still have one or two more to go.
This is actually the REPL trying to be smart and recognizing that what you have doesn't start with a
This one is now fixed, and really now anytime there is a backtick even when not needed it will still complete it and add the backticks.
This one is also now fixed
This one I addressed above in a comment as it's a whole other beast that might have to be fixed separately since the issue is totally different with
object Foo:
val `foo-bar` = 1
`fo${m1} What's returned here from
|
This removes the backticks when you are seeing all the available options, but still includes them in the actual value of the completion.
Done |
The scala 2 ticket is from 2013. scala/bug#6919 I'm unable to add heart emoji reaction more than once. |
The problem with Could you elaborate on the problem in REPL for |
Sure, you can see it illustrated in the |
I looks like the enclosing class is only returned, I think it's a case that no actual Ident is being created and it's something we might want to fix in the parser. We should return an Ident with an error name in that case. We could do it in a follow up though. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks awesome! I would say that any improvement in case of backticks is a good way forward, but I would not focus on this as it is a super niche use case with some of the edge cases.
Cool, glad to hear this 😄 . I spent a big chunk of the day today looking at the edge cases here and worked a bit with @bishabosha on it as well. The issue seems to be that when we encounter unclosed backticks in the parser depending on where they are, we create various different things. So there are two more cases locally I actually have a fix for, but it's more of hack than a fix so I really don't know how I feel about them. The situations are as follows: val `foo-bar` = 3
val x = `foo@@
type Foo = `Lis@@
When you are just doing val `foo-bar` = 3
`foo
So as you can see, it ends up adding a ton of special cases into the completion logic which really clogs it all up to capture all these situations. One thing we tried was to actually tag this with a All that to say, I think maybe this should be fixed in the parser and try to unify the way we handle unclosed backticks in a way that can easily be determined later on in a similar way. The fix that is here currently should take care of probably the majority of cases users will actually hit on, but these niche ones are difficult. Ha, I've been hitting more walls than expected with this 😅 |
@prolativ what do you say that we merge this as it's already a great improvement? We should but the remaining edge cases into an issue. |
We had a small discussion and it could be worth trying to make a new Token for an unfinished backticked identifier, which we can then make a valid tree for |
I'm all for this, and yea I can extract the remaining edgecases out into issues with details since I think the way forward is what @bishabosha recommends, but I'd rather not mix that with this pr. |
Ok. I started to dig a bit into the corner case with object Foo:
val foo = 1
val `foo-bar` = 2
Foo.`fo<TAB> myself but indeed this seems very tricky so let's get this merged first |
This PR adds in the functionality to add in backticks when needed when
giving back completions and also correctly returning completions when
the user has already started typing a backtick.
Hopefully this will fix the issues in the REPL and also rid the need of Ammonite and Metals to both have custom logic to handle this. Some of the work in here is based off stuff we have in Metals, which itself borrows from Ammonite. There are also parts of @tgodzik's work from #13369 in here and @alexarchambault's work in the early commits of #11794.
Fixes: #4406, #14006