diff --git a/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt b/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt index 2075a0d6f..6be06863a 100644 --- a/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt +++ b/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt @@ -164,7 +164,7 @@ class DefaultActionsRegistry : ActionsRegistry() { } } } - + private fun notifyActionExec(action: ActionItem, result: Any) { for (listener in listeners) { listener.onExec(action, result) diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/actions/ActionProvider.java b/lsp/java/src/main/java/com/itsaky/lsp/java/actions/ActionProvider.java deleted file mode 100644 index 4b6e08c6b..000000000 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/actions/ActionProvider.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This file is part of AndroidIDE. - * - * AndroidIDE is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AndroidIDE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AndroidIDE. If not, see . - */ - -package com.itsaky.lsp.java.actions; - -import androidx.annotation.NonNull; - -import com.itsaky.lsp.java.compiler.CompilerProvider; -import com.itsaky.lsp.models.CodeActionItem; -import com.itsaky.lsp.models.DiagnosticItem; -import com.itsaky.lsp.models.Range; - -import java.nio.file.Path; -import java.util.List; - -/** - * @author Akash Yadav - */ -public interface ActionProvider { - - /** - * Provide code actions. - * - * @param compiler The compiler. - * @param file The file. - * @param range The cursor range. - * @param diagnostics The diagnostics within the cursor range. - * @return The list of code actions. - */ - @NonNull - List provideActions( - @NonNull CompilerProvider compiler, - @NonNull Path file, - @NonNull Range range, - @NonNull List diagnostics); -} diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/actions/AddThrowsAction.kt b/lsp/java/src/main/java/com/itsaky/lsp/java/actions/AddThrowsAction.kt new file mode 100644 index 000000000..9d45be329 --- /dev/null +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/actions/AddThrowsAction.kt @@ -0,0 +1,76 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.lsp.java.actions + +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.utils.Logger +import com.itsaky.lsp.java.JavaLanguageServer +import com.itsaky.lsp.java.rewrite.AddException +import com.itsaky.lsp.java.utils.CodeActionUtils +import com.itsaky.lsp.models.DiagnosticItem + +/** @author Akash Yadav */ +class AddThrowsAction : BaseCodeAction() { + override val id = "lsp_java_addThrows" + override var label: String = "Add 'throws'" + private val diagnosticCode = "compiler.err.unreported.exception.need.to.catch.or.throw" + private val log = Logger.newInstance(javaClass.simpleName) + + override fun prepare(data: ActionData) { + super.prepare(data) + + if (!visible || !hasRequiredData(data, DiagnosticItem::class.java)) { + markInvisible() + return + } + + val diagnostic = data[DiagnosticItem::class.java]!! + if (diagnosticCode != diagnostic.code) { + markInvisible() + return + } + } + + override fun execAction(data: ActionData): Any { + val diagnostic = data[DiagnosticItem::class.java]!! + val server = data[JavaLanguageServer::class.java]!! + val file = requirePath(data) + return server.compiler.compile(file).get { task -> + val needsThrow = CodeActionUtils.findMethod(task, diagnostic.range) + val exceptionName = CodeActionUtils.extractExceptionName(diagnostic.message) + return@get AddException( + needsThrow.className, + needsThrow.methodName, + needsThrow.erasedParameterTypes, + exceptionName) + } + } + + override fun postExec(data: ActionData, result: Any) { + if (result !is AddException) { + log.warn("Unable to add 'throws' expression") + return + } + + val server = data[JavaLanguageServer::class.java]!! + val client = server.client!! + val file = requireFile(data) + + client.performCodeAction(file, result.asCodeActions(server.compiler, label)) + } +} diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/actions/CursorCodeActionProvider.java b/lsp/java/src/main/java/com/itsaky/lsp/java/actions/CursorCodeActionProvider.java index ab1eb5435..a5799f92b 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/actions/CursorCodeActionProvider.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/actions/CursorCodeActionProvider.java @@ -83,17 +83,15 @@ import javax.lang.model.util.Elements; * * @author Akash Yadav */ -public class CursorCodeActionProvider implements ActionProvider { +public class CursorCodeActionProvider { private static final Logger LOG = newInstance("JavaCursorCodeActionProvider"); @NonNull - @Override - public List provideActions( + public List provideActions ( @NonNull CompilerProvider compiler, @NonNull Path file, - @NonNull Range range, - @NonNull List diagnostics) { + @NonNull Range range) { logStarted(file, range); Instant started = Instant.now(); diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/actions/DiagnosticsCodeActionProvider.java b/lsp/java/src/main/java/com/itsaky/lsp/java/actions/DiagnosticsCodeActionProvider.java deleted file mode 100644 index 9ebc1821b..000000000 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/actions/DiagnosticsCodeActionProvider.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * This file is part of AndroidIDE. - * - * AndroidIDE is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AndroidIDE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AndroidIDE. If not, see . - */ - -package com.itsaky.lsp.java.actions; - -import static com.itsaky.androidide.utils.Logger.newInstance; -import static com.itsaky.lsp.java.utils.CodeActionUtils.extractExceptionName; -import static com.itsaky.lsp.java.utils.CodeActionUtils.findClassNeedingConstructor; -import static com.itsaky.lsp.java.utils.CodeActionUtils.findMethod; - -import android.text.TextUtils; -import android.util.Pair; - -import androidx.annotation.NonNull; - -import com.itsaky.androidide.utils.Logger; -import com.itsaky.lsp.java.compiler.CompileTask; -import com.itsaky.lsp.java.compiler.CompilerProvider; -import com.itsaky.lsp.java.compiler.SynchronizedTask; -import com.itsaky.lsp.java.rewrite.AddException; -import com.itsaky.lsp.java.rewrite.AddSuppressWarningAnnotation; -import com.itsaky.lsp.java.rewrite.GenerateRecordConstructor; -import com.itsaky.lsp.java.rewrite.Rewrite; -import com.itsaky.lsp.java.utils.MethodPtr; -import com.itsaky.lsp.models.CodeActionItem; -import com.itsaky.lsp.models.DiagnosticItem; -import com.itsaky.lsp.models.Range; - -import java.nio.file.Path; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.stream.Collectors; - -/** - * Provides code actions for diagnostics. - * - * @author Akash Yadav - */ -public class DiagnosticsCodeActionProvider implements ActionProvider { - - private static final Logger LOG = newInstance("JavaDiagnosticsCodeActionProvider"); - - @NonNull - @Override - public List provideActions( - @NonNull CompilerProvider compiler, - @NonNull Path file, - @NonNull Range range, - @NonNull List diagnostics) { - LOG.info( - String.format( - Locale.getDefault(), - "Check %d diagnostics for quick fixes...", - diagnostics.size())); - Instant started = Instant.now(); - final SynchronizedTask synchronizedTask = compiler.compile(file); - final List> actions = - synchronizedTask.get(task -> computeActions(compiler, file, diagnostics, task)); - - final List result = new ArrayList<>(); - for (Pair pair : actions) { - try { - final CodeActionItem action = pair.second.asCodeActions(compiler, pair.first); - if (action != null) { - result.add(action); - } - } catch (Throwable throwable) { - LOG.error("Unable to create rewrite for quickfix", throwable); - } - } - - LOG.debug( - String.format( - Locale.ROOT, - "Created %d code actions in %dms", - result.size(), - Instant.now().toEpochMilli() - started.toEpochMilli())); - - return result; - } - - @NonNull - private List> computeActions( - CompilerProvider compiler, - @NonNull Path file, - @NonNull List diagnostics, - CompileTask task) { - List> rewrites = new ArrayList<>(); - for (DiagnosticItem d : diagnostics) { - List> pairs = - codeActionForDiagnostic(compiler, task, file, d).stream() - .filter(pair -> !TextUtils.isEmpty(pair.first) && pair.second != null) - .collect(Collectors.toList()); - rewrites.addAll(pairs); - } - return rewrites; - } - - @NonNull - private List> codeActionForDiagnostic( - CompilerProvider compiler, CompileTask task, Path file, @NonNull DiagnosticItem d) { - switch (d.getCode()) { - case "compiler.warn.unchecked.call.mbr.of.raw.type": - return handleUnchecked(task, d); - case "compiler.err.unreported.exception.need.to.catch.or.throw": - return handleUnreportedException(task, d); - case "compiler.err.var.not.initialized.in.default.constructor": - return handleVarNotInitialized(task, d); - default: - return Collections.singletonList(Pair.create("", null)); - } - } - - @NonNull - private List> handleVarNotInitialized( - CompileTask task, @NonNull DiagnosticItem d) { - Rewrite rewrite; - String title; - final String needsConstructor = findClassNeedingConstructor(task, d.getRange()); - if (needsConstructor == null) { - return Collections.singletonList(Pair.create("", null)); - } - - rewrite = new GenerateRecordConstructor(needsConstructor); - title = "Generate constructor"; - return Collections.singletonList(Pair.create(title, rewrite)); - } - - @NonNull - private List> handleUnreportedException( - CompileTask task, @NonNull DiagnosticItem d) { - Rewrite rewrite; - String title; - final MethodPtr needsThrow = findMethod(task, d.getRange()); - final String exceptionName = extractExceptionName(d.getMessage()); - rewrite = - new AddException( - needsThrow.className, - needsThrow.methodName, - needsThrow.erasedParameterTypes, - exceptionName); - title = "Add 'throws'"; - return Collections.singletonList(Pair.create(title, rewrite)); - } - - @NonNull - private List> handleUnchecked( - CompileTask task, @NonNull DiagnosticItem d) { - Rewrite rewrite; - String title; - final MethodPtr warnedMethod = findMethod(task, d.getRange()); - rewrite = - new AddSuppressWarningAnnotation( - warnedMethod.className, - warnedMethod.methodName, - warnedMethod.erasedParameterTypes); - title = "Suppress 'unchecked' warning"; - return Collections.singletonList(Pair.create(title, rewrite)); - } -} diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/actions/GenerateConstructorAction.kt b/lsp/java/src/main/java/com/itsaky/lsp/java/actions/GenerateConstructorAction.kt new file mode 100644 index 000000000..6eb6ad1a4 --- /dev/null +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/actions/GenerateConstructorAction.kt @@ -0,0 +1,73 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.lsp.java.actions + +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.utils.Logger +import com.itsaky.lsp.java.JavaLanguageServer +import com.itsaky.lsp.java.rewrite.GenerateRecordConstructor +import com.itsaky.lsp.java.utils.CodeActionUtils +import com.itsaky.lsp.models.DiagnosticItem + +/** @author Akash Yadav */ +class GenerateConstructorAction : BaseCodeAction() { + override val id = "lsp_java_generateConstructor" + override var label: String = "Generate constructor" + private val diagnosticCode = "compiler.err.var.not.initialized.in.default.constructor" + private val log = Logger.newInstance(javaClass.simpleName) + + override fun prepare(data: ActionData) { + super.prepare(data) + + if (!visible || !hasRequiredData(data, DiagnosticItem::class.java)) { + markInvisible() + return + } + + val diagnostic = data[DiagnosticItem::class.java]!! + if (diagnosticCode != diagnostic.code) { + markInvisible() + return + } + } + + override fun execAction(data: ActionData): Any { + val diagnostic = data[DiagnosticItem::class.java]!! + val server = data[JavaLanguageServer::class.java]!! + val file = requirePath(data) + return server.compiler.compile(file).get { task -> + val needsConstructor = + CodeActionUtils.findClassNeedingConstructor(task, diagnostic.range) + ?: return@get false + return@get GenerateRecordConstructor(needsConstructor) + } + } + + override fun postExec(data: ActionData, result: Any) { + if (result !is GenerateRecordConstructor) { + log.warn("Unable to generate constructor") + return + } + + val server = data[JavaLanguageServer::class.java]!! + val client = server.client!! + val file = requireFile(data) + + client.performCodeAction(file, result.asCodeActions(server.compiler, label)) + } +} diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/actions/JavaCodeActionsMenu.kt b/lsp/java/src/main/java/com/itsaky/lsp/java/actions/JavaCodeActionsMenu.kt index 667f6d352..0fddee2bc 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/actions/JavaCodeActionsMenu.kt +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/actions/JavaCodeActionsMenu.kt @@ -32,5 +32,7 @@ class JavaCodeActionsMenu : CodeActionsMenu() { addAction(RemoveMethodAction()) addAction(RemoveUnusedThrowsAction()) addAction(CreateMissingMethodAction()) + addAction(SuppressUncheckedWarningAction()) + addAction(AddThrowsAction()) } } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/actions/SuppressUncheckedWarningAction.kt b/lsp/java/src/main/java/com/itsaky/lsp/java/actions/SuppressUncheckedWarningAction.kt new file mode 100644 index 000000000..466289bfa --- /dev/null +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/actions/SuppressUncheckedWarningAction.kt @@ -0,0 +1,72 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.lsp.java.actions + +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.utils.Logger +import com.itsaky.lsp.java.JavaLanguageServer +import com.itsaky.lsp.java.rewrite.AddSuppressWarningAnnotation +import com.itsaky.lsp.java.utils.CodeActionUtils +import com.itsaky.lsp.models.DiagnosticItem + +/** @author Akash Yadav */ +class SuppressUncheckedWarningAction : BaseCodeAction() { + override val id = "lsp_java_suppressUncheckedWarning" + override var label: String = "Suppress 'unchecked' warning" + private val diagnosticCode = "compiler.warn.unchecked.call.mbr.of.raw.type" + private val log = Logger.newInstance(javaClass.simpleName) + + override fun prepare(data: ActionData) { + super.prepare(data) + + if (!visible || !hasRequiredData(data, DiagnosticItem::class.java)) { + markInvisible() + return + } + + val diagnostic = data[DiagnosticItem::class.java]!! + if (diagnosticCode != diagnostic.code) { + markInvisible() + return + } + } + + override fun execAction(data: ActionData): Any { + val diagnostic = data[DiagnosticItem::class.java]!! + val server = data[JavaLanguageServer::class.java]!! + val file = requirePath(data) + return server.compiler.compile(file).get { task -> + val warnedMethod = CodeActionUtils.findMethod(task, diagnostic.range) + return@get AddSuppressWarningAnnotation( + warnedMethod.className, warnedMethod.methodName, warnedMethod.erasedParameterTypes) + } + } + + override fun postExec(data: ActionData, result: Any) { + if (result !is AddSuppressWarningAnnotation) { + log.warn("Unable to suppress 'unchecked' warning") + return + } + + val server = data[JavaLanguageServer::class.java]!! + val client = server.client!! + val file = requireFile(data) + + client.performCodeAction(file, result.asCodeActions(server.compiler, label)) + } +} diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/CodeActionProvider.java b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/CodeActionProvider.java index e5535765b..edacea76a 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/providers/CodeActionProvider.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/providers/CodeActionProvider.java @@ -20,9 +20,7 @@ package com.itsaky.lsp.java.providers; import androidx.annotation.NonNull; import com.itsaky.androidide.utils.Logger; -import com.itsaky.lsp.java.actions.ActionProvider; import com.itsaky.lsp.java.actions.CursorCodeActionProvider; -import com.itsaky.lsp.java.actions.DiagnosticsCodeActionProvider; import com.itsaky.lsp.java.compiler.CompilerProvider; import com.itsaky.lsp.models.CodeActionParams; import com.itsaky.lsp.models.CodeActionResult; @@ -38,15 +36,8 @@ public class CodeActionProvider { @NonNull public CodeActionResult codeActions(@NonNull CodeActionParams params) { - final ActionProvider actionProvider; - if (params.getDiagnostics().isEmpty()) { - actionProvider = new CursorCodeActionProvider(); - } else { - actionProvider = new DiagnosticsCodeActionProvider(); - } - return new CodeActionResult( - actionProvider.provideActions( - compiler, params.getFile(), params.getRange(), params.getDiagnostics())); + new CursorCodeActionProvider() + .provideActions(compiler, params.getFile(), params.getRange())); } }