Skip to content
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

Implement textDocument/signatureHelp #51

Merged
merged 13 commits into from
Nov 24, 2017

Conversation

olafurpg
Copy link
Member

This feature is only triggered when opening a parentheses at a function
call-site. The box disappears as soon as you write the first parameter.
We reuse the completion mechanism to find the method symbol and extract
the parameter lists.

screen shot 2017-11-22 at 21 49 07

This feature is only triggered when opening a parentheses at a function
call-site. The box disappears as soon as you write the first parameter.
We reuse the completion mechanism to find the method symbol and extract
the parameter lists.
@ShaneDelmore
Copy link
Collaborator

I was unable to get this to work. I tried duplicating your success step exactly but when I type Predef.assert(
then pause I see no dialog, only a message recorded in the output dialog of vscode

[Error - 3:29:22 PM] Request completionItem/resolve failed.
  Message: Invalid params
  Code: -32602 
[object Object]

tailing metaserver.log shows me a request was made, but I don't see any response for it. Not sure if I am looking in the correct places or not.

15:38:48.997 DEBUG l.core.Connection - Received {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/sdelmore/workspace/language-server/test-workspace/a/src/test/scala/example/UserTest.scala","version":28},"contentChanges":[{"text":"package example\n\nclass UserTest {\n  val user = a.User.apply(name = \"\", 1)\n  user.copy(age = 2)\n  List\n    .apply(1, 2)\n    .map(x => x.+(2))\n  scala.runtime.CharRef.create('a')\n  Predef.assert()\n}\n"}]}}

@laughedelic
Copy link
Member

laughedelic commented Nov 23, 2017

I just updated my Atom plugin to try this out. And it works!

screen shot 2017-11-23 at 05 00 48

Although I'm not sure it's supposed to look like this.. 🤔


tailing metaserver.log shows me a request was made, but I don't see any response for it. Not sure if I am looking in the correct places or not.

@ShaneDelmore you see didChange notification, but what you should look for instead is something like this:

DEBUG l.core.Connection - Received {"jsonrpc":"2.0","id":40,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"file:///Users/laughedelic/dev/laughedelic/language-server/test-workspace/a/src/main/scala/example/User.scala"},"position":{"line":7,"character":13}}}

and response:

INFO  s.m.l.Compiler - Signature help at /Users/laughedelic/dev/laughedelic/language-server/test-workspace/a/src/main/scala/example/User.scala:7:13
DEBUG l.core.MessageWriter - payload: {"jsonrpc":"2.0","result":{"isIncomplete":false,"items":[]},"id":39}
DEBUG l.core.MessageWriter - payload: {"jsonrpc":"2.0","result":{"signatures":[{"label":"apply","documentation":"(name: String, age: Int)a.User","parameters":[{"label":"name","documentation":"name: String"},{"label":"age","documentation":"age: Int"}]}]},"id":40}
  • check that the VS Code extension refers to the right version of the server, that you published locally (now you have to set it explicitly (i.e. fe14433))
  • check that you opened test-workspace/a/. I opened test-workspace/ at first and then things were weird, some stuff worked, other not (probably it's Atom specific).

@laughedelic
Copy link
Member

laughedelic commented Nov 23, 2017

Here's what I got with some little changes (laughedelic@0749371). I think documentation field is only for the scaladoc (which we don't have yet, right?), and label is directly what will be shown in the interface:

screen shot 2017-11-23 at 05 38 21

(notice that the first parameter is highlighted in bold)

Probably you guys don't care much for Atom, but I think it's useful to see how different clients implement these things to stay unbiased. Here's atom-ide-ui docs for this thing. Some relevant implementation details:

Once activated, all signature help providers (matching the current grammar scope) will continuously be queried for signatures when the cursor location changes, regardless of the value of triggerCharacters.

@laughedelic
Copy link
Member

And here is how it looks in VSCode:

screen shot 2017-11-23 at 05 55 23

which is quite the same as in Atom ☺️ except it let's you to check the second alternative (I guess in atom-ide-ui it's just not implemented yet)

@gabro
Copy link
Member

gabro commented Nov 23, 2017

Nice! does it highlight the second parameter when you get to it?

@laughedelic
Copy link
Member

No, but it's just not implemented yet as far as I understand. I didn't read the code in details yet, so I'm not sure how exactly it works.

@laughedelic
Copy link
Member

laughedelic commented Nov 23, 2017

Moreover, something strange is happening when you start typing:

2017-11-23 06 12 13

P.S. I just discovered that you can switch those alternative signatures with / keys or ^P/^N. Cool! 💥

@olafurpg
Copy link
Member Author

@laughedelic that weird behavior is due to the fact that signature helper uses code completions to get the symbols of the methods, it works fine as long as you only invoke it on the trigger character (We need to find a more creative way to handle signature help while you fill out the argument list.

@olafurpg
Copy link
Member Author

check that the VS Code extension refers to the right version of the server, that you published locally (now you have to set it explicitly (i.e. fe14433))

This will grow tiring very quickly. Is there any chance to use a stable -SNAPSHOT version number outside of CI?

laughedelic and others added 2 commits November 23, 2017 09:34
It grows very tiring to manually update extension.js on every new
commit.
Previously we blindly used the offset provided by the client even if
that offset didn't point to a trigger character. Now we locate
the relevant open paren.
@olafurpg
Copy link
Member Author

@laughedelic I've fixed the weird behavior for basic cases but to make it handle multiple nested arguments we'll need a slightly more sophisticated solution explained in #52. Now it behaves like this

signaturehelp

@laughedelic
Copy link
Member

This will grow tiring very quickly. Is there any chance to use a stable -SNAPSHOT version number outside of CI?

I agree. So did you solve it with a9c4cac?

I've fixed the weird behavior for basic cases

Looks good! 👍

Copy link
Contributor

@jvican jvican left a comment

Choose a reason for hiding this comment

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

:shipit:

// symbol of `bar` when we want to match against `foo`.
// Related https://github.com/scalameta/language-server/issues/52
val lastParenOffset =
position.source.content.lastIndexOf('(', position.point)
Copy link
Member

Choose a reason for hiding this comment

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

This works outside of parentheses too 😄

screen shot 2017-11-23 at 16 39 20

}
}
SignatureInformation(
label = s"${sym.nameString}${sym.info.toLongString}",
Copy link
Member

Choose a reason for hiding this comment

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

I combined this just from what was already around. But is there a better way to get separately parameters list and return type?

screen shot 2017-11-23 at 13 41 40

.completionsAt(lastParenPosition)
.matchingResults()
.distinct
if member.sym.isMethod
Copy link
Member

Choose a reason for hiding this comment

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

What about constructors?

2017-11-23 16 36 22

Previously, we found the first open paren but now we keep a stack of how
many open parens are in between.
This change makes it possible to return the correct active parameter,
as well as filtering out overloads that have too few parameters.
This also means we no longer return bogus results when outside of
an argument list.
@olafurpg
Copy link
Member Author

I think I got it working reasonably well now, it tracks the active argument and doesn't return results anymore if you're outside of an argument list! The activeParameter detection is character based so it will count commas inside string literals so that foo(", ", <caret> will say the active argument is 2 when it should be 1. We could use the tokenizer to fix this case, but then we'll have to deal with other problems like unclosed string literals. I think we can leave experiments with that for a future PR!

nov-23-2017 21-23-21

@olafurpg
Copy link
Member Author

What about constructors?

@laughedelic great question! I'll take a look, we can try to fallback to the apply member if the symbol we found is not a method

@olafurpg
Copy link
Member Author

That User(<caret> example @laughedelic crashes 😂

java.lang.StringIndexOutOfBoundsException: String index out of range: -2
  java.lang.String.<init>(String.java:196)
  scala.tools.nsc.interactive.Global.typeCompletions$1(Global.scala:1217)

This commit prevents a StringIndexOutOfBounds by triggering a mutative call to
`.typedTreeAt` before calling `.completionsAt`. If the completions
return no results, which is what happens for `.apply`, we fallback to
the symbol of `typedTreeAt`.
@olafurpg
Copy link
Member Author

I got .apply working too 💃
screen shot 2017-11-23 at 22 07 20

Previously, didn't use the open paren offset to find the fallback
`.apply` symbol causing a NPE since `.typedTreeAt` returns null
on error.
Previously we only returned the primary constructor, which is a bit
annoying esp. when dealing with java apis.
@olafurpg
Copy link
Member Author

Lastly, secondary constructors also get some love ❤️

screen shot 2017-11-23 at 22 29 49

Ok, I think this PR is ready to go!

@gabro
Copy link
Member

gabro commented Nov 23, 2017

Very nice job! These are super useful when paired with autocompletion! A thing I can see improving (for future PRs) is to pretty print the signature. For instance adding a colon between the param list and the result typ

trait OverloadHack2; implicit object OverloadHack2 extends OverloadHack2
trait OverloadHack3; implicit object OverloadHack3 extends OverloadHack3
trait OverloadHack4; implicit object OverloadHack4 extends OverloadHack4
trait OverloadHack5; implicit object OverloadHack5 extends OverloadHack5
Copy link
Member

Choose a reason for hiding this comment

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

What's this for? If it's for disambiguating the erased signature, can you maybe use DummyImplicit on just one of the targeted signature below?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is copied from the scalameta test suite where there are 6 targeted overloads. It seems we don't need any overloads here so we can just remove this.

@laughedelic
Copy link
Member

laughedelic commented Nov 24, 2017

It works now with User, but I can't make it work with any constructor from the standard lib, like List(), or sys.process.Process().

Also, this looks strange:
screen shot 2017-11-24 at 02 24 04
screen shot 2017-11-24 at 02 24 23

(besides different typesetting and no syntax highlighting, which is Atom's fault)

It seems that the first parameter list is missing (and brackets around type parameters too). And by the way, what about multiple parameter lists?

It seems it doesn't matter if we force compilation or not the exception
still occurs. Best to catch it for now.
Previously, signature help didn't work for varargs or applications with
explicit type parameters. This commit generalizes the open/close trick
to work for both brackets and parens, this opens up the possibility
to show signature help for type parameters too in the future.

Varargs are also special cased since the argument count does not match
the length of the method's parameter list.
@olafurpg
Copy link
Member Author

@laughedelic I got List(<<1>> working, that one was tricky since there is both an inferred .apply and an inferred type parameter Int. I also added special handling for varargs so that the signature help doesn't disappear on the trailing list of arguments.

screen shot 2017-11-24 at 09 21 01
screen shot 2017-11-24 at 09 31 25

Unfortunately, I couldn't get Process to work, the presentation compiler doesn't have the type information. I suspect it's related to how the .apply methods a mixed into the Process object with a trait.

@olafurpg
Copy link
Member Author

@laughedelic I am not able to reproduce the missing argument list for map

screen shot 2017-11-24 at 09 45 18

@olafurpg
Copy link
Member Author

Gonna merge this since we have #55 using the same testing infrastructure

@olafurpg olafurpg merged commit c9a6adf into scalameta:master Nov 24, 2017
@olafurpg olafurpg deleted the signaturehelp branch November 24, 2017 08:46
@olafurpg olafurpg mentioned this pull request Nov 24, 2017
@gabro gabro mentioned this pull request Nov 24, 2017
@laughedelic laughedelic changed the title Implement textDocument/signatureHelper. Implement textDocument/signatureHelp Nov 24, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants