Skip to content

Commit

Permalink
Merge pull request #1447 from alekseiAlefirov/pick-input
Browse files Browse the repository at this point in the history
Add new LSP extension 'metals/pickInput' to implement the "Create Scala file" command
  • Loading branch information
tgodzik authored Feb 24, 2020
2 parents 60af682 + 948961c commit b897fe2
Show file tree
Hide file tree
Showing 15 changed files with 444 additions and 234 deletions.
68 changes: 68 additions & 0 deletions docs/editors/new-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,73 @@ export interface MetalsInputBoxResult {
}
```

### `metals/quickPick`

The Metals quick pick request is sent from the server to the client to let the
user provide a string value by picking one out of a number of given options. It is similar to `window/showMessageRequest`, but the `metals/quickPick` request has richer parameters, that can be used to filter items to pick, like `description` and `detail`.

_Request_:

- method: `metals/quickPick`
- params: `MetalsQuickInputParams` defined as follows. It partially matches [`QuickPickOptions`](https://code.visualstudio.com/api/references/vscode-api#QuickPickOptions) in the Visual Studio Code API, but it also contains `items` of [`MetalsQuickPickItem`](https://code.visualstudio.com/api/references/vscode-api#QuickPickItem), which, in it's turn, partially matches `QuickPickItem`, but these interfaces do not contain options for picking many items:

```ts
export interface MetalsQuickPickParams {
/**
* An array of items that can be selected from.
*/
items: MetalsQuickPickItem[];
/**
* An optional flag to include the description when filtering the picks.
*/
matchOnDescription?: boolean;
/**
* An optional flag to include the detail when filtering the picks.
*/
matchOnDetail?: boolean;
/**
* An optional string to show as place holder in the input box to guide the user what to pick on.
*/
placeHolder?: string;
/**
* Set to `true` to keep the picker open when focus moves to another part of the editor or to another window.
*/
ignoreFocusOut?: boolean;
}

export interface MetalsQuickPickItem {
/**
* An id for this items that should be return as a result of the picking.
*/
id: string;
/**
* A human readable string which is rendered prominent.
*/
label: string;
/**
* A human readable string which is rendered less prominent.
*/
description?: string;
/**
* A human readable string which is rendered less prominent.
*/
detail?: string;
/**
* Always show this item.
*/
alwaysShow?: boolean;
}
```

- result: `MetalsQuickPickResult` defined as follows:

```ts
export interface MetalsQuickPickResult {
itemId?: string;
cancelled?: boolean;
}
```

### `metals/windowStateDidChange`

The `metals/windowStateDidChange` notification is sent from the client to the
Expand Down Expand Up @@ -594,6 +661,7 @@ rather then using server properties. The currently available settings for
"debuggingProvider": boolean,
"decorationProvider": boolean,
"inputBoxProvider": boolean,
"quickPickProvider": boolean,
"didFocusProvider": boolean,
"slowTaskProvider": boolean,
"executeClientCommandProvider": boolean,
Expand Down
2 changes: 2 additions & 0 deletions docs/editors/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ using the Metals sidebar. This feature is only implemented in VS Code.

**Input box**: Editor client implements the `metals/inputBox` request.

**Quick pick**: Editor client implements the `metals/quickPick` request.

**Window state**: Editor client implements the `metals/windowStateDidChange`
notification.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ final case class ClientExperimentalCapabilities(
treeViewProvider: java.lang.Boolean = false,
decorationProvider: java.lang.Boolean = false,
inputBoxProvider: java.lang.Boolean = false,
quickPickProvider: java.lang.Boolean = false,
didFocusProvider: java.lang.Boolean = false,
slowTaskProvider: java.lang.Boolean = false,
executeClientCommandProvider: java.lang.Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@ final class ConfiguredLanguageClient(
}
}

override def metalsQuickPick(
params: MetalsQuickPickParams
): CompletableFuture[MetalsQuickPickResult] = {
if (clientCapabilities.quickPickProvider) {
underlying.metalsQuickPick(params)
} else {
showMessageRequest(
toShowMessageRequestParams(params)
).asScala
.map(item => MetalsQuickPickResult(itemId = item.getTitle()))
.asJava
}
}

override def metalsPublishDecorations(
params: PublishDecorationsParams
): Unit = {
Expand All @@ -114,4 +128,13 @@ final class ConfiguredLanguageClient(
}
}

private def toShowMessageRequestParams(
params: MetalsQuickPickParams
): ShowMessageRequestParams = {
val result = new ShowMessageRequestParams()
result.setMessage(params.placeHolder)
result.setActions(params.items.map(item => new MessageActionItem(item.id)))
result
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ class DelegatingLanguageClient(var underlying: MetalsLanguageClient)
underlying.metalsInputBox(params)
}

override def metalsQuickPick(
params: MetalsQuickPickParams
): CompletableFuture[MetalsQuickPickResult] = {
underlying.metalsQuickPick(params)
}

override def metalsTreeViewDidChange(
params: TreeViewDidChangeParams
): Unit = {
Expand Down
10 changes: 0 additions & 10 deletions metals/src/main/scala/scala/meta/internal/metals/JsonParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,4 @@ object JsonParser {
}
}

// NOTE(alekseiAlefirov): one cannot do type parameterized extractor, unfortunately (https://github.com/scala/bug/issues/884)
// so instantiating this class is a workaround
class Of[A: ClassTag] {
object Jsonized {
def unapply(json: JsonElement): Option[A] = {
json.as[A].toOption
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -382,4 +382,10 @@ class Messages(icons: Icons) {
s"Please upgrade to Scala $recommended."
}
}

object NewScalaFile {
def selectTheKindOfFileMessage = "Select the kind of file to create"
def enterNameMessage(kind: String): String =
s"Enter the name for the new $kind"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ object MetalsEnrichments
Future.failed(e)
}
}

def liftOption(
implicit ec: ExecutionContext
): Future[Option[A]] = future.map(Some(_))
}

implicit class XtensionJavaList[A](lst: util.List[A]) {
Expand Down Expand Up @@ -602,4 +606,19 @@ object MetalsEnrichments
def findFirstTrailing(predicate: Token => Boolean): Option[Token] =
trailingTokens.find(predicate)
}

implicit class OptionFutureTransformer[A](state: Future[Option[A]]) {
def flatMapOption[B](
f: A => Future[Option[B]]
)(implicit ec: ExecutionContext): Future[Option[B]] =
state.flatMap(_.fold(Future.successful(Option.empty[B]))(f))

def mapOption[B](
f: A => Future[B]
)(implicit ec: ExecutionContext): Future[Option[B]] =
state.flatMap(
_.fold(Future.successful(Option.empty[B]))(f(_).liftOption)
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ trait MetalsLanguageClient
params: MetalsInputBoxParams
): CompletableFuture[MetalsInputBoxResult]

/**
* Opens an menu to ask the user to pick one of the suggested options.
*
* @return the user provided pick. The future can be cancelled, meaning
* the input box should be dismissed in the editor.
*/
@JsonRequest("metals/quickPick")
def metalsQuickPick(
params: MetalsQuickPickParams
): CompletableFuture[MetalsQuickPickResult]

final def showMessage(messageType: MessageType, message: String): Unit = {
val params = new MessageParams(messageType, message)
showMessage(params)
Expand Down Expand Up @@ -112,3 +123,33 @@ case class MetalsInputBoxResult(
@Nullable value: String = null,
@Nullable cancelled: java.lang.Boolean = null
)

case class MetalsQuickPickParams(
items: java.util.List[MetalsQuickPickItem],
// An optional flag to include the description when filtering the picks.
@Nullable matchOnDescription: java.lang.Boolean = null,
// An optional flag to include the detail when filtering the picks.
@Nullable matchOnDetail: java.lang.Boolean = null,
// An optional string to show as place holder in the input box to guide the user what to pick on.
@Nullable placeHolder: String = null,
// Set to `true` to keep the picker open when focus moves to another part of the editor or to another window.
@Nullable ignoreFocusOut: java.lang.Boolean = null
)

case class MetalsQuickPickResult(
// value=null when cancelled=true
@Nullable itemId: String = null,
@Nullable cancelled: java.lang.Boolean = null
)

case class MetalsQuickPickItem(
id: String,
// A human readable string which is rendered prominent.
label: String,
// A human readable string which is rendered less prominent.
@Nullable description: String = null,
// A human readable string which is rendered less prominent.
@Nullable detail: String = null,
// Always show this item.
@Nullable alwaysShow: java.lang.Boolean = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import scala.meta.internal.rename.RenameProvider
import ch.epfl.scala.bsp4j.CompileReport
import java.{util => ju}
import scala.meta.internal.metals.Messages.IncompatibleBloopVersion
import com.google.gson.JsonNull

class MetalsLanguageServer(
ec: ExecutionContextExecutorService,
Expand Down Expand Up @@ -178,8 +179,7 @@ class MetalsLanguageServer(
private var foldingRangeProvider: FoldingRangeProvider = _
private val packageProvider: PackageProvider =
new PackageProvider(buildTargets)
private val newFilesProvider: NewFilesProvider =
new NewFilesProvider(workspace, packageProvider)
private var newFilesProvider: NewFilesProvider = _
private var symbolSearch: MetalsSymbolSearch = _
private var compilers: Compilers = _
var tables: Tables = _
Expand Down Expand Up @@ -358,6 +358,13 @@ class MetalsLanguageServer(
Nil
}
)
newFilesProvider = new NewFilesProvider(
workspace,
languageClient,
packageProvider,
config,
() => focusedDocument
)
multilineStringFormattingProvider = new MultilineStringFormattingProvider(
semanticdbs,
buffers
Expand Down Expand Up @@ -1282,27 +1289,18 @@ class MetalsLanguageServer(
Future.failed(new IllegalArgumentException(msg)).asJavaObject
}
case ServerCommands.NewScalaFile() =>
val parser = new JsonParser.Of[MetalsNewScalaFileParams]
val args = params.getArguments.asScala
(args match {
case Seq(parser.Jsonized(newScalaFileParams)) =>
import newScalaFileParams._
val result =
newFilesProvider
.createNewFile(Option(directory).map(new URI(_)), name, kind)
result.onFailure {
case NonFatal(e) =>
languageClient
.showMessage(
MessageType.Error,
s"Cannot create file:\n ${e.toString()}"
)
}
result
case Seq(directory: JsonPrimitive) if directory.isString =>
newFilesProvider.createNewFileDialog(
Some(directory.getAsString()).map(new URI(_))
)
case Seq(_: JsonNull) =>
newFilesProvider.createNewFileDialog(directoryUri = None)
case _ =>
val argExample = ServerCommands.NewScalaFile.arguments
val msg = s"Invalid arguments: $args. Expecting: $argExample"
Future.failed(new IllegalArgumentException(msg))
Future.failed(
new IllegalArgumentException(s"Invalid arguments: $args.")
)
}).asJavaObject

case cmd =>
Expand Down
Loading

0 comments on commit b897fe2

Please sign in to comment.