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 workspace/executeCommand and clearIndexCache command #131

Merged
merged 3 commits into from
Dec 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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",
Expand Down Expand Up @@ -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,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
Expand Down Expand Up @@ -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")
Expand All @@ -88,5 +91,4 @@ class LanguageServer(inStream: InputStream, outStream: OutputStream)(implicit s:
def onChangeWatchedFiles(changes: Seq[FileEvent]): Unit =
logger.debug(s"changeWatchedFiles $changes")


}
18 changes: 15 additions & 3 deletions languageserver/src/main/scala/langserver/messages/Commands.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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]
Expand Down Expand Up @@ -183,14 +192,14 @@ 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
case class TextDocumentReferencesRequest(params: ReferenceParams) extends ServerCommand
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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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]
}
Expand All @@ -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
)
}
6 changes: 6 additions & 0 deletions languageserver/src/main/scala/langserver/types/types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,12 @@ object DocumentFormattingParams {
implicit val format = Json.format[DocumentFormattingParams]
}

case class WorkspaceExecuteCommandParams(command: String, arguments: Option[Seq[JsValue]])
Copy link
Member Author

Choose a reason for hiding this comment

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

arguments is any[] in the LSP, so I figured JsValue is a good type is.

object WorkspaceExecuteCommandParams {
implicit val format = Json.format[WorkspaceExecuteCommandParams]
}


/**
* An event describing a file change.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -135,6 +143,8 @@ class ScalametaLanguageServer(
}
}

private val clearIndexCacheCommand = "clearIndexCache"

override def initialize(
request: InitializeParams
): Task[InitializeResult] = Task {
Expand All @@ -158,7 +168,9 @@ class ScalametaLanguageServer(
documentHighlightProvider = true,
documentSymbolProvider = true,
documentFormattingProvider = true,
hoverProvider = true
hoverProvider = true,
executeCommandProvider =
ExecuteCommandOptions(WorkspaceCommand.values.map(_.entryName))
)
InitializeResult(capabilities)
}
Expand Down Expand Up @@ -289,6 +301,18 @@ 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]
Expand Down Expand Up @@ -323,6 +347,27 @@ object ScalametaLanguageServer extends LazyLogging {
path
}

def clearCacheDirectory(): Unit =
Copy link
Member Author

Choose a reason for hiding this comment

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

if somebody knows a better way of recursively deleting a directory with Java NIO, please let me know!

Copy link
Member

Choose a reason for hiding this comment

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

This is the best way to do it IMO :) It should be verbose!

Copy link
Member

Choose a reason for hiding this comment

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

On a more serious note, this might merit inclusion in org.langmeta.io.FileIO where we have similar utils

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]) = {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
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 went for a lowerCamelCase convention here and thanks to Uncapitalised this is automatically handled by withNameOption and entryName


case object WorkspaceCommand extends Enum[WorkspaceCommand] {

case object ClearIndexCache extends WorkspaceCommand

val values = findValues

}
4 changes: 4 additions & 0 deletions vscode-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 9 additions & 1 deletion vscode-extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
LanguageClientOptions,
ServerOptions,
TransportKind,
RevealOutputChannelOn
RevealOutputChannelOn,
ExecuteCommandRequest
} from 'vscode-languageclient';
import { Requirements } from './requirements';
import { exec } from 'child_process';
Expand Down Expand Up @@ -91,5 +92,12 @@ export async function activate(context: ExtensionContext) {
}
});

client.onReady().then(() => {
const clearIndexCacheCommand = commands.registerCommand("scalameta.clearIndexCache", async () => {
Copy link
Member

Choose a reason for hiding this comment

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

Is this needed for every new command? I thought codeActions was a way for the server to list the commands it supports.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it is. codeAction is not very well integrated in the client library, as of now. I guess it may depend by the requirement for extensions to statically include commands in the manifest

return client.sendRequest(ExecuteCommandRequest.type, { command: "clearIndexCache" });
});
context.subscriptions.push(clearIndexCacheCommand);
});

context.subscriptions.push(client.start(), restartServerCommand);
}