-
Notifications
You must be signed in to change notification settings - Fork 354
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 textEdit/codeAction #154
Conversation
First time scalafix fails the build 😄 --- /home/travis/build/scalameta/language-server/languageserver/src/main/scala/langserver/messages/Commands.scala
+++ <expected fix from ExplicitResultTypes+RemoveUnusedImports+Disable>
@@ -5,6 +5,7 @@
import langserver.types._
import langserver.utils.JsonRpcUtils
import play.api.libs.json.OFormat
+import langserver.messages.ApplyWorkspaceEditParams
sealed trait Message
sealed trait ServerCommand extends Message
@@ -229,7 +230,7 @@
case class WorkspaceSymbolRequest(params: WorkspaceSymbolParams) extends ServerCommand
case class ApplyWorkspaceEditParams(label: Option[String], edit: WorkspaceEdit)
object ApplyWorkspaceEditParams {
- implicit val format = Json.format[ApplyWorkspaceEditParams]
+ implicit val format: OFormat[ApplyWorkspaceEditParams] = Json.format[ApplyWorkspaceEditParams]
} |
CodeActionResult(Nil) | ||
val removeUnusedImports = request.params.context.diagnostics.collectFirst { | ||
case Diagnostic(_, _, _, Some("scalac"), "Unused import") => | ||
// This command doesn't seem to do much |
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.
This comment confuses me.
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's a leftover comment before I made it remove unused imports, comment removed
f6653a5
to
795e27f
Compare
case Some(document) => | ||
removeUnused( | ||
uri, | ||
schema.Database(document :: Nil).toDb(None).documents.head |
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.
This reads to me like it is creating a single document database just to convert it to a different database format to then extract the single document provided initially? Is this correct? If so a method to name the purpose would be helpful, or just an extension method, toOtherDocumentType.
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, while this is not new code introduced here schema.Database(...).toDb... is also confusing. It reads as “Create a db then convert it to a db”.
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 moved this to an extension method.
The API around scala.meta.Database / schema.Database conversions is quite awkward, we used to have fine grained conversions per type Document/Message/Synthetic but now it's all in a big fat method only to convert scala.meta.Database. language-server has been a great test on a lot of the scalameta APIs, I would love to incorporate some of our workarounds here into the core API
// Copy-pasta from scalafix because all of these methods are private. | ||
// We should expose a package private API to get a list of token patches from | ||
// a Patch. | ||
// TODO(olafur): Figure out how to expose a minimal public API in scalafix.Patch |
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 think we can actually remove this with the next scalafix release, Guillaume already refactored this API to support --diff and // scalafix:off
for rewrites
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 a couple of questions but overall it looks really good.
In case an exception happens on server startup then the server exited silently.
Users can put the cursor or hover over an unused import to see a lightbulb appear. If the user clicks on the lightbulb, a dialogue appears with options to run commands like "Remove unused import". This commit turned out to be quite tricky since I wasn't sure how to submit a request from the server to the client for workspace/applyEdit. It turned out to be quite dead simple, just send a JsonRpcRequestMessage and discard the response.
Thank you for the review @ShaneDelmore ! |
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.
@@ -58,6 +59,13 @@ abstract class Connection(inStream: InputStream, outStream: OutputStream)(implic | |||
} | |||
} | |||
|
|||
var i = new AtomicInteger() |
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.
Should it be private?
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.
Yes! And it can also be a val
@@ -14,6 +16,10 @@ class CompilerSuite extends MegaSuite { | |||
Nil | |||
) | |||
|
|||
def toDocument(name: String, code: String): Document = { | |||
InteractiveSemanticdb.toDocument(compiler, code, name, 10000) |
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 is this number?
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.
/**
@param timeout max number of milliseconds to allow the presentation compiler
*/
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 have some WIP experiments to use monix Task to avoid ad-hoc timeouts and support proper cancellation for presentation compiler requests
@@ -10,6 +10,7 @@ case object WorkspaceCommand extends Enum[WorkspaceCommand] { | |||
|
|||
case object ClearIndexCache extends WorkspaceCommand | |||
case object ResetPresentationCompiler extends WorkspaceCommand | |||
case object ScalafixUnusedImports extends WorkspaceCommand |
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 be possible (in the future) to adapt Scalafix rewrites to code actions so that it won't be necessary to define a WorkspaceCommand
for each type of rewrite?
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.
Scalafix absolutely, scalafix can run custom rules from configuration in .scalafix.conf, we can probably embed that config as command arguments to get away with only a single Scalafix
WorkspaceCommand.
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 review!
case Some(document) => | ||
removeUnused( | ||
uri, | ||
schema.Database(document :: Nil).toDb(None).documents.head |
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 moved this to an extension method.
The API around scala.meta.Database / schema.Database conversions is quite awkward, we used to have fine grained conversions per type Document/Message/Synthetic but now it's all in a big fat method only to convert scala.meta.Database. language-server has been a great test on a lot of the scalameta APIs, I would love to incorporate some of our workarounds here into the core API
// Copy-pasta from scalafix because all of these methods are private. | ||
// We should expose a package private API to get a list of token patches from | ||
// a Patch. | ||
// TODO(olafur): Figure out how to expose a minimal public API in scalafix.Patch |
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 we can actually remove this with the next scalafix release, Guillaume already refactored this API to support --diff and // scalafix:off
for rewrites
@@ -58,6 +59,13 @@ abstract class Connection(inStream: InputStream, outStream: OutputStream)(implic | |||
} | |||
} | |||
|
|||
var i = new AtomicInteger() |
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.
Yes! And it can also be a val
@@ -10,6 +10,7 @@ case object WorkspaceCommand extends Enum[WorkspaceCommand] { | |||
|
|||
case object ClearIndexCache extends WorkspaceCommand | |||
case object ResetPresentationCompiler extends WorkspaceCommand | |||
case object ScalafixUnusedImports extends WorkspaceCommand |
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.
Scalafix absolutely, scalafix can run custom rules from configuration in .scalafix.conf, we can probably embed that config as command arguments to get away with only a single Scalafix
WorkspaceCommand.
@@ -14,6 +16,10 @@ class CompilerSuite extends MegaSuite { | |||
Nil | |||
) | |||
|
|||
def toDocument(name: String, code: String): Document = { | |||
InteractiveSemanticdb.toDocument(compiler, code, name, 10000) |
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 have some WIP experiments to use monix Task to avoid ad-hoc timeouts and support proper cancellation for presentation compiler requests
@laughedelic PS, that gif from atom looks amazing 😍 |
This is intentional, we could make scalafix remove only a single unused import but I'm not sure how useful that would be. In IntelliJ there is only a single "Organize imports" which both removes unused imports and sorts/groups them. To remove a single imports it's easier to just remove it manually, IMO |
This PR implements a codeAction to remove unused imports with scalafix. The patch produced by scalafix is converted to precise
TextEdit
per changed token, instead of sending a single TextEdit to replace the whole file as we do with scalafmt.codeActions appear as lightbulbs above the cursor and when the lightbulb is clicked a dialogue opens with a list of commands to run (not shown in gif).
All of
ScalafixPatchEnrichments
is copy pasted from the scalafix sources, with the next release of scalafix this file will no longer be necessary since in master we have an API to getList[TokenPatch]
from aPatch