From eb09877c1f7996bd794a29d8cfc0afb2bbf10cd6 Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Thu, 9 Sep 2021 10:18:54 +0200 Subject: [PATCH] WEB-49060 Completion without typing < - code cleanup, make it work in an empty document and no middle match in char entities when auto-popup GitOrigin-RevId: b55f56cd49052ecd1e656f209507a419397e7dc8 --- .../completion/HtmlCompletionContributor.java | 47 ++++++++++--------- .../completion/XmlCompletionContributor.java | 7 ++- ...opopupTest.java => HtmlAutoPopupTest.java} | 36 +++++++++++--- 3 files changed, 60 insertions(+), 30 deletions(-) rename xml/tests/src/com/intellij/codeInsight/completion/{HtmlAutopopupTest.java => HtmlAutoPopupTest.java} (84%) diff --git a/xml/impl/src/com/intellij/codeInsight/completion/HtmlCompletionContributor.java b/xml/impl/src/com/intellij/codeInsight/completion/HtmlCompletionContributor.java index 435db3a1259e..0cb5867455af 100644 --- a/xml/impl/src/com/intellij/codeInsight/completion/HtmlCompletionContributor.java +++ b/xml/impl/src/com/intellij/codeInsight/completion/HtmlCompletionContributor.java @@ -11,7 +11,9 @@ import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.patterns.ElementPattern; import com.intellij.patterns.PlatformPatterns; +import com.intellij.patterns.StandardPatterns; import com.intellij.patterns.XmlPatterns; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; @@ -28,10 +30,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.xml.util.HtmlUtil; import com.intellij.xml.util.XmlUtil; import com.intellij.xml.util.documentation.MimeTypeDictionary; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.*; import javax.swing.*; import java.nio.charset.Charset; @@ -86,9 +85,7 @@ public class HtmlCompletionContributor extends CompletionContributor implements } } }); - extend(CompletionType.BASIC, psiElement(XmlTokenType.XML_DATA_CHARACTERS) - .withParent(psiElement(XmlText.class)) - .inFile(PlatformPatterns.psiFile(HtmlFileImpl.class)), + extend(CompletionType.BASIC, getHtmlElementInTextPattern(), new HtmlElementInTextCompletionProvider()); } @@ -196,7 +193,7 @@ public class HtmlCompletionContributor extends CompletionContributor implements if ((elementType == XmlTokenType.XML_DATA_CHARACTERS || element.getNode().getElementType() == XmlTokenType.XML_WHITE_SPACE) - && element.getParent() instanceof XmlText + && (element.getParent() instanceof XmlText || element.getParent() instanceof XmlDocument) ) { return !element.getText().endsWith("<"); } @@ -207,9 +204,23 @@ public class HtmlCompletionContributor extends CompletionContributor implements return false; } - public static CompletionResultSet withoutLiveTemplatesWeigher(@NotNull CompletionResultSet result, - @NotNull CompletionParameters parameters) { - return result.withRelevanceSorter(withoutLiveTemplatesWeigher(null, parameters, result.getPrefixMatcher())); + @ApiStatus.Internal + public static ElementPattern getHtmlElementInTextPattern() { + return psiElement(XmlTokenType.XML_DATA_CHARACTERS) + .withParent(StandardPatterns.or(psiElement(XmlText.class), psiElement(XmlDocument.class))) + .inFile(PlatformPatterns.psiFile(HtmlFileImpl.class)); + } + + @ApiStatus.Internal + public static CompletionResultSet patchResultSetForHtmlElementInTextCompletion(@NotNull CompletionResultSet result, + @NotNull CompletionParameters parameters) { + // We want live templates to be mixed with tags and other contributions + result = result.withRelevanceSorter(withoutLiveTemplatesWeigher(null, parameters, result.getPrefixMatcher())); + if (parameters.getInvocationCount() == 0) { + // We only want results which start with the prefix first char + result = result.withPrefixMatcher(new StartOnlyMatcher(result.getPrefixMatcher())); + } + return result; } private static CompletionSorter withoutLiveTemplatesWeigher(@Nullable CompletionSorter sorter, @@ -235,13 +246,7 @@ public class HtmlCompletionContributor extends CompletionContributor implements offsets = offsets.copyWithReplacement(offset, offset, "<"); PsiElement tag = doIfNotNull(offsets.getFile().findElementAt(offset + 1), PsiElement::getParent); if (tag instanceof XmlTag) { - // We want live templates to be mixed with tags and other contributions - result = withoutLiveTemplatesWeigher(result, parameters); - if (parameters.getInvocationCount() == 0) { - // We only want results which start with the prefix first char - result = result.withPrefixMatcher(new StartOnlyMatcher(result.getPrefixMatcher())); - } - CompletionResultSet patchedResultSet = result; + CompletionResultSet patchedResultSet = patchResultSetForHtmlElementInTextCompletion(result, parameters); for (LookupElement variant : TagNameReferenceCompletionProvider.getTagNameVariants((XmlTag)tag, "")) { LookupElement decorated = new LookupElementDecorator<>(variant) { @@ -289,7 +294,7 @@ public class HtmlCompletionContributor extends CompletionContributor implements @Override public void afterAppend(char c) { // Select first item when two chars are typed after '&' - if (lookup.getCurrentItemOrEmpty() == null && hasTwoCharAfterAmp(lookup)) { + if (lookup.getCurrentItemOrEmpty() == null && hasTwoCharsAfterAmp(lookup)) { lookup.setSelectedIndex(0); } } @@ -301,7 +306,7 @@ public class HtmlCompletionContributor extends CompletionContributor implements if (currentCompletion != null && currentCompletion.isAutopopupCompletion() && !lookup.isSelectionTouched() - && !hasTwoCharAfterAmp(lookup)) { + && !hasTwoCharsAfterAmp(lookup)) { // Deselect topmost item lookup.getList().setSelectedValue(null, false); ListSelectionModel selectionModel = lookup.getList().getSelectionModel(); @@ -313,7 +318,7 @@ public class HtmlCompletionContributor extends CompletionContributor implements } } - private static boolean hasTwoCharAfterAmp(LookupImpl lookup) { + private static boolean hasTwoCharsAfterAmp(LookupImpl lookup) { int start = Math.max(lookup.getLookupStart() - 1, 0); int end = lookup.getEditor().getCaretModel().getOffset(); if (end - start < 3) return false; diff --git a/xml/impl/src/com/intellij/codeInsight/completion/XmlCompletionContributor.java b/xml/impl/src/com/intellij/codeInsight/completion/XmlCompletionContributor.java index 3c4f2dae23a9..41e78572d61d 100644 --- a/xml/impl/src/com/intellij/codeInsight/completion/XmlCompletionContributor.java +++ b/xml/impl/src/com/intellij/codeInsight/completion/XmlCompletionContributor.java @@ -100,8 +100,11 @@ public final class XmlCompletionContributor extends CompletionContributor { else if (prefix.contains("&")) { prefix = prefix.substring(prefix.indexOf("&") + 1); } - - addEntityRefCompletions(position, result.withPrefixMatcher(prefix)); + matcher = matcher.cloneWithPrefix(prefix); + if (parameters.getInvocationCount() == 0) { + matcher = new StartOnlyMatcher(matcher); + } + addEntityRefCompletions(position, result.withPrefixMatcher(matcher)); } } }); diff --git a/xml/tests/src/com/intellij/codeInsight/completion/HtmlAutopopupTest.java b/xml/tests/src/com/intellij/codeInsight/completion/HtmlAutoPopupTest.java similarity index 84% rename from xml/tests/src/com/intellij/codeInsight/completion/HtmlAutopopupTest.java rename to xml/tests/src/com/intellij/codeInsight/completion/HtmlAutoPopupTest.java index e77b9ba525ac..1c6ddcb8e10a 100644 --- a/xml/tests/src/com/intellij/codeInsight/completion/HtmlAutopopupTest.java +++ b/xml/tests/src/com/intellij/codeInsight/completion/HtmlAutoPopupTest.java @@ -23,7 +23,7 @@ import com.intellij.openapi.fileTypes.FileType; import com.intellij.testFramework.fixtures.CompletionAutoPopupTestCase; import com.intellij.util.containers.ContainerUtil; -public class HtmlAutopopupTest extends CompletionAutoPopupTestCase { +public class HtmlAutoPopupTest extends CompletionAutoPopupTestCase { public void testAfterTagOpen() { doTestPopup(HtmlFileType.INSTANCE, "
", "<"); @@ -55,6 +55,15 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase { myFixture.checkResult("
"); } + public void testAfterAmpersandEmptyFile() { + doTestPopup(HtmlFileType.INSTANCE, "&", "s"); + assertNull(getLookup().getCurrentItemOrEmpty()); + myFixture.type("t"); + type("a"); + myFixture.type("\n"); + myFixture.checkResult("☆"); + } + public void testAfterAmpersandZeroChars() { doTestPopup(HtmlFileType.INSTANCE, "
", "&"); myFixture.type("\n"); @@ -73,6 +82,13 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase { myFixture.checkResult("
"); } + public void testAfterAmpersandContents() { + doTestPopup(HtmlFileType.INSTANCE, "
", "&bb"); + assertSameElements(myFixture.getLookupElementStrings(), "bbrk", "bbrktbrk"); + myFixture.completeBasic(); + assertSameElements(myFixture.getLookupElementStrings(), "bbrk", "bbrktbrk", "lbbrk", "rbbrk"); + } + public void testAfterAmpersandWithPrefix() { doTestPopup(HtmlFileType.INSTANCE, "
", "the&n"); myFixture.type("bs\n"); @@ -83,9 +99,7 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase { } public void testAmpersandInsideEmptyAttributeValue() { - doTestPopup(HtmlFileType.INSTANCE, "
", "&"); - myFixture.type("ta"); - type("r"); + doTestPopup(HtmlFileType.INSTANCE, "
", "&tar"); myFixture.type("\n"); myFixture.checkResult("
"); } @@ -109,9 +123,9 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase { } public void testAmpersandInsideAttributeValue() { - doTestPopup(HtmlFileType.INSTANCE, "
", "&"); - myFixture.type("tar\n"); - myFixture.checkResult("
"); + doTestPopup(HtmlFileType.INSTANCE, "
", "&tar"); + myFixture.type("\n"); + myFixture.checkResult("
"); } public void testDoNotShowPopupAfterQuotedSymbolXhtml() { @@ -141,6 +155,14 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase { myFixture.checkResult("
The p\n
"); } + public void testTypingInHtmlTextEmptyFile() { + myFixture.configureByText(HtmlFileType.INSTANCE, ""); + type("p"); + assertNull(getLookup().getCurrentItemOrEmpty()); + myFixture.type("\n"); + myFixture.checkResult("p\n"); + } + public void testStartsWithCharMatchingAutoPopup() { myFixture.configureByText(HtmlFileType.INSTANCE, ""); type("la");