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 "Create new symbol '$name'..." code action #1528

Merged
merged 7 commits into from
Mar 23, 2020

Conversation

gabro
Copy link
Member

@gabro gabro commented Mar 23, 2020

This PR is a proposal to add a "Create new..." code action which is a quickfix for a "missing symbol" diagnostic.

Previously we would propose to import a symbol (if available) in response to "missing symbol" errors. Now we also propose to create a new class/trait/object

Here's a demo:

2020-03-23 08 08 56

As you can see in the demo, this is very useful when sketching down a domain model, which requires creating a bunch of case classes in a top-down fashion.

What do you all think?

(This is currently based on top of #1525, but I'll rebase once it's merged)

def createNewFileDialog(directoryUri: Option[URI]): Future[Unit] = {
def createNewFileDialog(
directoryUri: Option[URI],
name: Option[String]
Copy link
Member Author

Choose a reason for hiding this comment

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

I've changed this to also accept a name, so that we can skip the name-picking dialog and just ask for a file kind.

This allows the code action to fully re-use the existing provider's flow, which I think is really nice


val codeActions = params.getContext().getDiagnostics().asScala.collect {
case d @ ScalacDiagnostic.SymbolNotFound(name)
if d.getRange().encloses(params.getRange()) =>
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 encloses and not overlapsWith, because I don't think it makes sense to show the "Create new..." action when the user has selected a range which contains multiple SymbolNotFound diagnostics.

Comment on lines 356 to 360
def applyCodeAction(codeAction: CodeAction, server: TestingServer): Unit = {
val edit = codeAction.getEdit()
val command = codeAction.getCommand()
if (edit != null) applyWorkspaceEdit(edit)
if (command != null) executeServerCommand(command, server)
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 now as per the LSP specification: if edit and command are both present, first edit is applied then command is executed.

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.

This is an awesome idea!

}

object CreateNewFile {
val title = "Create new..."
Copy link
Member

Choose a reason for hiding this comment

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

create new symbol 'NAME'?

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought about adding something like that, but I feel like "symbol" is not very user-friendly (it's very much compiler lingo).

Anything else would require listing out the options ("Create new trait 'NAME'", "Create new class 'NAME'"), but think it would become too heavy.

I feel like "Create new..." is clear when presented in context and it avoids the problem of listing out all the options.

Copy link
Member

@olafurpg olafurpg Mar 23, 2020

Choose a reason for hiding this comment

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

I agree "symbol" is not ideal but it's used in "rename symbol" and "go to symbol in file" in VS Code so I think it's OK to use it here.

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right, it's used around, at least in VSCode. I'll make the change

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!

image

Copy link
Member

Choose a reason for hiding this comment

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

Nice! I think it's a bit more self documenting what this option does with the name. Should we drop the ... suffix?

Copy link
Member Author

Choose a reason for hiding this comment

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

The ... is used to convey the fact that the action starts a flow and it doesn't take effect immediately.

I've seen this pattern used quite often, including in VSCode. See for instance

image

The commands ending with ... start a flow that requires further interaction to complete.

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.

Just some minor comments, but I think it's a great idea! Thanks @gabro !

s"""|$kind $name {
|
|$indent
Copy link
Member Author

Choose a reason for hiding this comment

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

My editor is set to strip out whitespaces on save, so I've been struggling with this.

I think it's much clearer this way, anyway.

Copy link
Member

Choose a reason for hiding this comment

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

I agree this is clearer. I also have my editor remove trailing whitespace on save, intellij has this enabled by default, so we should not rely on it in our tests

Copy link
Member

Choose a reason for hiding this comment

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

I also like this as I get a warning in vim about trailing whitespaces all the time ha.

@gabro gabro requested a review from tgodzik March 23, 2020 12:05
@gabro gabro changed the title "Create new..." code action Add "Create new symbol '$name'..." code action Mar 23, 2020
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!

Copy link
Member

@ckipp01 ckipp01 left a comment

Choose a reason for hiding this comment

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

👍 this is great LGTM

s"""|$kind $name {
|
|$indent
Copy link
Member

Choose a reason for hiding this comment

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

I also like this as I get a warning in vim about trailing whitespaces all the time ha.

@gabro
Copy link
Member Author

gabro commented Mar 23, 2020

The failed test on Windows looks relevant, but I think it's flaky (especially because it succeeded on Linux and macOS): probably a race condition that caused the diagnostic not to disappear on time.

2020-03-23T14:14:44.3224758Z [error] ==> X tests.codeactions.CreateNewSymbolLspSuite.trait  6.271s munit.FailException: D:\a\metals\metals\tests\unit\src\test\scala\tests\codeactions\CreateNewSymbolLspSuite.scala:30
2020-03-23T14:14:44.4127307Z [error] 29:
2020-03-23T14:14:44.4133840Z [error] 30:  checkNewSymbol(
2020-03-23T14:14:44.4134203Z [error] 31:    "trait",
2020-03-23T14:14:44.4134455Z [error] diff assertion failed
2020-03-23T14:14:44.4134698Z [error] => Obtained
2020-03-23T14:14:44.4134967Z [error]     """|a/src/main/scala/a/A.scala:3:43: error: not found: type Location
2020-03-23T14:14:44.4135562Z [error]        |case class School(name: String, location: Location)
2020-03-23T14:14:44.4135868Z [error]        |                                          ^^^^^^^^
2020-03-23T14:14:44.4136127Z [error]        |""".stripMargin
2020-03-23T14:14:44.4136585Z [error] => Diff (- obtained, + expected)
2020-03-23T14:14:44.4136910Z [error] -a/src/main/scala/a/A.scala:3:43: error: not found: type Location
2020-03-23T14:14:44.4137203Z [error] -case class School(name: String, location: Location)
2020-03-23T14:14:44.4137468Z [error] -                                          ^^^^^^^^
2020-03-23T14:14:44.4148767Z [error] +
2020-03-23T14:14:44.4149080Z [error]     at munit.Assertions.fail(Assertions.scala:134)

I'll restart and see whether it fixes itself

@kpbochenek
Copy link
Contributor

Do you think it would be possible to move cursor into final case class MyClass(<<here>>) ?

From your gif it seems like you want to be there anyway and I would like to be there too if I created a new class :) (not sure how hard it is though and if it is metals or more editor specific action?)

@gabro
Copy link
Member Author

gabro commented Mar 23, 2020

@kpbochenek that's absolutely what I would like, and I think it should be possible by working with the range here

new Location(path.toURI.toString(), new Range()): Object
).asJava

I would do it in a separate issue since it's quite independent from this PR.

@gabro
Copy link
Member Author

gabro commented Mar 23, 2020

Ok, now the CI is failing for an unrelated test (I've fixed the previous race condition, which was my fault)

2020-03-23T15:26:33.1840250Z [error] ==> X tests.HoverLspSuite.basic-rambo  2.375s munit.FailException: D:\a\metals\metals\tests\unit\src\test\scala\tests\HoverLspSuite.scala:35 Obtained empty output!
2020-03-23T15:26:33.1840824Z [error] 34:      )
2020-03-23T15:26:33.1841093Z [error] 35:      _ <- server.assertHover(
2020-03-23T15:26:33.1841345Z [error] 36:        "a/src/main/scala/a/Main.scala",
2020-03-23T15:26:33.1873104Z [error]     at munit.Assertions.fail(Assertions.scala:134)

Merging

@gabro gabro merged commit 06a669b into scalameta:master Mar 23, 2020
@gabro gabro deleted the new-file-action branch March 23, 2020 16:08
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.

5 participants