IDEA-CR-52982: GO-8123 fix getOriginalElement during postfix template completion

GitOrigin-RevId: 73f3045e4c07b5ca65e512877d55f3d2dde436af
This commit is contained in:
Dmitry Neverov
2019-09-30 13:36:53 +00:00
committed by intellij-monorepo-bot
parent b84c228f47
commit 10bf03cc53
5 changed files with 138 additions and 13 deletions
@@ -0,0 +1,7 @@
public class Foo {
void m(boolean x) {
x.foo<caret>
}
void m2() {
}
}
@@ -0,0 +1,7 @@
public class Foo {
void m(boolean x) {
System.out.println();<caret>
}
void m2() {
}
}
@@ -0,0 +1,86 @@
// Copyright 2000-2019 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.
package com.intellij.java.codeInsight.template.postfix.templates;
import com.intellij.codeInsight.completion.CompletionUtilCoreImpl;
import com.intellij.codeInsight.template.postfix.settings.PostfixTemplateStorage;
import com.intellij.codeInsight.template.postfix.templates.JavaPostfixTemplateProvider;
import com.intellij.codeInsight.template.postfix.templates.PostfixTemplate;
import com.intellij.codeInsight.template.postfix.templates.editable.JavaEditablePostfixTemplate;
import com.intellij.codeInsight.template.postfix.templates.editable.JavaPostfixTemplateExpressionCondition;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
import static java.util.Arrays.asList;
public class OriginalElementPostfixTemplateTest extends PostfixTemplateTestCase {
private static final JavaPostfixTemplateProvider PROVIDER = new JavaPostfixTemplateProvider();
private Set<PostfixTemplate> myOriginalTemplates;
@Override
public void setUp() throws Exception {
super.setUp();
myOriginalTemplates = PROVIDER.getTemplates();
// Register a custom condition which returns true if it can get an original element of the next method.
// This emulates conditions in some languages e.g. in go where resolve involves getOriginalElement() calls.
PostfixTemplate template = new JavaEditablePostfixTemplate(
"myId", "foo", "System.out.println();$END$", "",
ContainerUtil.set(new OriginalElementCondition()),
LanguageLevel.JDK_1_8, true, PROVIDER);
PostfixTemplateStorage.getInstance().setTemplates(PROVIDER, asList(template));
}
@Override
protected void tearDown() throws Exception {
try {
PostfixTemplateStorage.getInstance().setTemplates(PROVIDER, myOriginalTemplates);
}
catch (Throwable e) {
addSuppressedException(e);
}
finally {
super.tearDown();
}
}
public void testOriginalElement() {
doTest();
}
@NotNull
@Override
protected String getSuffix() {
return "originalElement";
}
private static class OriginalElementCondition implements JavaPostfixTemplateExpressionCondition {
@NotNull
@Override
public String getPresentableName() {
return "customCondition";
}
@NotNull
@Override
public String getId() {
return "customCondition";
}
@Override
public boolean value(@NotNull PsiExpression expression) {
PsiMethod method = PsiTreeUtil.getParentOfType(expression, PsiMethod.class);
PsiMethod nextMethod = PsiTreeUtil.getNextSiblingOfType(method, PsiMethod.class);
return nextMethod != null && CompletionUtilCoreImpl.getOriginalElement(nextMethod) != null;
}
@Override
public void serializeTo(@NotNull Element element) {
}
}
}
@@ -18,14 +18,14 @@ import java.util.LinkedList;
/**
* @author peter
*/
class OffsetTranslator implements Disposable {
public class OffsetTranslator implements Disposable {
static final Key<OffsetTranslator> RANGE_TRANSLATION = Key.create("completion.rangeTranslation");
private final PsiFile myOriginalFile;
private final Document myCopyDocument;
private final LinkedList<DocumentEvent> myTranslation = new LinkedList<>();
OffsetTranslator(Document originalDocument, PsiFile originalFile, Document copyDocument, int start, int end, String replacement) {
public OffsetTranslator(Document originalDocument, PsiFile originalFile, Document copyDocument, int start, int end, String replacement) {
myOriginalFile = originalFile;
myCopyDocument = copyDocument;
myCopyDocument.putUserData(RANGE_TRANSLATION, this);
@@ -2,6 +2,7 @@
package com.intellij.codeInsight.template.postfix.templates;
import com.google.common.collect.Sets;
import com.intellij.codeInsight.completion.OffsetTranslator;
import com.intellij.codeInsight.template.CustomLiveTemplateBase;
import com.intellij.codeInsight.template.CustomTemplateCallback;
import com.intellij.codeInsight.template.impl.CustomLiveTemplateLookupElement;
@@ -11,6 +12,7 @@ import com.intellij.diagnostic.AttachmentFactory;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.undo.UndoConstants;
@@ -20,6 +22,7 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
@@ -205,19 +208,26 @@ public class PostfixLiveTemplate extends CustomLiveTemplateBase {
int offset) {
Collection<CustomLiveTemplateLookupElement> result = new HashSet<>();
CustomTemplateCallback callback = new CustomTemplateCallback(editor, file);
for (PostfixTemplateProvider provider : LanguagePostfixTemplate.LANG_EP.allForLanguage(getLanguage(callback))) {
ProgressManager.checkCanceled();
String key = computeTemplateKeyWithoutContextChecking(callback);
if (key != null && editor.getCaretModel().getCaretCount() == 1) {
Condition<PostfixTemplate> isApplicationTemplateFunction = createIsApplicationTemplateFunction(provider, key, file, editor);
for (PostfixTemplate postfixTemplate : PostfixTemplatesUtils.getAvailableTemplates(provider)) {
ProgressManager.checkCanceled();
if (isApplicationTemplateFunction.value(postfixTemplate)) {
result.add(new PostfixTemplateLookupElement(this, postfixTemplate, postfixTemplate.getKey(), provider, false));
Disposable parentDisposable = Disposer.newDisposable();
try {
for (PostfixTemplateProvider provider : LanguagePostfixTemplate.LANG_EP.allForLanguage(getLanguage(callback))) {
ProgressManager.checkCanceled();
String key = computeTemplateKeyWithoutContextChecking(callback);
if (key != null && editor.getCaretModel().getCaretCount() == 1) {
Condition<PostfixTemplate> isApplicationTemplateFunction =
createIsApplicationTemplateFunction(provider, key, file, editor, parentDisposable);
for (PostfixTemplate postfixTemplate : PostfixTemplatesUtils.getAvailableTemplates(provider)) {
ProgressManager.checkCanceled();
if (isApplicationTemplateFunction.value(postfixTemplate)) {
result.add(new PostfixTemplateLookupElement(this, postfixTemplate, postfixTemplate.getKey(), provider, false));
}
}
}
}
}
finally {
Disposer.dispose(parentDisposable);
}
return result;
}
@@ -256,7 +266,8 @@ public class PostfixLiveTemplate extends CustomLiveTemplateBase {
private static Condition<PostfixTemplate> createIsApplicationTemplateFunction(@NotNull final PostfixTemplateProvider provider,
@NotNull String key,
@NotNull PsiFile file,
@NotNull Editor editor) {
@NotNull Editor editor,
@NotNull Disposable parentDisposable) {
if (file.getFileType().isBinary()) {
return Conditions.alwaysFalse();
}
@@ -279,6 +290,14 @@ public class PostfixLiveTemplate extends CustomLiveTemplateBase {
return Conditions.alwaysFalse();
}
Document originalDocument = file.getViewProvider().getDocument();
if (originalDocument != null) {
// The copy document doesn't contain live template key.
// Register offset translator to make getOriginalElement() work in the copy.
OffsetTranslator translator = new OffsetTranslator(originalDocument, file, copyDocument, newOffset, currentOffset, "");
Disposer.register(parentDisposable, translator);
}
final PsiElement context = CustomTemplateCallback.getContext(copyFile, positiveOffset(newOffset));
final Document finalCopyDocument = copyDocument;
return template -> template != null && template.isEnabled(provider) && template.isApplicable(context, finalCopyDocument, newOffset);
@@ -314,7 +333,13 @@ public class PostfixLiveTemplate extends CustomLiveTemplateBase {
@NotNull PsiFile file,
@NotNull Editor editor,
@Nullable PostfixTemplate template) {
return createIsApplicationTemplateFunction(provider, key, file, editor).value(template);
Disposable parentDisposable = Disposer.newDisposable();
try {
return createIsApplicationTemplateFunction(provider, key, file, editor, parentDisposable).value(template);
}
finally {
Disposer.dispose(parentDisposable);
}
}
@NotNull