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

Add new LSP extension 'metals/pickInput' to implement the "Create Scala file" command #1447

Merged
merged 5 commits into from
Feb 24, 2020

Conversation

alekseiAlefirov
Copy link
Contributor

After new-scala-file command has been added to metals #1339 , client-side service was discussed scalameta/metals-vscode#183 , and it was decided to move and implemet quick-pick communication to server.

.orElse(focusedDocument.map(_.parent))

val newlyCreatedFile =
askForKind
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've made some kind of DSL here, to cover Future[Option[_]] handling (making this method more or readable). Future is for about remote requests, and Option reflect that the request can be cancelled (thus, nothing should be returned eventually).
Not sure, if it's good here.

@@ -71,6 +71,7 @@ final class TestingClient(workspace: AbsolutePath, buffers: Buffers)
_: ShowMessageRequestParams =>
None
}
var inputBoxHandler: MetalsInputBoxParams => Option[MetalsInputBoxResult] = _
Copy link
Contributor Author

Choose a reason for hiding this comment

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

while picking kind of file can be expressed via showMessageRequest, asking for name of the new file cannot, so I had to add this handler for testing purposes.

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 default implementation returning some random name? You can have var nameToPick = "Foo" and replace that if needed. You also have None as the default if it's an issue.

Otherwise, we will get a null pointer in case of some particular changes.

@@ -106,6 +111,8 @@ object MetalsServerConfig {
openFilesOnRenames = true,
executeClientCommand = ExecuteClientCommandConfig.on,
globSyntax = GlobSyntaxConfig.vscode,
isInputBoxEnabled = true,
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 had to change it as well, because entering name for the new file cannot be expressed via showMessageRequest. This resulted into a bit changed UI when formatting and no scalafmt version is set.
Also, I would expect for such configs to be set by client, but okay, I mimicked it with isPickInputEnabled.

Copy link
Contributor

Choose a reason for hiding this comment

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

That should be in future enabled with the client capabilities, so after @ckipp01 's PR you can move it there.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would even remove it from here, since we want to drop the properties.

@@ -134,6 +134,14 @@ Possible values:
Metals tries to fallback to `window/showMessageRequest` when possible.
- `on`: the `metals/inputBox` request is fully supported.

### `-Dmetals.pick-input`
Copy link
Member

Choose a reason for hiding this comment

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

Related to some of the work I started in here: #1414, is it possible to not introduce this as a property, but rather as a clientExperimentalCapabilities? I feel like this would fit well there.

Copy link
Contributor Author

@alekseiAlefirov alekseiAlefirov Feb 17, 2020

Choose a reason for hiding this comment

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

Agree, as I said in my previous comment, I would expect it to be a client config, rather then a server one. I've just repeated after input-box. If it's to be changed -- then this is to be changed 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.

Let's use the recently merged way defining capabilities.

Copy link
Contributor

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

The PR is really good! I added quite a few comments, but I really like direction you've taken and mostly I just mentioned minor stuff.

@@ -117,4 +129,13 @@ final class ConfiguredLanguageClient(
}
}

private implicit def metalsPickInputParams2ShowMessageRequestInputParams(
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not use the implicit conversions here, they are not neccessarily good for readibility. And maybe just rename to toShowMessageRequestParams?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done, changed to

private def toShowMessageRequestParams(
      params: MetalsPickInputParams
  ): ShowMessageRequestParams

/**
* An optional flag to include the description when filtering the picks.
*/
matchOnDescription?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

Are the additional properties needed to match the ones in VSCode API?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good question to discuss.
I've added them to match, yes, to be able to use the most of VSCode API.
But if we don't want them - we can remove them, they'll be initialized to default values on VSCode side.

Copy link
Contributor

Choose a reason for hiding this comment

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

It should be fine, we can leave it as is. Unless they might cause issues for implementing this in other clients?

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 won't be no problem, thanks to JS agility. Even now it got implicitly converted from Metals interface to VSCode one.

@@ -112,3 +123,33 @@ case class MetalsInputBoxResult(
@Nullable value: String = null,
@Nullable cancelled: java.lang.Boolean = null
)

case class MetalsPickInputParams(
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we move it to a separate file?

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 just followed MetalsInputBoxParams, defined in this file before.

Some(directory.getAsString()).map(new URI(_))
)
case Seq(_: JsonNull) =>
newFilesProvider.createNewFileDialog(None)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe use a named parameter for None

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -106,6 +111,8 @@ object MetalsServerConfig {
openFilesOnRenames = true,
executeClientCommand = ExecuteClientCommandConfig.on,
globSyntax = GlobSyntaxConfig.vscode,
isInputBoxEnabled = true,
Copy link
Contributor

Choose a reason for hiding this comment

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

That should be in future enabled with the client capabilities, so after @ckipp01 's PR you can move it there.

@@ -71,6 +71,7 @@ final class TestingClient(workspace: AbsolutePath, buffers: Buffers)
_: ShowMessageRequestParams =>
None
}
var inputBoxHandler: MetalsInputBoxParams => Option[MetalsInputBoxResult] = _
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 default implementation returning some random name? You can have var nameToPick = "Foo" and replace that if needed. You also have None as the default if it's an issue.

Otherwise, we will get a null pointer in case of some particular changes.

test("new-worksheet") {
cleanCompileCache("a")
RecursivelyDelete(workspace.resolve("a"))
Files.createDirectories(
workspace.resolve("a/src/main/scala/").toNIO
)
client.showMessageRequestHandler = { params =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe a utility closure:

def createMessageRequestHander(name : String) : MessageRequestParams => Option[MessageRequestResult] =
{ params =>
      if (NewScalaFile.isSelectTheKindOfFile(params)) {
        params.getActions().asScala.find(_.getTitle() == name)
      } else {
        None
      }
    }

then you can just use:
client.showMessageRequestHandler = createMessageRequestHander("worksheet")

Copy link
Contributor

Choose a reason for hiding this comment

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

Or even better create a check method:

def check(picked : String, expectedOutput : String, name : String = "Foo", shouldCheckForName : Boolean)

then you can just have:

check(
  "object",
 """| object Foo {
     |}
     |""".stripMargin

createMessageRequestHande might not be needed then.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not sure, if I got you correctly, but I've done something there - please, check.


implicit class DialogContinuation[A](state: Future[Option[A]]) {

def continueWith[B](
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks good, this is almost a MonadTransformer haha.

Maybe flatMapOption and mapOption ? This will be a bit more natural.

And these methods might be actually useful to add to MetalsEnrichments, I remember situations when we were combining Futures and Options.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I also thought about OptionT :D . Done

val result = Future {
Files.createFile(path.toNIO).toUri()
AbsolutePath(
Files.createFile(path.toNIO)
Copy link
Contributor

Choose a reason for hiding this comment

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

You can use touch from XtensionAbsolutePathBuffers

Copy link
Contributor Author

Choose a reason for hiding this comment

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

touch does not return the path, and although I already have the path, I like to reuse output of the function.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we change touch to return the path?

)(
implicit ec: ExecutionContext
): Future[AbsolutePath] = {
val path = directory.getOrElse(workspace).resolve(name + ".scala")
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if the name is a/b/c/A - I think we should create the directories there. Most likely createFile should also create directories? Package will then be added automatically which is neat.

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 fails. Not really sure, it looks neat to ask for the name for the new class foo/Bar to create class Barat foo/Bar.scala

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we implement it here? I think we just need to do getParent.mkdirs

@@ -106,6 +111,8 @@ object MetalsServerConfig {
openFilesOnRenames = true,
executeClientCommand = ExecuteClientCommandConfig.on,
globSyntax = GlobSyntaxConfig.vscode,
isInputBoxEnabled = true,
isPickInputEnabled = true,
Copy link
Member

Choose a reason for hiding this comment

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

As a follow-up to my previous comment, and similiar to what @tgodzik mentioned, if you rebase on master now, you'll see the pattern I followed in #1414. Moving forward for most cases, we shouldn't need to add new server properties, but rather list them under ClientExperimentalCapabilities.

Copy link
Contributor Author

@alekseiAlefirov alekseiAlefirov Feb 20, 2020

Choose a reason for hiding this comment

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

I've added client properties, but server ones are still there, cause scalameta/metals-vscode#207 is not being pushed.

Copy link
Member

Choose a reason for hiding this comment

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

But that's because those properties already exist and are relied upon via clients that are already using them. This is a brand new one that we are introducing, so I'm not sure I understand why we also need to support this with client properties since nothing is relying on it? We can just only support it via ClientExperimentalCapabilities, and introduce it in vscode via experimentl.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

isPickInputEnabled removed now, and capability added to the client in scalameta/metals-vscode#183. Though isInputBoxEnabled = true still in this place, I am not sure, if it should not be here now.

Copy link
Member

Choose a reason for hiding this comment

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

Yea, for now that's fine since that property already existed anyways. Once we release, and the the vs code pr gets merged, we can then remove the isInputBoxEnabled since then it will be set via experimental from vs code.

Copy link
Contributor Author

@alekseiAlefirov alekseiAlefirov left a comment

Choose a reason for hiding this comment

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

Thanks for the profound review, @tgodzik . I've addressed your comments, please, check it out.

@olafurpg olafurpg changed the title metals/pickInput method endpoint added, new-scala-file re-implemented on it Add new LSP extension 'metals/pickInput' to implement the "Create Scala file" command Feb 21, 2020
Copy link
Member

@olafurpg olafurpg left a comment

Choose a reason for hiding this comment

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

Nice job writing the docs and implementing the endpoint! Just a small comment on the name of the endpoint.

@@ -541,6 +541,73 @@ export interface MetalsInputBoxResult {
}
```

### `metals/pickInput`
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to name this metals/quickPick? In general I try to follow the same naming convention as in the VS Code APIs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I was not sure about the name, so if you think, this one's better, okay then. Renamed.

Copy link
Contributor

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

Couple of follow up comments, I think it's almost ready!

def selectTheKindOfFileMessage = "Select the kind of file to create"
def enterNameMessage(kind: String): String = s"Enter name for the new $kind"

def isSelectTheKindOfFile(params: ShowMessageRequestParams): Boolean =
Copy link
Contributor

Choose a reason for hiding this comment

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

That's what I meant, I think they can just be move to the test class.

)
}

implicit class OptionFutureLift[A](state: Future[A]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

There should already be XtensionScalaFuture

Copy link
Contributor Author

@alekseiAlefirov alekseiAlefirov Feb 21, 2020

Choose a reason for hiding this comment

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

moved

@@ -106,6 +111,8 @@ object MetalsServerConfig {
openFilesOnRenames = true,
executeClientCommand = ExecuteClientCommandConfig.on,
globSyntax = GlobSyntaxConfig.vscode,
isInputBoxEnabled = true,
Copy link
Contributor

Choose a reason for hiding this comment

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

I would even remove it from here, since we want to drop the properties.

)(
implicit ec: ExecutionContext
): Future[AbsolutePath] = {
val path = directory.getOrElse(workspace).resolve(name + ".scala")
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we implement it here? I think we just need to do getParent.mkdirs

val result = Future {
Files.createFile(path.toNIO).toUri()
AbsolutePath(
Files.createFile(path.toNIO)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we change touch to return the path?


private def check(
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the direction, but I thought of something similar to RenameLspSuite and other, where we would even invoke test from here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@alekseiAlefirov
Copy link
Contributor Author

@tgodzik

isInputBoxEnabled = true,

I would even remove it from here, since we want to drop the properties.

What should I do with the client part scalameta/metals-vscode#183 ?

@alekseiAlefirov
Copy link
Contributor Author

@tgodzik PR reworked,

That's what I meant, I think they can just be move to the test class.

Moved.

I would even remove it from here, since we want to drop the properties.

removed, but see previous comment.

Could we implement it here? I think we just need to do getParent.mkdirs

done.

Can we change touch to return the path?

Nah, I just returned path there (and changed Files.create to touch).

Copy link
Contributor

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

LGTM! Thanks for addressing the comments!

Copy link
Member

@olafurpg olafurpg left a comment

Choose a reason for hiding this comment

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

Great work @alekseiAlefirov ! 🎉 I'm super excited to start using this feature 😄

object NewScalaFile {
def selectTheKindOfFileMessage = "Select the kind of file to create"
def enterNameMessage(kind: String): String =
s"Enter the name for the new $kind"
Copy link
Member

Choose a reason for hiding this comment

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

Super nitpicky, but I just tested this, and is it possible to just add a : in here?

Suggested change
s"Enter the name for the new $kind"
s"Enter the name for the new $kind:"

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 seems not to be going look good here:
image

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's leave it as is, I don't think it adds that much visually and it's not really being used in UI anywhere.

Copy link
Member

Choose a reason for hiding this comment

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

Hmmm, yea I see what you're saying. That's tricky, because in coc-metals there isn't a nice break, so it seems like there should really be a :. No worries, I'll just add it in on the vim side.

Screenshot 2020-02-24 at 14 01 42

Copy link
Member

Choose a reason for hiding this comment

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

Also, this feature works great 🎉 !!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks 😌

@tgodzik tgodzik merged commit b897fe2 into scalameta:master Feb 24, 2020
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.

4 participants