-
Notifications
You must be signed in to change notification settings - Fork 327
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
Conversation
.orElse(focusedDocument.map(_.parent)) | ||
|
||
val newlyCreatedFile = | ||
askForKind |
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'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] = _ |
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.
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.
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.
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, |
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 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
.
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.
That should be in future enabled with the client capabilities, so after @ckipp01 's PR you can move it there.
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 even remove it from here, since we want to drop the properties.
docs/editors/new-editor.md
Outdated
@@ -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` |
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.
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.
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.
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 :)
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.
Let's use the recently merged way defining capabilities.
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.
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( |
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.
Let's not use the implicit conversions here, they are not neccessarily good for readibility. And maybe just rename to toShowMessageRequestParams
?
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.
done, changed to
private def toShowMessageRequestParams(
params: MetalsPickInputParams
): ShowMessageRequestParams
/** | ||
* An optional flag to include the description when filtering the picks. | ||
*/ | ||
matchOnDescription?: boolean; |
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.
Are the additional properties needed to match the ones in VSCode API?
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.
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.
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.
It should be fine, we can leave it as is. Unless they might cause issues for implementing this in other clients?
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 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( |
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.
Can we move it to a separate file?
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 just followed MetalsInputBoxParams
, defined in this file before.
Some(directory.getAsString()).map(new URI(_)) | ||
) | ||
case Seq(_: JsonNull) => | ||
newFilesProvider.createNewFileDialog(None) |
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.
Maybe use a named parameter for None
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.
done
@@ -106,6 +111,8 @@ object MetalsServerConfig { | |||
openFilesOnRenames = true, | |||
executeClientCommand = ExecuteClientCommandConfig.on, | |||
globSyntax = GlobSyntaxConfig.vscode, | |||
isInputBoxEnabled = true, |
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.
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] = _ |
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.
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 => |
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.
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")
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.
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.
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.
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]( |
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 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.
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.
Yeah, I also thought about OptionT
:D . Done
val result = Future { | ||
Files.createFile(path.toNIO).toUri() | ||
AbsolutePath( | ||
Files.createFile(path.toNIO) |
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.
You can use touch
from XtensionAbsolutePathBuffers
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.
touch
does not return the path, and although I already have the path, I like to reuse output of the function.
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.
Can we change touch to return the path?
)( | ||
implicit ec: ExecutionContext | ||
): Future[AbsolutePath] = { | ||
val path = directory.getOrElse(workspace).resolve(name + ".scala") |
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 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.
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.
it fails. Not really sure, it looks neat to ask for the name for the new class foo/Bar
to create class Bar
at foo/Bar.scala
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.
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, |
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.
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've added client properties, but server ones are still there, cause scalameta/metals-vscode#207 is not being pushed.
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.
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.
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.
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.
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, 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.
a055063
to
248ab2a
Compare
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.
Thanks for the profound review, @tgodzik . I've addressed your comments, please, check it out.
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.
Nice job writing the docs and implementing the endpoint! Just a small comment on the name of the endpoint.
docs/editors/new-editor.md
Outdated
@@ -541,6 +541,73 @@ export interface MetalsInputBoxResult { | |||
} | |||
``` | |||
|
|||
### `metals/pickInput` |
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.
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
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.
Yeah, I was not sure about the name, so if you think, this one's better, okay then. Renamed.
72f5636
to
2431896
Compare
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.
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 = |
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.
That's what I meant, I think they can just be move to the test class.
) | ||
} | ||
|
||
implicit class OptionFutureLift[A](state: Future[A]) { |
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.
There should already be XtensionScalaFuture
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.
moved
@@ -106,6 +111,8 @@ object MetalsServerConfig { | |||
openFilesOnRenames = true, | |||
executeClientCommand = ExecuteClientCommandConfig.on, | |||
globSyntax = GlobSyntaxConfig.vscode, | |||
isInputBoxEnabled = true, |
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 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") |
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.
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) |
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.
Can we change touch to return the path?
|
||
private def check( |
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 like the direction, but I thought of something similar to RenameLspSuite and other, where we would even invoke test
from here.
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.
done
What should I do with the client part scalameta/metals-vscode#183 ?
|
@tgodzik PR reworked,
Moved.
removed, but see previous comment.
done.
Nah, I just returned path there (and changed |
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.
LGTM! Thanks for addressing the comments!
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.
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" |
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.
Super nitpicky, but I just tested this, and is it possible to just add a :
in here?
s"Enter the name for the new $kind" | |
s"Enter the name for the new $kind:" |
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.
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.
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.
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.
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.
Also, this feature works great 🎉 !!
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.
thanks 😌
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.