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.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<PsiElement> 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;

View File

@@ -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));
}
}
});

View File

@@ -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, "<div><caret></div>", "<");
@@ -55,6 +55,15 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase {
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() {
doTestPopup(HtmlFileType.INSTANCE, "<div><caret></div>", "&");
myFixture.type("\n");
@@ -73,6 +82,13 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase {
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() {
doTestPopup(HtmlFileType.INSTANCE, "<div><caret></div>", "the&n");
myFixture.type("bs\n");
@@ -83,9 +99,7 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase {
}
public void testAmpersandInsideEmptyAttributeValue() {
doTestPopup(HtmlFileType.INSTANCE, "<div title='<caret>'></div>", "&");
myFixture.type("ta");
type("r");
doTestPopup(HtmlFileType.INSTANCE, "<div title='<caret>'></div>", "&tar");
myFixture.type("\n");
myFixture.checkResult("<div title='&target;'></div>");
}
@@ -109,9 +123,9 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase {
}
public void testAmpersandInsideAttributeValue() {
doTestPopup(HtmlFileType.INSTANCE, "<div title='v<caret>a'></div>", "&");
myFixture.type("tar\n");
myFixture.checkResult("<div title='v&bigstar;a'></div>");
doTestPopup(HtmlFileType.INSTANCE, "<div title='v<caret>a'></div>", "&tar");
myFixture.type("\n");
myFixture.checkResult("<div title='v&target;a'></div>");
}
public void testDoNotShowPopupAfterQuotedSymbolXhtml() {
@@ -141,6 +155,14 @@ public class HtmlAutopopupTest extends CompletionAutoPopupTestCase {
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() {
myFixture.configureByText(HtmlFileType.INSTANCE, "<html lang='en'><caret>");
type("la");