mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[mod-command] Support injection in ModCommands#psiUpdate
GitOrigin-RevId: 3c191627d242960cb0e8f846529f3a2b5627b5c8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
14893955d4
commit
647205e696
@@ -0,0 +1,16 @@
|
||||
// "Replace with '.keySet().stream()'" "true-preview"
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
class Test {
|
||||
public void testAnyMatch() {
|
||||
@Language("JAVA")
|
||||
String javaText = "import java.util.*;\n" +
|
||||
"\n" +
|
||||
"class X {\n" +
|
||||
" void test(Map<String, Integer> map) {\n" +
|
||||
" map.keySet().stream().<caret>filter(x -> !x.isEmpty()).forEach(s -> System.out.println(\"hello\"+s));\n" +
|
||||
" }\n" +
|
||||
"}\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// "Replace with '.keySet().stream()'" "true-preview"
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
class Test {
|
||||
public void testAnyMatch() {
|
||||
@Language("JAVA")
|
||||
String javaText = "import java.util.*;\n" +
|
||||
"\n" +
|
||||
"class X {\n" +
|
||||
" void test(Map<String, Integer> map) {\n" +
|
||||
" map.entrySet().stream().<caret>map(Map.Entry::getKey).filter(x -> !x.isEmpty()).forEach(s -> System.out.println(\"hello\"+s));\n" +
|
||||
" }\n" +
|
||||
"}\n";
|
||||
}
|
||||
}
|
||||
@@ -54,8 +54,8 @@ public abstract class ModCommandQuickFix implements LocalQuickFix {
|
||||
|
||||
@Override
|
||||
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) {
|
||||
ModCommand modCommand = perform(project, previewDescriptor);
|
||||
PsiFile file = previewDescriptor.getPsiElement().getContainingFile();
|
||||
ModCommand modCommand = perform(project, previewDescriptor);
|
||||
return IntentionPreviewUtils.getModCommandPreview(modCommand, file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
// 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.codeInspection;
|
||||
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.lang.injection.InjectionEditService;
|
||||
import com.intellij.modcommand.*;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.RangeMarker;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.fileEditor.TextEditor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
|
||||
import com.intellij.psi.impl.source.tree.injected.EditorWindowTracker;
|
||||
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;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -67,20 +73,40 @@ public final class ModCommands {
|
||||
public static <E extends PsiElement> @NotNull ModCommand psiUpdate(@NotNull E orig,
|
||||
@NotNull BiConsumer<@NotNull E, @NotNull PsiUpdateContext> updater) {
|
||||
PsiFile origFile = orig.getContainingFile();
|
||||
VirtualFile origVirtualFile = origFile.getOriginalFile().getVirtualFile();
|
||||
Project project = origFile.getProject();
|
||||
ModCommandAction.ActionContext actionContext = createContext(project, origFile);
|
||||
PsiFile copyFile = actionContext.file();
|
||||
String oldText = copyFile.getText();
|
||||
E copy = PsiTreeUtil.findSameElementInCopy(orig, copyFile);
|
||||
PostprocessReformattingAspect aspect = PostprocessReformattingAspect.getInstance(project);
|
||||
PsiDocumentManager manager = PsiDocumentManager.getInstance(project);
|
||||
Document document = copyFile.getViewProvider().getDocument();
|
||||
InjectedLanguageManager injectionManager = InjectedLanguageManager.getInstance(project);
|
||||
Disposable disposable;
|
||||
PsiFile targetFile;
|
||||
Document positionDocument;
|
||||
boolean injected = injectionManager.isInjectedFragment(origFile);
|
||||
if (injected) {
|
||||
PsiLanguageInjectionHost host = Objects.requireNonNull(injectionManager.getInjectionHost(origFile));
|
||||
PsiFile hostFile = host.getContainingFile();
|
||||
PsiFile hostFileCopy = (PsiFile)hostFile.copy();
|
||||
PsiFile injectedFileCopy = getInjectedFileCopy(host, hostFileCopy, orig.getLanguage());
|
||||
disposable = ApplicationManager.getApplication().getService(InjectionEditService.class)
|
||||
.synchronizeWithFragment(injectedFileCopy, document);
|
||||
targetFile = hostFileCopy;
|
||||
origFile = hostFile;
|
||||
positionDocument = hostFileCopy.getViewProvider().getDocument();
|
||||
} else {
|
||||
disposable = null;
|
||||
targetFile = copyFile;
|
||||
positionDocument = document;
|
||||
}
|
||||
String oldText = targetFile.getText();
|
||||
var context = new PsiUpdateContext() {
|
||||
@Nullable RangeMarker mySelectionRange = actionContext.selection().getStartOffset() == -1 ? null :
|
||||
document.createRangeMarker(actionContext.selection().getStartOffset(), actionContext.selection().getEndOffset(), true);
|
||||
positionDocument.createRangeMarker(actionContext.selection().getStartOffset(),
|
||||
actionContext.selection().getEndOffset(), true);
|
||||
@Nullable RangeMarker myCaretRange = actionContext.offset() == -1 ? null :
|
||||
document.createRangeMarker(actionContext.offset(), actionContext.offset(), true);
|
||||
positionDocument.createRangeMarker(actionContext.offset(), actionContext.offset(), true);
|
||||
|
||||
@Override
|
||||
public void select(@NotNull PsiElement element) {
|
||||
@@ -92,8 +118,11 @@ public final class ModCommands {
|
||||
if (myCaretRange != null) {
|
||||
myCaretRange.dispose();
|
||||
}
|
||||
mySelectionRange = document.createRangeMarker(element.getTextRange());
|
||||
myCaretRange = document.createRangeMarker(element.getTextRange().getStartOffset(), element.getTextRange().getStartOffset());
|
||||
if (injected) {
|
||||
element = PsiTreeUtil.findSameElementInCopy(element, targetFile);
|
||||
}
|
||||
mySelectionRange = positionDocument.createRangeMarker(element.getTextRange());
|
||||
myCaretRange = positionDocument.createRangeMarker(element.getTextRange().getStartOffset(), element.getTextRange().getStartOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,7 +132,10 @@ public final class ModCommands {
|
||||
if (myCaretRange != null) {
|
||||
myCaretRange.dispose();
|
||||
}
|
||||
myCaretRange = document.createRangeMarker(element.getTextRange().getStartOffset(), element.getTextRange().getStartOffset());
|
||||
if (injected) {
|
||||
element = PsiTreeUtil.findSameElementInCopy(element, targetFile);
|
||||
}
|
||||
myCaretRange = positionDocument.createRangeMarker(element.getTextRange().getStartOffset(), element.getTextRange().getStartOffset());
|
||||
}
|
||||
|
||||
private void validate(@NotNull PsiElement element) {
|
||||
@@ -115,8 +147,9 @@ public final class ModCommands {
|
||||
() -> aspect.forcePostprocessFormatInside(copyFile, () -> updater.accept(copy, context)));
|
||||
manager.commitDocument(document);
|
||||
manager.doPostponedOperationsAndUnblockDocument(document);
|
||||
String newText = copyFile.getText();
|
||||
String newText = targetFile.getText();
|
||||
ModCommand command = oldText.equals(newText) ? new ModNothing() : new ModUpdatePsiFile(origFile, oldText, newText);
|
||||
VirtualFile origVirtualFile = origFile.getOriginalFile().getVirtualFile();
|
||||
if (origVirtualFile != null) {
|
||||
int start = -1, end = -1, caret = -1;
|
||||
if (context.mySelectionRange != null && context.mySelectionRange.getEndOffset() <= newText.length()) {
|
||||
@@ -136,23 +169,43 @@ public final class ModCommands {
|
||||
if (context.myCaretRange != null) {
|
||||
context.myCaretRange.dispose();
|
||||
}
|
||||
if (disposable != null) {
|
||||
Disposer.dispose(disposable);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
private static @NotNull PsiFile getInjectedFileCopy(@NotNull PsiLanguageInjectionHost host,
|
||||
@NotNull PsiFile hostFileCopy,
|
||||
@NotNull Language injectedLanguage) {
|
||||
InjectedLanguageManager injectionManager = InjectedLanguageManager.getInstance(hostFileCopy.getProject());
|
||||
PsiLanguageInjectionHost hostCopy = PsiTreeUtil.findSameElementInCopy(host, hostFileCopy);
|
||||
var visitor = new PsiLanguageInjectionHost.InjectedPsiVisitor() {
|
||||
private final Language origLanguage = injectedLanguage;
|
||||
PsiFile injectedFileCopy = null;
|
||||
|
||||
@Override
|
||||
public void visit(@NotNull PsiFile injectedPsi, @NotNull List<? extends PsiLanguageInjectionHost.Shred> places) {
|
||||
if (injectedPsi.getLanguage() == origLanguage) {
|
||||
injectedFileCopy = injectedPsi;
|
||||
}
|
||||
}
|
||||
};
|
||||
injectionManager.enumerate(hostCopy, visitor);
|
||||
return Objects.requireNonNull(visitor.injectedFileCopy);
|
||||
}
|
||||
|
||||
private static ModCommandAction.@NotNull ActionContext createContext(Project project, PsiFile origFile) {
|
||||
var manager = InjectedLanguageManager.getInstance(project);
|
||||
VirtualFile origVirtualFile = origFile.getOriginalFile().getVirtualFile();
|
||||
boolean injectedFragment = manager.isInjectedFragment(origFile);
|
||||
VirtualFile origVirtualFile = (injectedFragment ? manager.getTopLevelFile(origFile) : origFile).getOriginalFile().getVirtualFile();
|
||||
Editor origEditor =
|
||||
origVirtualFile != null && FileEditorManager.getInstance(project).getSelectedEditor(origVirtualFile) instanceof TextEditor textEditor
|
||||
?
|
||||
textEditor.getEditor()
|
||||
: null;
|
||||
boolean injectedFragment = manager.isInjectedFragment(origFile);
|
||||
int offset = -1, start = -1, end = -1;
|
||||
int offset = 0, start = 0, end = 0;
|
||||
if (origEditor != null) {
|
||||
if (injectedFragment) {
|
||||
origEditor = EditorWindowTracker.getInstance().getEditorForInjectedFile(origEditor, origFile);
|
||||
}
|
||||
offset = origEditor.getCaretModel().getOffset();
|
||||
start = origEditor.getSelectionModel().getSelectionStart();
|
||||
end = origEditor.getSelectionModel().getSelectionEnd();
|
||||
@@ -164,9 +217,6 @@ public final class ModCommands {
|
||||
copyFile = PsiFileFactory.getInstance(project).createFileFromText(
|
||||
origFile.getName(), origFile.getFileType(), manager.getUnescapedText(origFile),
|
||||
LocalTimeCounter.currentTime(), false);
|
||||
offset = manager.mapInjectedOffsetToUnescaped(origFile, offset);
|
||||
start = manager.mapInjectedOffsetToUnescaped(origFile, start);
|
||||
end = manager.mapInjectedOffsetToUnescaped(origFile, end);
|
||||
}
|
||||
return new ModCommandAction.ActionContext(project, copyFile, offset, TextRange.create(start, end));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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.lang.injection;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface InjectionEditService {
|
||||
/**
|
||||
* Synchronizes the content of injectedFile with the content of copyDocument.
|
||||
*
|
||||
* @param injectedFile file to apply changes to
|
||||
* @param copyDocument unescaped non-physical copy of the original injected file,
|
||||
* where additional changes will be applied.
|
||||
* @return disposable to dispose when synchronization is not needed anymore
|
||||
*/
|
||||
@NotNull Disposable synchronizeWithFragment(@NotNull PsiFile injectedFile, @NotNull Document copyDocument);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// 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.intention.impl;
|
||||
|
||||
import com.intellij.injected.editor.InjectedFileChangesHandler;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.lang.injection.InjectionEditService;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.editor.event.DocumentListener;
|
||||
import com.intellij.openapi.editor.impl.ImaginaryEditor;
|
||||
import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiLanguageInjectionHost;
|
||||
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtilBase;
|
||||
import com.intellij.psi.impl.source.tree.injected.Place;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class InjectionEditServiceImpl implements InjectionEditService {
|
||||
@Override
|
||||
public @NotNull Disposable synchronizeWithFragment(@NotNull PsiFile injectedFile, @NotNull Document copyDocument) {
|
||||
Place shreds = InjectedLanguageUtilBase.getShreds(injectedFile);
|
||||
Project project = injectedFile.getProject();
|
||||
PsiLanguageInjectionHost host = Objects.requireNonNull(InjectedLanguageManager.getInstance(project).getInjectionHost(injectedFile));
|
||||
Editor editor = new ImaginaryEditor(project, host.getContainingFile().getViewProvider().getDocument());
|
||||
InjectedFileChangesHandler handler = QuickEditHandler.getHandler(injectedFile, editor, shreds, copyDocument);
|
||||
copyDocument.addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void documentChanged(@NotNull DocumentEvent event) {
|
||||
handler.commitToOriginal(event);
|
||||
}
|
||||
});
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
+17
-11
@@ -151,17 +151,7 @@ public final class QuickEditHandler extends UserDataHolderBase implements Dispos
|
||||
}, this);
|
||||
|
||||
|
||||
InjectedFileChangesHandlerProvider changesHandlerFactory =
|
||||
InjectedFileChangesHandlerProvider.EP.forLanguage(firstShred.getHost().getLanguage());
|
||||
if (changesHandlerFactory != null) {
|
||||
myEditChangesHandler = changesHandlerFactory.createFileChangesHandler(shreds, editor, myNewDocument, injectedFile);
|
||||
}
|
||||
else if (ContainerUtil.or(shreds, it -> InjectionMeta.INJECTION_INDENT.get(it.getHost()) != null)) {
|
||||
myEditChangesHandler = new IndentAwareInjectedFileChangesHandler(shreds, editor, myNewDocument, injectedFile);
|
||||
}
|
||||
else {
|
||||
myEditChangesHandler = new CommonInjectedFileChangesHandler(shreds, editor, myNewDocument, injectedFile);
|
||||
}
|
||||
myEditChangesHandler = getHandler(injectedFile, editor, shreds, myNewDocument);
|
||||
Disposer.register(this, myEditChangesHandler);
|
||||
|
||||
StreamEx.of(shreds).map(it -> it.getHost()).nonNull().distinct().forEach(h -> {
|
||||
@@ -181,6 +171,22 @@ public final class QuickEditHandler extends UserDataHolderBase implements Dispos
|
||||
myNewDocument.addDocumentListener(this, this);
|
||||
}
|
||||
|
||||
static InjectedFileChangesHandler getHandler(@NotNull PsiFile injectedFile,
|
||||
@NotNull Editor editor,
|
||||
@NotNull Place shreds,
|
||||
@NotNull Document document) {
|
||||
PsiLanguageInjectionHost host = ContainerUtil.getFirstItem(shreds).getHost();
|
||||
InjectedFileChangesHandlerProvider changesHandlerFactory =
|
||||
host == null ? null : InjectedFileChangesHandlerProvider.EP.forLanguage(host.getLanguage());
|
||||
if (changesHandlerFactory != null) {
|
||||
return changesHandlerFactory.createFileChangesHandler(shreds, editor, document, injectedFile);
|
||||
}
|
||||
if (ContainerUtil.or(shreds, it -> InjectionMeta.INJECTION_INDENT.get(it.getHost()) != null)) {
|
||||
return new IndentAwareInjectedFileChangesHandler(shreds, editor, document, injectedFile);
|
||||
}
|
||||
return new CommonInjectedFileChangesHandler(shreds, editor, document, injectedFile);
|
||||
}
|
||||
|
||||
private static final Key<Set<QuickEditHandler>> QUICK_EDIT_HANDLERS = Key.create("QUICK_EDIT_HANDLERS");
|
||||
|
||||
public static @NotNull Set<QuickEditHandler> getFragmentEditors(@NotNull PsiLanguageInjectionHost host) {
|
||||
|
||||
@@ -97,6 +97,8 @@
|
||||
serviceImplementation="com.intellij.formatting.FormattingProgressTaskFactory"/>
|
||||
<applicationService serviceInterface="com.intellij.openapi.editor.ex.util.EditorFacade"
|
||||
serviceImplementation="com.intellij.formatting.EditorFacadeImpl"/>
|
||||
<applicationService serviceInterface="com.intellij.lang.injection.InjectionEditService"
|
||||
serviceImplementation="com.intellij.codeInsight.intention.impl.InjectionEditServiceImpl"/>
|
||||
|
||||
<applicationService serviceInterface="com.intellij.concurrency.JobLauncher"
|
||||
serviceImplementation="com.intellij.concurrency.JobLauncherImpl"/>
|
||||
|
||||
Reference in New Issue
Block a user