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

Infer method #6877

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
30 changes: 30 additions & 0 deletions metals/src/main/scala/scala/meta/internal/metals/Compilers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import org.eclipse.lsp4j.SignatureHelp
import org.eclipse.lsp4j.TextDocumentIdentifier
import org.eclipse.lsp4j.TextDocumentPositionParams
import org.eclipse.lsp4j.TextEdit
import org.eclipse.lsp4j.WorkspaceEdit
import org.eclipse.lsp4j.jsonrpc.messages.{Either => JEither}
import org.eclipse.lsp4j.{Position => LspPosition}
import org.eclipse.lsp4j.{Range => LspRange}
Expand Down Expand Up @@ -918,6 +919,35 @@ class Compilers(
}
}.getOrElse(Future.successful(Nil.asJava))

def insertInferredMethod(
params: TextDocumentPositionParams,
token: CancelToken,
): Future[WorkspaceEdit] = {
withPCAndAdjustLsp(params) { (pc, pos, adjust) =>
pc.insertInferredMethod(CompilerOffsetParamsUtils.fromPos(pos, token))
.asScala
.map(workspaceEdit =>
new WorkspaceEdit(
workspaceEdit
.getChanges()
.asScala
.map {
case (uri, textEdits) => {
(
uri,
tgodzik marked this conversation as resolved.
Show resolved Hide resolved
textEdits.asScala.map { textEdit =>
textEdit.setRange(adjust.adjustRange(textEdit.getRange()))
textEdit
}.asJava,
)
}
}
.asJava
)
)
}
}.getOrElse(Future.successful(new WorkspaceEdit()))

def implementAbstractMembers(
params: TextDocumentPositionParams,
token: CancelToken,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ object ServerCommands {
val DiscoverMainClasses = new ParametrizedCommand[DebugDiscoveryParams](
"discover-jvm-run-command",
"Discover main classes to run and return the object",
"""|Gets the DebugSession object that also contains a command to run in shell based
"""|Gets the DebugSession object that also contains a command to run in shell based
|on JVM environment including classpath, jvmOptions and environment parameters.
|""".stripMargin,
"""|DebugUnresolvedTestClassParams object
Expand Down Expand Up @@ -267,7 +267,7 @@ object ServerCommands {
"Clean compile",
"""|Recompile all build targets in this workspace.
|
|By default, Metals compiles the files incrementally. In case of any compile artifacts corruption
|By default, Metals compiles the files incrementally. In case of any compile artifacts corruption
|this command might be run to make sure everything is recompiled correctly.
|""".stripMargin,
)
Expand Down Expand Up @@ -448,7 +448,7 @@ object ServerCommands {
"Choose class",
"""|Exists only because of how vscode virtual documents work. Usage of this command is discouraged, it'll be removed in the future,
|when metals-vscode will implement custom editor for .tasty and .class files.
|Shows toplevel definitions such as classes, traits, objects and toplevel methods which are defined in a given scala file.
|Shows toplevel definitions such as classes, traits, objects and toplevel methods which are defined in a given scala file.
|Then, returns an URI pointing to the .tasty or .class file for class picked by user""".stripMargin,
"""|Object with `textDocument` and `includeInnerClasses`
|
Expand Down Expand Up @@ -580,11 +580,11 @@ object ServerCommands {
val NewScalaProject = new Command(
"new-scala-project",
"New Scala Project",
"""|Create a new Scala project using one of the available g8 templates.
"""|Create a new Scala project using one of the available g8 templates.
|This includes simple projects as well as samples for most of the popular Scala frameworks.
|The command reuses the Metals quick pick extension to work and can function with `window/showMessageRequest`,
|The command reuses the Metals quick pick extension to work and can function with `window/showMessageRequest`,
|however the experience will not be optimal in that case. Some editors might also offer to open the newly created
|project via `openNewWindowProvider`, but it is not necessary for the main functionality to work.
|project via `openNewWindowProvider`, but it is not necessary for the main functionality to work.
|""".stripMargin,
)

Expand Down Expand Up @@ -613,7 +613,7 @@ object ServerCommands {
val InsertInferredType = new ParametrizedCommand[TextDocumentPositionParams](
"insert-inferred-type",
"Insert inferred type of a value",
"""|Whenever a user chooses code action to insert the inferred type this command is later ran to
"""|Whenever a user chooses code action to insert the inferred type this command is later ran to
|calculate the type and insert it in the correct location.
|""".stripMargin,
"""|This command should be sent in with the LSP [`TextDocumentPositionParams`](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams)
Expand Down Expand Up @@ -641,7 +641,7 @@ object ServerCommands {
"""|Whenever a user chooses code action to extract method, this command is later ran to
|calculate parameters for the newly created method and create its definition.
|""".stripMargin,
"""|LSP [`TextDocumentIdentifier`], (https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentIdentifier),
"""|LSP [`TextDocumentIdentifier`], (https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentIdentifier),
|LSP [`Range`], range of the code you'd like to extract as method,
|LSP [`Position`], position where the definition of extracted method will be created.
|""".stripMargin,
Expand All @@ -655,13 +655,34 @@ object ServerCommands {
new ParametrizedCommand[ConvertToNamedArgsRequest](
"convert-to-named-arguments",
"Convert positional arguments to named ones",
"""|Whenever a user chooses code action to convert to named arguments, this command is later run to
"""|Whenever a user chooses code action to convert to named arguments, this command is later run to
|determine the parameter names of all unnamed arguments and insert names at the correct locations.
|""".stripMargin,
"""|Object with [TextDocumentPositionParams](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentPositionParams) of the target Apply and `numUnnamedArgs` (int)
|""".stripMargin,
)

val InsertInferredMethod =
new ParametrizedCommand[TextDocumentPositionParams](
"insert-inferred-method",
"Insert inferred method",
"""|Try and create a method from the error symbol at the current position
|where that position points to a name of form for example:
|- `nonExisting(param)`
|- `obj.nonExisting()`
|""".stripMargin,
"""|Object with `document` and `position`
|
|Example:
|```json
|{
| document: "file:///home/dev/foo/Bar.scala",
| position: {line: 5, character: 12}
|}
|```
|""".stripMargin,
)

val GotoLog = new Command(
"goto-log",
"Check logs",
Expand Down Expand Up @@ -759,6 +780,7 @@ object ServerCommands {
GotoSymbol,
ImportBuild,
InsertInferredType,
InsertInferredMethod,
InlineValue,
NewScalaFile,
NewJavaFile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ final class CodeActionProvider(
private val allActions: List[CodeAction] = List(
new ImplementAbstractMembers(compilers),
new ImportMissingSymbol(compilers, buildTargets),
new CreateNewSymbol(),
new CreateNewSymbol(compilers, buildTargets, languageClient),
new ActionableDiagnostic(),
new StringActions(buffers),
extractMemberAction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,46 @@ package scala.meta.internal.metals.codeactions
import scala.concurrent.ExecutionContext
import scala.concurrent.Future

import scala.meta.internal.metals.BuildTargets
import scala.meta.internal.metals.Compilers
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.metals.ScalaVersions
import scala.meta.internal.metals.ScalacDiagnostic
import scala.meta.internal.metals.ServerCommands
import scala.meta.internal.metals.clients.language.MetalsLanguageClient
import scala.meta.internal.metals.codeactions.CodeAction
import scala.meta.pc.CancelToken

import org.eclipse.{lsp4j => l}

class CreateNewSymbol() extends CodeAction {
class CreateNewSymbol(
compilers: Compilers,
buildTargets: BuildTargets,
languageClient: MetalsLanguageClient,
) extends CodeAction {
override def kind: String = l.CodeActionKind.QuickFix

override type CommandData = l.TextDocumentPositionParams
override def command: Option[ActionCommand] = Some(
ServerCommands.InsertInferredMethod
)

override def handleCommand(
textDocumentParams: l.TextDocumentPositionParams,
token: CancelToken,
)(implicit ec: ExecutionContext): Future[Unit] = {
for {
workspaceEdit <- compilers.insertInferredMethod(
textDocumentParams,
token,
)
if (!workspaceEdit.getChanges().isEmpty())
_ <- languageClient
.applyEdit(new l.ApplyWorkspaceEditParams(workspaceEdit))
.asScala
} yield ()
}

override def contribute(
params: l.CodeActionParams,
token: CancelToken,
Expand All @@ -22,6 +51,17 @@ class CreateNewSymbol() extends CodeAction {
lazy val parentUri =
params.getTextDocument.getUri.toAbsolutePath.parent.toURI

val uri = params.getTextDocument().getUri()
val file = uri.toAbsolutePath
lazy val isScala3 =
(for {
buildId <- buildTargets.inverseSources(file)
target <- buildTargets.scalaTarget(buildId)
isScala3 = ScalaVersions.isScala3Version(
target.scalaVersion
)
} yield isScala3).getOrElse(false)

def createNewSymbol(
diagnostic: l.Diagnostic,
name: String,
Expand All @@ -37,15 +77,44 @@ class CreateNewSymbol() extends CodeAction {
)
}

def createNewMethod(
ag91 marked this conversation as resolved.
Show resolved Hide resolved
diagnostic: l.Diagnostic,
name: String,
): l.CodeAction = {
val command =
ServerCommands.InsertInferredMethod.toLsp(
new l.TextDocumentPositionParams(
params.getTextDocument(),
params.getRange().getStart(),
)
)

CodeActionBuilder.build(
title = CreateNewSymbol.method(name),
kind = l.CodeActionKind.QuickFix,
command = Some(command),
diagnostics = List(diagnostic),
)
}

val codeActions = params
.getContext()
.getDiagnostics()
.asScala
.groupBy {
case ScalacDiagnostic.SymbolNotFound(name) => Some(name)
case ScalacDiagnostic.SymbolNotFound(name) if name.nonEmpty =>
Some(name)
case ScalacDiagnostic.NotAMember(name) if name.nonEmpty =>
Some(name)
case _ => None
}
.collect {
case (Some(name), diags)
if !isScala3 // scala 3 not supported yet
&& name.head.isLower && params
.getRange()
.overlapsWith(diags.head.getRange()) =>
createNewMethod(diags.head, name)
tgodzik marked this conversation as resolved.
Show resolved Hide resolved
case (Some(name), diags)
if params.getRange().overlapsWith(diags.head.getRange()) =>
createNewSymbol(diags.head, name)
Expand All @@ -59,4 +128,5 @@ class CreateNewSymbol() extends CodeAction {

object CreateNewSymbol {
def title(name: String): String = s"Create new symbol '$name'..."
def method(name: String): String = s"Create new method 'def $name'..."
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.eclipse.lsp4j.DocumentHighlight;
import org.eclipse.lsp4j.SignatureHelp;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.Range;

import org.eclipse.lsp4j.Diagnostic;
Expand Down Expand Up @@ -140,7 +141,7 @@ public List<String> supportedCodeActions() {

/**
* Return decoded and pretty printed TASTy content for .scala or .tasty file.
*
*
*/
public abstract CompletableFuture<String> getTasty(URI targetUri, boolean isHttpEnabled);

Expand All @@ -156,6 +157,11 @@ public abstract CompletableFuture<List<AutoImportsResult>> autoImports(String na
*/
public abstract CompletableFuture<List<TextEdit>> implementAbstractMembers(OffsetParams params);

/**
* Return the missing method
*/
public abstract CompletableFuture<WorkspaceEdit> insertInferredMethod(OffsetParams params);
Copy link
Contributor

Choose a reason for hiding this comment

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

This should have default implementation for Mima to pass, but I will change it later on to use codeAction, so don't worry about it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes please, I haven't dived in the codeAction method sure that it would be easier for you to adapt.


/**
* Return the missing implements and imports for the symbol at the given
* position.
Expand All @@ -173,7 +179,7 @@ public CompletableFuture<List<TextEdit>> inlineValue(OffsetParams params) {

/**
* Extract method in selected range
*
*
* @param range range to extract from
* @param extractionPos position in file to extract to
*/
Expand Down Expand Up @@ -350,7 +356,7 @@ public abstract PresentationCompiler newInstance(String buildTargetIdentifier, L
/**
* Returns false if the presentation compiler has not been used since the last
* reset.
*
*
* NOTE(olafur) This method was added for testing purposes. It's critical that
* we correctly reset the presentation compiler when build compilation complete
* to prevent the presentatin compiler from returning stale results. It's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import org.eclipse.lsp4j.DocumentHighlight
import org.eclipse.lsp4j.SelectionRange
import org.eclipse.lsp4j.SignatureHelp
import org.eclipse.lsp4j.TextEdit
import org.eclipse.lsp4j.WorkspaceEdit

case class JavaPresentationCompiler(
buildTargetIdentifier: String = "",
Expand Down Expand Up @@ -128,6 +129,11 @@ case class JavaPresentationCompiler(
): CompletableFuture[util.List[TextEdit]] =
CompletableFuture.completedFuture(Nil.asJava)

override def insertInferredMethod(
params: OffsetParams
): CompletableFuture[WorkspaceEdit] =
CompletableFuture.completedFuture(new WorkspaceEdit())

override def extractMethod(
range: RangeParams,
extractionPos: OffsetParams
Expand Down
Loading
Loading