From 4fc0cfc94c0f87802a195c57e94162eb0d398634 Mon Sep 17 00:00:00 2001 From: Gabriele Petronella Date: Fri, 15 Dec 2017 10:39:44 +0000 Subject: [PATCH 1/3] Add workspace/executeCommand and clearIndexCache command --- build.sbt | 4 ++- .../langserver/core/LanguageServer.scala | 4 ++- .../scala/langserver/messages/Commands.scala | 18 ++++++++-- .../main/scala/langserver/types/types.scala | 6 ++++ .../scala/langserver/utils/JsonRpcUtils.scala | 8 ++++- .../ScalametaLanguageServer.scala | 34 ++++++++++++++++++- .../languageserver/WorkspaceCommand.scala | 15 ++++++++ vscode-extension/package.json | 4 +++ vscode-extension/src/extension.ts | 10 +++++- 9 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 metaserver/src/main/scala/scala/meta/languageserver/WorkspaceCommand.scala diff --git a/build.sbt b/build.sbt index ff6397d3fd2..c5fddceeb13 100644 --- a/build.sbt +++ b/build.sbt @@ -58,6 +58,7 @@ lazy val V = new { val scala212 = "2.12.4" val scalameta = "2.1.5" val scalafix = "0.5.7" + val enumeratum = "1.5.12" } lazy val noPublish = List( @@ -81,7 +82,7 @@ lazy val languageserver = project resolvers += Resolver.bintrayRepo("dhpcs", "maven"), libraryDependencies ++= Seq( "com.dhpcs" %% "scala-json-rpc" % "2.0.1", - "com.beachape" %% "enumeratum" % "1.5.12", + "com.beachape" %% "enumeratum" % V.enumeratum, "com.beachape" %% "enumeratum-play-json" % "1.5.12-2.6.0-M7", "com.typesafe.scala-logging" %% "scala-logging" % "3.7.2", "io.monix" %% "monix" % "2.3.0", @@ -116,6 +117,7 @@ lazy val metaserver = project "io.get-coursier" %% "coursier-cache" % coursier.util.Properties.version, "ch.epfl.scala" % "scalafix-cli" % V.scalafix cross CrossVersion.full, "org.scalameta" %% "semanticdb-scalac" % V.scalameta cross CrossVersion.full, + "com.beachape" %% "enumeratum" % V.enumeratum, "com.lihaoyi" %% "utest" % "0.6.0" % Test, "org.scalameta" %% "testkit" % V.scalameta % Test, ) diff --git a/languageserver/src/main/scala/langserver/core/LanguageServer.scala b/languageserver/src/main/scala/langserver/core/LanguageServer.scala index 3ebce3d04a8..502577bd999 100644 --- a/languageserver/src/main/scala/langserver/core/LanguageServer.scala +++ b/languageserver/src/main/scala/langserver/core/LanguageServer.scala @@ -23,6 +23,7 @@ class LanguageServer(inStream: InputStream, outStream: OutputStream)(implicit s: case ("textDocument/hover", request: TextDocumentHoverRequest) => hover(request) case ("textDocument/references", request: TextDocumentReferencesRequest) => references(request) case ("textDocument/signatureHelp", request: TextDocumentSignatureHelpRequest) => signatureHelp(request) + case ("workspace/executeCommand", request: WorkspaceExecuteCommandRequest) => executeCommand(request).map(_ => ExecuteCommandResult) case ("shutdown", _: Shutdown) => shutdown() case c => Task.raiseError(new IllegalArgumentException(s"Unknown command $c")) } @@ -68,6 +69,8 @@ class LanguageServer(inStream: InputStream, outStream: OutputStream)(implicit s: def references(request: TextDocumentReferencesRequest): Task[ReferencesResult] = Task.now(ReferencesResult(Nil)) def signatureHelp(request: TextDocumentSignatureHelpRequest): Task[SignatureHelpResult] = Task.now(SignatureHelpResult(Nil, None, None)) + // workspace + def executeCommand(request: WorkspaceExecuteCommandRequest): Task[Unit] = Task.now(()) def onOpenTextDocument(td: TextDocumentItem): Unit = { logger.debug(s"openTextDocument $td") @@ -88,5 +91,4 @@ class LanguageServer(inStream: InputStream, outStream: OutputStream)(implicit s: def onChangeWatchedFiles(changes: Seq[FileEvent]): Unit = logger.debug(s"changeWatchedFiles $changes") - } diff --git a/languageserver/src/main/scala/langserver/messages/Commands.scala b/languageserver/src/main/scala/langserver/messages/Commands.scala index e33d4ecfcd6..38e9206122c 100644 --- a/languageserver/src/main/scala/langserver/messages/Commands.scala +++ b/languageserver/src/main/scala/langserver/messages/Commands.scala @@ -106,7 +106,11 @@ case class ServerCapabilities( /** * The server provides rename support. */ - renameProvider: Boolean = false + renameProvider: Boolean = false, + /** + * The server provides execute command support. + */ + executeCommandProvider: ExecuteCommandOptions = ExecuteCommandOptions(Nil) ) object ServerCapabilities { @@ -133,6 +137,11 @@ object DocumentOnTypeFormattingOptions { implicit val format: Format[DocumentOnTypeFormattingOptions] = Json.format[DocumentOnTypeFormattingOptions] } +case class ExecuteCommandOptions(commands: Seq[String]) +object ExecuteCommandOptions { + implicit val format: Format[ExecuteCommandOptions] = Json.format[ExecuteCommandOptions] +} + case class CompletionList(isIncomplete: Boolean, items: Seq[CompletionItem]) extends ResultResponse object CompletionList { implicit val format = Json.format[CompletionList] @@ -183,7 +192,6 @@ case class ReferenceParams( ) case class DocumentSymbolParams(textDocument: TextDocumentIdentifier) extends ServerCommand - case class TextDocumentSignatureHelpRequest(params: TextDocumentPositionParams) extends ServerCommand case class TextDocumentCompletionRequest(params: TextDocumentPositionParams) extends ServerCommand case class TextDocumentDefinitionRequest(params: TextDocumentPositionParams) extends ServerCommand @@ -191,6 +199,7 @@ case class TextDocumentReferencesRequest(params: ReferenceParams) extends Server case class TextDocumentDocumentHighlightRequest(params: TextDocumentPositionParams) extends ServerCommand case class TextDocumentHoverRequest(params: TextDocumentPositionParams) extends ServerCommand case class TextDocumentFormattingRequest(params: DocumentFormattingParams) extends ServerCommand +case class WorkspaceExecuteCommandRequest(params: WorkspaceExecuteCommandParams) extends ServerCommand case class Hover(contents: Seq[MarkedString], range: Option[Range]) extends ResultResponse object Hover { @@ -212,7 +221,8 @@ object ServerCommand extends CommandCompanion[ServerCommand] { "textDocument/documentHighlight" -> valueFormat(TextDocumentDocumentHighlightRequest)(_.params), "textDocument/hover" -> valueFormat(TextDocumentHoverRequest)(_.params), "textDocument/documentSymbol" -> Json.format[DocumentSymbolParams], - "textDocument/formatting" -> valueFormat(TextDocumentFormattingRequest)(_.params) + "textDocument/formatting" -> valueFormat(TextDocumentFormattingRequest)(_.params), + "workspace/executeCommand" -> valueFormat(WorkspaceExecuteCommandRequest)(_.params) ) // NOTE: this is a workaround to read `shutdown` request which doesn't have parameters (scala-json-rpc requires parameters for all requests) @@ -294,6 +304,7 @@ case class DocumentFormattingResult(params: Seq[TextEdit]) extends ResultRespons case class SignatureHelpResult(signatures: Seq[SignatureInformation], activeSignature: Option[Int], activeParameter: Option[Int]) extends ResultResponse +case object ExecuteCommandResult extends ResultResponse object SignatureHelpResult { implicit val format = Json.format[SignatureHelpResult] } @@ -311,6 +322,7 @@ object ResultResponse extends ResponseCompanion[Any] { "textDocument/hover" -> Json.format[Hover], "textDocument/documentSymbol" -> valueFormat(DocumentSymbolResult)(_.params), "textDocument/formatting" -> valueFormat(DocumentFormattingResult)(_.params), + "workspace/executeCommand" -> emptyFormat[ExecuteCommandResult.type], "shutdown" -> ShutdownResult.format ) } diff --git a/languageserver/src/main/scala/langserver/types/types.scala b/languageserver/src/main/scala/langserver/types/types.scala index 999b3b1098a..d36a95e8e0f 100644 --- a/languageserver/src/main/scala/langserver/types/types.scala +++ b/languageserver/src/main/scala/langserver/types/types.scala @@ -317,6 +317,12 @@ object DocumentFormattingParams { implicit val format = Json.format[DocumentFormattingParams] } +case class WorkspaceExecuteCommandParams(command: String, params: Option[Seq[JsValue]]) +object WorkspaceExecuteCommandParams { + implicit val format = Json.format[WorkspaceExecuteCommandParams] +} + + /** * An event describing a file change. * diff --git a/languageserver/src/main/scala/langserver/utils/JsonRpcUtils.scala b/languageserver/src/main/scala/langserver/utils/JsonRpcUtils.scala index f0f1a6174dc..07c12faf5af 100644 --- a/languageserver/src/main/scala/langserver/utils/JsonRpcUtils.scala +++ b/languageserver/src/main/scala/langserver/utils/JsonRpcUtils.scala @@ -34,8 +34,14 @@ object JsonRpcUtils { * ) * }}} */ - def valueFormat[A, B: Format](apply: B => A)(unapply: A => B): Format[A] = new Format[A] { + def valueFormat[A, B: Format](apply: B => A)(unapply: A => B): Format[A] = new Format[A] { override def reads(json: JsValue) = Reads.of[B].reads(json).map(apply(_)) override def writes(o: A) = Writes.of[B].writes(unapply(o)) } + + def emptyFormat[A]: Format[A] = new Format[A] { + override def reads(json: JsValue) = ??? + override def writes(u: A) = JsObject.empty + } + } diff --git a/metaserver/src/main/scala/scala/meta/languageserver/ScalametaLanguageServer.scala b/metaserver/src/main/scala/scala/meta/languageserver/ScalametaLanguageServer.scala index 093d1466198..c25bd313374 100644 --- a/metaserver/src/main/scala/scala/meta/languageserver/ScalametaLanguageServer.scala +++ b/metaserver/src/main/scala/scala/meta/languageserver/ScalametaLanguageServer.scala @@ -3,7 +3,13 @@ package scala.meta.languageserver import java.io.InputStream import java.io.OutputStream import java.io.PrintStream +import java.io.IOException import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.SimpleFileVisitor +import java.nio.file.FileVisitResult +import java.nio.file.attribute.BasicFileAttributes import scala.concurrent.duration.FiniteDuration import scala.meta.languageserver.compiler.CompilerConfig import scala.meta.languageserver.compiler.Cursor @@ -36,6 +42,8 @@ import langserver.messages.TextDocumentFormattingRequest import langserver.messages.TextDocumentHoverRequest import langserver.messages.TextDocumentReferencesRequest import langserver.messages.TextDocumentSignatureHelpRequest +import langserver.messages.WorkspaceExecuteCommandRequest +import langserver.messages.ExecuteCommandOptions import langserver.types._ import monix.eval.Task import monix.execution.Cancelable @@ -135,6 +143,8 @@ class ScalametaLanguageServer( } } + private val clearIndexCacheCommand = "clearIndexCache" + override def initialize( request: InitializeParams ): Task[InitializeResult] = Task { @@ -158,7 +168,8 @@ class ScalametaLanguageServer( documentHighlightProvider = true, documentSymbolProvider = true, documentFormattingProvider = true, - hoverProvider = true + hoverProvider = true, + executeCommandProvider = ExecuteCommandOptions(WorkspaceCommand.values.map(_.entryName)) ) InitializeResult(capabilities) } @@ -289,6 +300,15 @@ class ScalametaLanguageServer( sourceChangeSubscriber.onNext(input) } + override def executeCommand(request: WorkspaceExecuteCommandRequest) = Task { + import WorkspaceCommand._ + WorkspaceCommand.withNameOption(request.params.command).map { + case ClearIndexCache => + logger.info("Clearing the index cache") + ScalametaLanguageServer.clearCacheDirectory() + }.getOrElse(logger.error(s"Unknown command ${request.params.command}")) + } + override def onChangeTextDocument( td: VersionedTextDocumentIdentifier, changes: Seq[TextDocumentContentChangeEvent] @@ -323,6 +343,18 @@ object ScalametaLanguageServer extends LazyLogging { path } + def clearCacheDirectory(): Unit = + Files.walkFileTree(cacheDirectory.toNIO, new SimpleFileVisitor[Path] { + override def visitFile(file: Path, attr: BasicFileAttributes): FileVisitResult = { + Files.delete(file) + FileVisitResult.CONTINUE + } + override def postVisitDirectory(dir: Path, exc: IOException): FileVisitResult = { + Files.delete(dir) + FileVisitResult.CONTINUE + } + }) + def compilerConfigStream(cwd: AbsolutePath)( implicit scheduler: Scheduler ): (Observer.Sync[AbsolutePath], Observable[CompilerConfig]) = { diff --git a/metaserver/src/main/scala/scala/meta/languageserver/WorkspaceCommand.scala b/metaserver/src/main/scala/scala/meta/languageserver/WorkspaceCommand.scala new file mode 100644 index 00000000000..da6df33ad6e --- /dev/null +++ b/metaserver/src/main/scala/scala/meta/languageserver/WorkspaceCommand.scala @@ -0,0 +1,15 @@ +package scala.meta.languageserver + +import enumeratum.Enum +import enumeratum.EnumEntry +import enumeratum.EnumEntry.Uncapitalised + +sealed trait WorkspaceCommand extends EnumEntry with Uncapitalised + +case object WorkspaceCommand extends Enum[WorkspaceCommand] { + + case object ClearIndexCache extends WorkspaceCommand + + val values = findValues + +} diff --git a/vscode-extension/package.json b/vscode-extension/package.json index c321e6cfefa..9c97d4402fc 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -18,6 +18,10 @@ "command": "scalameta.restartServer", "category": "Scalameta Language Server", "title": "Restart server" + }, { + "command": "scalameta.clearIndexCache", + "category": "Scalameta Language Server", + "title": "Clear index cache" }] }, "main": "./out/extension", diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts index 55fefa50b0e..cc0379036a1 100644 --- a/vscode-extension/src/extension.ts +++ b/vscode-extension/src/extension.ts @@ -7,7 +7,8 @@ import { LanguageClientOptions, ServerOptions, TransportKind, - RevealOutputChannelOn + RevealOutputChannelOn, + ExecuteCommandRequest } from 'vscode-languageclient'; import { Requirements } from './requirements'; import { exec } from 'child_process'; @@ -91,5 +92,12 @@ export async function activate(context: ExtensionContext) { } }); + client.onReady().then(() => { + const clearIndexCacheCommand = commands.registerCommand("scalameta.clearIndexCache", async () => { + return client.sendRequest(ExecuteCommandRequest.type, { command: "clearIndexCache" }); + }); + context.subscriptions.push(clearIndexCacheCommand); + }); + context.subscriptions.push(client.start(), restartServerCommand); } From e2a492df2e8d6d76f98da9c23c96f41209540519 Mon Sep 17 00:00:00 2001 From: Gabriele Petronella Date: Fri, 15 Dec 2017 11:20:02 +0000 Subject: [PATCH 2/3] Format --- .../ScalametaLanguageServer.scala | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/metaserver/src/main/scala/scala/meta/languageserver/ScalametaLanguageServer.scala b/metaserver/src/main/scala/scala/meta/languageserver/ScalametaLanguageServer.scala index c25bd313374..451931db054 100644 --- a/metaserver/src/main/scala/scala/meta/languageserver/ScalametaLanguageServer.scala +++ b/metaserver/src/main/scala/scala/meta/languageserver/ScalametaLanguageServer.scala @@ -169,7 +169,8 @@ class ScalametaLanguageServer( documentSymbolProvider = true, documentFormattingProvider = true, hoverProvider = true, - executeCommandProvider = ExecuteCommandOptions(WorkspaceCommand.values.map(_.entryName)) + executeCommandProvider = + ExecuteCommandOptions(WorkspaceCommand.values.map(_.entryName)) ) InitializeResult(capabilities) } @@ -302,11 +303,14 @@ class ScalametaLanguageServer( override def executeCommand(request: WorkspaceExecuteCommandRequest) = Task { import WorkspaceCommand._ - WorkspaceCommand.withNameOption(request.params.command).map { - case ClearIndexCache => - logger.info("Clearing the index cache") - ScalametaLanguageServer.clearCacheDirectory() - }.getOrElse(logger.error(s"Unknown command ${request.params.command}")) + WorkspaceCommand + .withNameOption(request.params.command) + .map { + case ClearIndexCache => + logger.info("Clearing the index cache") + ScalametaLanguageServer.clearCacheDirectory() + } + .getOrElse(logger.error(s"Unknown command ${request.params.command}")) } override def onChangeTextDocument( @@ -344,16 +348,25 @@ object ScalametaLanguageServer extends LazyLogging { } def clearCacheDirectory(): Unit = - Files.walkFileTree(cacheDirectory.toNIO, new SimpleFileVisitor[Path] { - override def visitFile(file: Path, attr: BasicFileAttributes): FileVisitResult = { - Files.delete(file) - FileVisitResult.CONTINUE - } - override def postVisitDirectory(dir: Path, exc: IOException): FileVisitResult = { - Files.delete(dir) - FileVisitResult.CONTINUE + Files.walkFileTree( + cacheDirectory.toNIO, + new SimpleFileVisitor[Path] { + override def visitFile( + file: Path, + attr: BasicFileAttributes + ): FileVisitResult = { + Files.delete(file) + FileVisitResult.CONTINUE + } + override def postVisitDirectory( + dir: Path, + exc: IOException + ): FileVisitResult = { + Files.delete(dir) + FileVisitResult.CONTINUE + } } - }) + ) def compilerConfigStream(cwd: AbsolutePath)( implicit scheduler: Scheduler From 867e5b2b0a081dfb481be2a14c9b7bffa47a2d98 Mon Sep 17 00:00:00 2001 From: Gabriele Petronella Date: Fri, 15 Dec 2017 11:25:06 +0000 Subject: [PATCH 3/3] s/params/arguments to match LSP spec --- languageserver/src/main/scala/langserver/types/types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languageserver/src/main/scala/langserver/types/types.scala b/languageserver/src/main/scala/langserver/types/types.scala index d36a95e8e0f..ef49c858e8a 100644 --- a/languageserver/src/main/scala/langserver/types/types.scala +++ b/languageserver/src/main/scala/langserver/types/types.scala @@ -317,7 +317,7 @@ object DocumentFormattingParams { implicit val format = Json.format[DocumentFormattingParams] } -case class WorkspaceExecuteCommandParams(command: String, params: Option[Seq[JsValue]]) +case class WorkspaceExecuteCommandParams(command: String, arguments: Option[Seq[JsValue]]) object WorkspaceExecuteCommandParams { implicit val format = Json.format[WorkspaceExecuteCommandParams] }