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
This commit is contained in:
Piotr Tomiak
2021-09-09 10:18:54 +02:00
committed by intellij-monorepo-bot
parent 37e1a05dac
commit eb09877c1f
3 changed files with 60 additions and 30 deletions

View File

@@ -11,7 +11,9 @@ import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PlatformPatterns; import com.intellij.patterns.PlatformPatterns;
import com.intellij.patterns.StandardPatterns;
import com.intellij.patterns.XmlPatterns; import com.intellij.patterns.XmlPatterns;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile; 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.HtmlUtil;
import com.intellij.xml.util.XmlUtil; import com.intellij.xml.util.XmlUtil;
import com.intellij.xml.util.documentation.MimeTypeDictionary; import com.intellij.xml.util.documentation.MimeTypeDictionary;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.*;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*; import javax.swing.*;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@@ -86,9 +85,7 @@ public class HtmlCompletionContributor extends CompletionContributor implements
} }
} }
}); });
extend(CompletionType.BASIC, psiElement(XmlTokenType.XML_DATA_CHARACTERS) extend(CompletionType.BASIC, getHtmlElementInTextPattern(),
.withParent(psiElement(XmlText.class))
.inFile(PlatformPatterns.psiFile(HtmlFileImpl.class)),
new HtmlElementInTextCompletionProvider()); new HtmlElementInTextCompletionProvider());
} }
@@ -196,7 +193,7 @@ public class HtmlCompletionContributor extends CompletionContributor implements
if ((elementType == XmlTokenType.XML_DATA_CHARACTERS if ((elementType == XmlTokenType.XML_DATA_CHARACTERS
|| element.getNode().getElementType() == XmlTokenType.XML_WHITE_SPACE) || element.getNode().getElementType() == XmlTokenType.XML_WHITE_SPACE)
&& element.getParent() instanceof XmlText && (element.getParent() instanceof XmlText || element.getParent() instanceof XmlDocument)
) { ) {
return !element.getText().endsWith("<"); return !element.getText().endsWith("<");
} }
@@ -207,9 +204,23 @@ public class HtmlCompletionContributor extends CompletionContributor implements
return false; return false;
} }
public static CompletionResultSet withoutLiveTemplatesWeigher(@NotNull CompletionResultSet result, @ApiStatus.Internal
@NotNull CompletionParameters parameters) { public static ElementPattern<PsiElement> getHtmlElementInTextPattern() {
return result.withRelevanceSorter(withoutLiveTemplatesWeigher(null, parameters, result.getPrefixMatcher())); 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, private static CompletionSorter withoutLiveTemplatesWeigher(@Nullable CompletionSorter sorter,
@@ -235,13 +246,7 @@ public class HtmlCompletionContributor extends CompletionContributor implements
offsets = offsets.copyWithReplacement(offset, offset, "<"); offsets = offsets.copyWithReplacement(offset, offset, "<");
PsiElement tag = doIfNotNull(offsets.getFile().findElementAt(offset + 1), PsiElement::getParent); PsiElement tag = doIfNotNull(offsets.getFile().findElementAt(offset + 1), PsiElement::getParent);
if (tag instanceof XmlTag) { if (tag instanceof XmlTag) {
// We want live templates to be mixed with tags and other contributions CompletionResultSet patchedResultSet = patchResultSetForHtmlElementInTextCompletion(result, parameters);
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;
for (LookupElement variant : TagNameReferenceCompletionProvider.getTagNameVariants((XmlTag)tag, "")) { for (LookupElement variant : TagNameReferenceCompletionProvider.getTagNameVariants((XmlTag)tag, "")) {
LookupElement decorated = new LookupElementDecorator<>(variant) { LookupElement decorated = new LookupElementDecorator<>(variant) {
@@ -289,7 +294,7 @@ public class HtmlCompletionContributor extends CompletionContributor implements
@Override @Override
public void afterAppend(char c) { public void afterAppend(char c) {
// Select first item when two chars are typed after '&' // Select first item when two chars are typed after '&'
if (lookup.getCurrentItemOrEmpty() == null && hasTwoCharAfterAmp(lookup)) { if (lookup.getCurrentItemOrEmpty() == null && hasTwoCharsAfterAmp(lookup)) {
lookup.setSelectedIndex(0); lookup.setSelectedIndex(0);
} }
} }
@@ -301,7 +306,7 @@ public class HtmlCompletionContributor extends CompletionContributor implements
if (currentCompletion != null if (currentCompletion != null
&& currentCompletion.isAutopopupCompletion() && currentCompletion.isAutopopupCompletion()
&& !lookup.isSelectionTouched() && !lookup.isSelectionTouched()
&& !hasTwoCharAfterAmp(lookup)) { && !hasTwoCharsAfterAmp(lookup)) {
// Deselect topmost item // Deselect topmost item
lookup.getList().setSelectedValue(null, false); lookup.getList().setSelectedValue(null, false);
ListSelectionModel selectionModel = lookup.getList().getSelectionModel(); 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 start = Math.max(lookup.getLookupStart() - 1, 0);
int end = lookup.getEditor().getCaretModel().getOffset(); int end = lookup.getEditor().getCaretModel().getOffset();
if (end - start < 3) return false; if (end - start < 3) return false;

View File

@@ -100,8 +100,11 @@ public final class XmlCompletionContributor extends CompletionContributor {
else if (prefix.contains("&")) { else if (prefix.contains("&")) {
prefix = prefix.substring(prefix.indexOf("&") + 1); prefix = prefix.substring(prefix.indexOf("&") + 1);
} }
matcher = matcher.cloneWithPrefix(prefix);
addEntityRefCompletions(position, result.withPrefixMatcher(prefix)); if (parameters.getInvocationCount() == 0) {
matcher = new StartOnlyMatcher(matcher);
}
addEntityRefCompletions(position, result.withPrefixMatcher(matcher));
} }
} }
}); });

View File

@@ -23,7 +23,7 @@ import com.intellij.openapi.fileTypes.FileType;
import com.intellij.testFramework.fixtures.CompletionAutoPopupTestCase; import com.intellij.testFramework.fixtures.CompletionAutoPopupTestCase;
import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtil;
public class HtmlAutopopupTest extends CompletionAutoPopupTestCase { public class HtmlAutoPopupTest extends CompletionAutoPopupTestCase {
public void testAfterTagOpen() { public void testAfterTagOpen() {
doTestPopup(HtmlFileType.INSTANCE, "<div><caret></div>", "<"); doTestPopup(HtmlFileType.INSTANCE, "<div><caret></div>", "<");
@@ -55,6 +55,15 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase {
myFixture.checkResult("<div>&star;</div>"); myFixture.checkResult("<div>&star;</div>");
} }
public void testAfterAmpersandEmptyFile() {
doTestPopup(HtmlFileType.INSTANCE, "&<caret>", "s");
assertNull(getLookup().getCurrentItemOrEmpty());
myFixture.type("t");
type("a");
myFixture.type("\n");
myFixture.checkResult("&star;");
}
public void testAfterAmpersandZeroChars() { public void testAfterAmpersandZeroChars() {
doTestPopup(HtmlFileType.INSTANCE, "<div><caret></div>", "&"); doTestPopup(HtmlFileType.INSTANCE, "<div><caret></div>", "&");
myFixture.type("\n"); myFixture.type("\n");
@@ -73,6 +82,13 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase {
myFixture.checkResult("<div>&target;</div>"); myFixture.checkResult("<div>&target;</div>");
} }
public void testAfterAmpersandContents() {
doTestPopup(HtmlFileType.INSTANCE, "<div><caret></div>", "&bb");
assertSameElements(myFixture.getLookupElementStrings(), "bbrk", "bbrktbrk");
myFixture.completeBasic();
assertSameElements(myFixture.getLookupElementStrings(), "bbrk", "bbrktbrk", "lbbrk", "rbbrk");
}
public void testAfterAmpersandWithPrefix() { public void testAfterAmpersandWithPrefix() {
doTestPopup(HtmlFileType.INSTANCE, "<div><caret></div>", "the&n"); doTestPopup(HtmlFileType.INSTANCE, "<div><caret></div>", "the&n");
myFixture.type("bs\n"); myFixture.type("bs\n");
@@ -83,9 +99,7 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase {
} }
public void testAmpersandInsideEmptyAttributeValue() { public void testAmpersandInsideEmptyAttributeValue() {
doTestPopup(HtmlFileType.INSTANCE, "<div title='<caret>'></div>", "&"); doTestPopup(HtmlFileType.INSTANCE, "<div title='<caret>'></div>", "&tar");
myFixture.type("ta");
type("r");
myFixture.type("\n"); myFixture.type("\n");
myFixture.checkResult("<div title='&target;'></div>"); myFixture.checkResult("<div title='&target;'></div>");
} }
@@ -109,9 +123,9 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase {
} }
public void testAmpersandInsideAttributeValue() { public void testAmpersandInsideAttributeValue() {
doTestPopup(HtmlFileType.INSTANCE, "<div title='v<caret>a'></div>", "&"); doTestPopup(HtmlFileType.INSTANCE, "<div title='v<caret>a'></div>", "&tar");
myFixture.type("tar\n"); myFixture.type("\n");
myFixture.checkResult("<div title='v&bigstar;a'></div>"); myFixture.checkResult("<div title='v&target;a'></div>");
} }
public void testDoNotShowPopupAfterQuotedSymbolXhtml() { public void testDoNotShowPopupAfterQuotedSymbolXhtml() {
@@ -141,6 +155,14 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase {
myFixture.checkResult("<div>The p\n</div>"); myFixture.checkResult("<div>The p\n</div>");
} }
public void testTypingInHtmlTextEmptyFile() {
myFixture.configureByText(HtmlFileType.INSTANCE, "<caret>");
type("p");
assertNull(getLookup().getCurrentItemOrEmpty());
myFixture.type("\n");
myFixture.checkResult("p\n");
}
public void testStartsWithCharMatchingAutoPopup() { public void testStartsWithCharMatchingAutoPopup() {
myFixture.configureByText(HtmlFileType.INSTANCE, "<html lang='en'><caret>"); myFixture.configureByText(HtmlFileType.INSTANCE, "<html lang='en'><caret>");
type("la"); type("la");