[mod-command] PsiUpdateContext#moveToPrevious; AddFinallyFix -> ModCommandAction

GitOrigin-RevId: 56625a1739da460bcc71712ae4ad682f2ce200c7
This commit is contained in:
Tagir Valeev
2023-05-05 19:45:51 +00:00
committed by intellij-monorepo-bot
parent 498d3de463
commit d75b5e726f
5 changed files with 119 additions and 93 deletions
@@ -1,4 +1,4 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
@@ -31,7 +31,7 @@ public class JavaErrorQuickFixProvider implements ErrorQuickFixProvider {
}
if (parent instanceof PsiTryStatement && description.equals(JavaPsiBundle.message("expected.catch.or.finally"))) {
registrar.add(new AddExceptionToCatchFix(false));
registrar.add(new AddFinallyFix((PsiTryStatement)parent));
registrar.add(new AddFinallyFix((PsiTryStatement)parent).asIntention());
}
if (parent instanceof PsiSwitchLabeledRuleStatement && description.equals(JavaPsiBundle.message("expected.switch.rule"))) {
IntentionAction action =
@@ -1,26 +1,47 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.generation.surroundWith.JavaWithTryFinallySurrounder;
import com.intellij.codeInsight.intention.FileModifier;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.codeInspection.ModCommands;
import com.intellij.codeInspection.PsiUpdateContext;
import com.intellij.modcommand.ModCommand;
import com.intellij.modcommand.PsiBasedModCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.psi.codeStyle.CodeStyleManager;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public class AddFinallyFix extends BaseIntentionAction {
private final PsiTryStatement myTryStatement;
public class AddFinallyFix extends PsiBasedModCommandAction<PsiTryStatement> {
public AddFinallyFix(PsiTryStatement statement) {
myTryStatement = statement;
super(statement);
}
@Override
protected @NotNull ModCommand perform(@NotNull ActionContext context, @NotNull PsiTryStatement element) {
return ModCommands.psiUpdate(element, (tryStatement, updater) -> {
PsiStatement replacement =
JavaPsiFacade.getElementFactory(context.project())
.createStatementFromText(tryStatement.getText() + "finally {\n\n}", tryStatement);
PsiTryStatement result = (PsiTryStatement)tryStatement.replace(replacement);
moveCaretToFinallyBlock(updater, Objects.requireNonNull(result.getFinallyBlock()));
});
}
private static void moveCaretToFinallyBlock(@NotNull PsiUpdateContext updater, @NotNull PsiCodeBlock block) {
PsiFile file = block.getContainingFile();
Document document = file.getViewProvider().getDocument();
Project project = file.getProject();
updater.moveTo(Objects.requireNonNull(block.getRBrace()));
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document);
TextRange finallyBlockRange = block.getTextRange();
int newLineOffset = finallyBlockRange.getStartOffset() + 2;
CodeStyleManager.getInstance(project).adjustLineIndent(document, newLineOffset);
updater.moveToPrevious('\n');
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@@ -29,31 +50,4 @@ public class AddFinallyFix extends BaseIntentionAction {
public String getFamilyName() {
return QuickFixBundle.message("add.finally.block.family");
}
@NotNull
@Override
public String getText() {
return getFamilyName();
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
if (!(file instanceof PsiJavaFile)) return false;
if (!myTryStatement.isValid()) return false;
if (myTryStatement.getFinallyBlock() != null) return false;
return true;
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
PsiStatement replacement =
JavaPsiFacade.getElementFactory(project).createStatementFromText(myTryStatement.getText() + "finally {\n\n}", myTryStatement);
PsiTryStatement result = (PsiTryStatement)myTryStatement.replace(replacement);
JavaWithTryFinallySurrounder.moveCaretToFinallyBlock(project, editor, Objects.requireNonNull(result.getFinallyBlock()));
}
@Override
public @Nullable FileModifier getFileModifierForPreview(@NotNull PsiFile target) {
return new AddFinallyFix(PsiTreeUtil.findSameElementInCopy(myTryStatement, target));
}
}
@@ -3,7 +3,7 @@ class Test {
void foo() {
try {
} finally {
<caret>
}
}
}
@@ -23,7 +23,6 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.LocalTimeCounter;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
@@ -108,48 +107,7 @@ public final class ModCommands {
positionDocument = document;
}
String oldText = targetFile.getText();
var context = new PsiUpdateContext() {
@Nullable RangeMarker mySelectionRange = actionContext.selection().getStartOffset() == -1 ? null :
positionDocument.createRangeMarker(actionContext.selection().getStartOffset(),
actionContext.selection().getEndOffset(), true);
@Nullable RangeMarker myCaretRange = actionContext.offset() == -1 ? null :
positionDocument.createRangeMarker(actionContext.offset(), actionContext.offset(), true);
@Override
public void select(@NotNull PsiElement element) {
validate(element);
manager.doPostponedOperationsAndUnblockDocument(document);
if (mySelectionRange != null) {
mySelectionRange.dispose();
}
if (myCaretRange != null) {
myCaretRange.dispose();
}
if (injected) {
element = PsiTreeUtil.findSameElementInCopy(element, targetFile);
}
mySelectionRange = positionDocument.createRangeMarker(element.getTextRange());
myCaretRange = positionDocument.createRangeMarker(element.getTextRange().getStartOffset(), element.getTextRange().getStartOffset());
}
@Override
public void moveTo(@NotNull PsiElement element) {
validate(element);
manager.doPostponedOperationsAndUnblockDocument(document);
if (myCaretRange != null) {
myCaretRange.dispose();
}
if (injected) {
element = PsiTreeUtil.findSameElementInCopy(element, targetFile);
}
myCaretRange = positionDocument.createRangeMarker(element.getTextRange().getStartOffset(), element.getTextRange().getStartOffset());
}
private void validate(@NotNull PsiElement element) {
if (!element.isValid()) throw new IllegalArgumentException();
if (!PsiTreeUtil.isAncestor(copyFile, element, false)) throw new IllegalArgumentException();
}
};
var context = new PsiUpdateContextImpl(actionContext, positionDocument, manager, document, injected, targetFile, copyFile);
aspect.postponeFormattingInside(
() -> aspect.forcePostprocessFormatInside(copyFile, () -> updater.accept(copy, context)));
manager.commitDocument(document);
@@ -159,23 +117,19 @@ public final class ModCommands {
VirtualFile origVirtualFile = origFile.getOriginalFile().getVirtualFile();
if (origVirtualFile != null) {
int start = -1, end = -1, caret = -1;
if (context.mySelectionRange != null && context.mySelectionRange.getEndOffset() <= newText.length()) {
if (context.mySelectionRange.getEndOffset() <= newText.length()) {
start = context.mySelectionRange.getStartOffset();
end = context.mySelectionRange.getEndOffset();
}
if (context.myCaretRange != null && context.myCaretRange.getStartOffset() <= newText.length()) {
if (context.myCaretRange.getStartOffset() <= newText.length()) {
caret = context.myCaretRange.getStartOffset();
}
if (start != -1 || end != -1 || caret != -1) {
command = command.andThen(new ModNavigate(origVirtualFile, start, end, caret));
}
}
if (context.mySelectionRange != null) {
context.mySelectionRange.dispose();
}
if (context.myCaretRange != null) {
context.myCaretRange.dispose();
}
context.mySelectionRange.dispose();
context.myCaretRange.dispose();
if (disposable != null) {
Disposer.dispose(disposable);
}
@@ -227,4 +181,76 @@ public final class ModCommands {
}
return new ModCommandAction.ActionContext(project, copyFile, offset, TextRange.create(start, end));
}
private static class PsiUpdateContextImpl implements PsiUpdateContext {
private final @NotNull Document myPositionDocument;
private final @NotNull PsiDocumentManager myManager;
private final @NotNull Document myDocument;
private final boolean myInjected;
private final @NotNull PsiFile myTargetFile;
private final @NotNull PsiFile myCopyFile;
@NotNull RangeMarker mySelectionRange;
@NotNull RangeMarker myCaretRange;
private PsiUpdateContextImpl(@NotNull ModCommandAction.ActionContext actionContext,
@NotNull Document positionDocument,
@NotNull PsiDocumentManager manager,
@NotNull Document document,
boolean injected,
@NotNull PsiFile targetFile,
@NotNull PsiFile copyFile) {
myPositionDocument = positionDocument;
myManager = manager;
myDocument = document;
myInjected = injected;
myTargetFile = targetFile;
myCopyFile = copyFile;
mySelectionRange = myPositionDocument.createRangeMarker(actionContext.selection().getStartOffset(),
actionContext.selection().getEndOffset(), true);
myCaretRange = myPositionDocument.createRangeMarker(actionContext.offset(), actionContext.offset(), true);
}
@Override
public void select(@NotNull PsiElement element) {
validate(element);
myManager.doPostponedOperationsAndUnblockDocument(myDocument);
mySelectionRange.dispose();
myCaretRange.dispose();
if (myInjected) {
element = PsiTreeUtil.findSameElementInCopy(element, myTargetFile);
}
mySelectionRange = myPositionDocument.createRangeMarker(element.getTextRange());
myCaretRange = myPositionDocument.createRangeMarker(element.getTextRange().getStartOffset(), element.getTextRange().getStartOffset());
}
@Override
public void moveTo(@NotNull PsiElement element) {
validate(element);
myManager.doPostponedOperationsAndUnblockDocument(myDocument);
if (myInjected) {
element = PsiTreeUtil.findSameElementInCopy(element, myTargetFile);
}
int offset = element.getTextRange().getStartOffset();
moveToOffset(offset);
}
private void moveToOffset(int offset) {
myCaretRange.dispose();
myCaretRange = myPositionDocument.createRangeMarker(offset, offset);
}
@Override
public void moveToPrevious(char ch) {
myManager.doPostponedOperationsAndUnblockDocument(myDocument);
String text = myPositionDocument.getText();
int idx = text.lastIndexOf(ch, myCaretRange.getStartOffset());
if (idx == -1) return;
moveToOffset(idx);
}
private void validate(@NotNull PsiElement element) {
if (!element.isValid()) throw new IllegalArgumentException();
if (!PsiTreeUtil.isAncestor(myCopyFile, element, false)) throw new IllegalArgumentException();
}
}
}
@@ -25,4 +25,10 @@ public interface PsiUpdateContext {
* @param element element to navigate to
*/
void moveTo(@NotNull PsiElement element);
/**
* Moves caret to a previous occurrence of character ch. Do nothing if no such occurrence is found
* @param ch character to find
*/
void moveToPrevious(char ch);
}