mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
js regexp: support key=value unicode properties (part of WEB-27639)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -84,6 +84,11 @@ public enum RegExpCapability {
|
||||
*/
|
||||
CARET_NEGATED_PROPERTIES,
|
||||
|
||||
/**
|
||||
* supports properties with name and value like \p{name=value}
|
||||
*/
|
||||
PROPERTY_VALUES,
|
||||
|
||||
/**
|
||||
* \\u, \l, \\U, \L, and \E
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.intellij.codeInsight.completion.*;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder;
|
||||
import com.intellij.codeInsight.lookup.TailTypeDecorator;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
@@ -13,9 +14,11 @@ import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.patterns.ElementPattern;
|
||||
import com.intellij.patterns.PsiElementPattern;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.PlatformIcons;
|
||||
import com.intellij.util.ProcessingContext;
|
||||
import com.intellij.util.ui.EmptyIcon;
|
||||
import org.intellij.lang.regexp.psi.RegExpProperty;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -62,6 +65,9 @@ public final class RegExpCompletionContributor extends CompletionContributor {
|
||||
|
||||
final ElementPattern<PsiElement> propertyPattern = psiElement().withText("\\\\p");
|
||||
extend(CompletionType.BASIC, psiElement().afterLeaf(propertyPattern), new PropertyCompletionProvider());
|
||||
|
||||
extend(CompletionType.BASIC, psiElement().inside(RegExpProperty.class).afterLeaf(psiElement(RegExpTT.EQ)),
|
||||
new PropertyValueCompletionProvider());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -172,4 +178,20 @@ public final class RegExpCompletionContributor extends CompletionContributor {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static class PropertyValueCompletionProvider extends CompletionProvider<CompletionParameters> {
|
||||
@Override
|
||||
protected void addCompletions(@NotNull CompletionParameters parameters,
|
||||
@NotNull ProcessingContext context,
|
||||
@NotNull CompletionResultSet result) {
|
||||
RegExpProperty property = ObjectUtils.tryCast(parameters.getPosition().getParent(), RegExpProperty.class);
|
||||
ASTNode propertyNameNode = property != null ? property.getCategoryNode() : null;
|
||||
if (propertyNameNode == null) {
|
||||
return;
|
||||
}
|
||||
for (String[] value : RegExpLanguageHosts.getInstance().getAllPropertyValues(property, propertyNameNode.getText())) {
|
||||
addLookupElement(result, value[0], value.length > 1 ? value[1] : null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import java.util.EnumSet;
|
||||
public interface RegExpLanguageHost {
|
||||
|
||||
EnumSet<RegExpGroup.Type> EMPTY_NAMED_GROUP_TYPES = EnumSet.noneOf(RegExpGroup.Type.class);
|
||||
@SuppressWarnings("SSBasedInspection")
|
||||
String[][] EMPTY_COMPLETION_ITEMS_ARRAY = new String[0][];
|
||||
|
||||
boolean characterNeedsEscaping(char c);
|
||||
boolean supportsPerl5EmbeddedComments();
|
||||
@@ -89,6 +91,11 @@ public interface RegExpLanguageHost {
|
||||
}
|
||||
|
||||
boolean isValidCategory(@NotNull String category);
|
||||
|
||||
default boolean isValidPropertyValue(@NotNull String propertyName, @NotNull String value){
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
String[][] getAllKnownProperties();
|
||||
@Nullable
|
||||
@@ -109,6 +116,11 @@ public interface RegExpLanguageHost {
|
||||
return Lookbehind.FULL; // to not break existing implementations, although rarely actually supported.
|
||||
}
|
||||
|
||||
@NotNull
|
||||
default String[][] getAllPropertyValues(@NotNull String propertyName){
|
||||
return EMPTY_COMPLETION_ITEMS_ARRAY;
|
||||
}
|
||||
|
||||
enum Lookbehind {
|
||||
/** Lookbehind not supported. */
|
||||
NOT_SUPPORTED,
|
||||
|
||||
@@ -151,6 +151,11 @@ public final class RegExpLanguageHosts extends ClassExtension<RegExpLanguageHost
|
||||
final RegExpLanguageHost host = findRegExpHost(element);
|
||||
return host != null ? host.isValidCategory(category) : myDefaultProvider.isValidCategory(category);
|
||||
}
|
||||
|
||||
public boolean isValidPropertyValue(@NotNull PsiElement element, @NotNull String propertyName, @NotNull String propertyValue) {
|
||||
final RegExpLanguageHost host = findRegExpHost(element);
|
||||
return host == null || host.isValidPropertyValue(propertyName, propertyValue);
|
||||
}
|
||||
|
||||
public boolean supportsNamedCharacters(@NotNull final RegExpNamedCharacter namedCharacter) {
|
||||
final RegExpLanguageHost host = findRegExpHost(namedCharacter);
|
||||
@@ -185,6 +190,12 @@ public final class RegExpLanguageHosts extends ClassExtension<RegExpLanguageHost
|
||||
return host != null ? host.getAllKnownProperties() : myDefaultProvider.getAllKnownProperties();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String[][] getAllPropertyValues(@NotNull PsiElement element, @NotNull String propertyName) {
|
||||
final RegExpLanguageHost host = findRegExpHost(element);
|
||||
return host != null ? host.getAllPropertyValues(propertyName) : RegExpLanguageHost.EMPTY_COMPLETION_ITEMS_ARRAY;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getPropertyDescription(@NotNull final PsiElement element, @Nullable final String name) {
|
||||
final RegExpLanguageHost host = findRegExpHost(element);
|
||||
|
||||
@@ -492,7 +492,11 @@ public class RegExpParser implements PsiParser, LightPsiParser {
|
||||
builder.advanceLexer();
|
||||
}
|
||||
if (builder.getTokenType() == RegExpTT.NAME) {
|
||||
builder.advanceLexer();
|
||||
builder.advanceLexer(); //name
|
||||
if (myCapabilities.contains(RegExpCapability.PROPERTY_VALUES) && builder.getTokenType() == RegExpTT.EQ) {
|
||||
builder.advanceLexer(); //eq
|
||||
checkMatches(builder, RegExpTT.NAME, "Property value expected");
|
||||
}
|
||||
checkMatches(builder, RegExpTT.RBRACE, "Unclosed property");
|
||||
}
|
||||
else
|
||||
|
||||
@@ -92,6 +92,10 @@ public interface RegExpTT {
|
||||
IElementType DOT = new RegExpElementType("DOT");
|
||||
/** "|" */
|
||||
IElementType UNION = new RegExpElementType("UNION");
|
||||
/**
|
||||
* "=" in some unicode properties, e.g.(\p{name=value})
|
||||
*/
|
||||
IElementType EQ = new RegExpElementType("EQ");
|
||||
|
||||
/** ">" in Python/Ruby named group */
|
||||
IElementType GT = new RegExpElementType("GT");
|
||||
|
||||
+1
@@ -21,6 +21,7 @@ public class EcmaScriptUnicodeRegexpParserDefinition extends RegExpParserDefinit
|
||||
private final EnumSet<RegExpCapability> CAPABILITIES = EnumSet.of(RegExpCapability.OCTAL_NO_LEADING_ZERO,
|
||||
RegExpCapability.ALLOW_EMPTY_CHARACTER_CLASS,
|
||||
RegExpCapability.NO_DANGLING_METACHARACTERS,
|
||||
RegExpCapability.PROPERTY_VALUES,
|
||||
RegExpCapability.EXTENDED_UNICODE_CHARACTER);
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,4 +32,10 @@ public interface RegExpProperty extends RegExpAtom, RegExpClassElement {
|
||||
*/
|
||||
@Nullable
|
||||
ASTNode getCategoryNode();
|
||||
|
||||
/**
|
||||
* The node that represents the category value, e.g 'Latin' in \p{Script=Latin}
|
||||
*/
|
||||
@Nullable
|
||||
ASTNode getValueNode();
|
||||
}
|
||||
|
||||
@@ -64,59 +64,78 @@ public class RegExpPropertyImpl extends RegExpElementImpl implements RegExpPrope
|
||||
return getNode().findChildByType(RegExpTT.NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(RegExpElementVisitor visitor) {
|
||||
visitor.visitRegExpProperty(this);
|
||||
}
|
||||
@Nullable
|
||||
@Override
|
||||
public ASTNode getValueNode() {
|
||||
ASTNode node = getNode();
|
||||
ASTNode eq = node.findChildByType(RegExpTT.EQ);
|
||||
return eq != null ? node.findChildByType(RegExpTT.NAME, eq) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(RegExpElementVisitor visitor) {
|
||||
visitor.visitRegExpProperty(this);
|
||||
}
|
||||
|
||||
private class MyPsiReference implements PsiReference {
|
||||
@Override
|
||||
@NotNull
|
||||
public PsiElement getElement() {
|
||||
return RegExpPropertyImpl.this;
|
||||
}
|
||||
@Override
|
||||
@NotNull
|
||||
public PsiElement getElement() {
|
||||
return RegExpPropertyImpl.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TextRange getRangeInElement() {
|
||||
ASTNode firstNode = getNode().findChildByType(RegExpTT.CARET);
|
||||
if (firstNode == null) {
|
||||
firstNode = getNode().findChildByType(RegExpTT.LBRACE);
|
||||
}
|
||||
assert firstNode != null;
|
||||
final ASTNode rbrace = getNode().findChildByType(RegExpTT.RBRACE);
|
||||
int to = rbrace == null ? getTextRange().getEndOffset() : rbrace.getTextRange().getEndOffset() - 1;
|
||||
@Override
|
||||
@NotNull
|
||||
public TextRange getRangeInElement() {
|
||||
ASTNode node = getNode();
|
||||
ASTNode firstNode = node.findChildByType(RegExpTT.CARET);
|
||||
if (firstNode == null) {
|
||||
firstNode = node.findChildByType(RegExpTT.LBRACE);
|
||||
}
|
||||
assert firstNode != null;
|
||||
ASTNode eq = node.findChildByType(RegExpTT.EQ);
|
||||
final ASTNode rbrace = node.findChildByType(RegExpTT.RBRACE);
|
||||
int to;
|
||||
if (eq != null) {
|
||||
to = eq.getTextRange().getEndOffset() - 1;
|
||||
}
|
||||
else if (rbrace != null) {
|
||||
to = rbrace.getTextRange().getEndOffset() - 1;
|
||||
}
|
||||
else {
|
||||
to = getTextRange().getEndOffset();
|
||||
}
|
||||
|
||||
final TextRange t = new TextRange(firstNode.getStartOffset() + 1, to);
|
||||
return t.shiftRight(-getTextRange().getStartOffset());
|
||||
}
|
||||
final TextRange t = new TextRange(firstNode.getStartOffset() + 1, to);
|
||||
return t.shiftRight(-getTextRange().getStartOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PsiElement resolve() {
|
||||
return RegExpPropertyImpl.this;
|
||||
}
|
||||
@Override
|
||||
@Nullable
|
||||
public PsiElement resolve() {
|
||||
return RegExpPropertyImpl.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getCanonicalText() {
|
||||
return getRangeInElement().substring(getElement().getText());
|
||||
}
|
||||
@Override
|
||||
@NotNull
|
||||
public String getCanonicalText() {
|
||||
return getRangeInElement().substring(getElement().getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException {
|
||||
throw new IncorrectOperationException();
|
||||
}
|
||||
@Override
|
||||
public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException {
|
||||
throw new IncorrectOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
|
||||
throw new IncorrectOperationException();
|
||||
}
|
||||
@Override
|
||||
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
|
||||
throw new IncorrectOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReferenceTo(@NotNull PsiElement element) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean isReferenceTo(@NotNull PsiElement element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
|
||||
@@ -301,6 +301,7 @@ HEX_CHAR=[0-9a-fA-F]
|
||||
<EMBRACED> {
|
||||
"^" { return RegExpTT.CARET; }
|
||||
{NAME} { return RegExpTT.NAME; }
|
||||
"=" { return RegExpTT.EQ; }
|
||||
{RBRACE} { yypopstate(); return RegExpTT.RBRACE; }
|
||||
{ANY} { yypopstate(); yypushback(1); }
|
||||
}
|
||||
|
||||
@@ -219,9 +219,16 @@ public final class RegExpAnnotator extends RegExpElementVisitor implements Annot
|
||||
if (category == null) {
|
||||
return;
|
||||
}
|
||||
if(!myLanguageHosts.isValidCategory(category.getPsi(), category.getText())) {
|
||||
String propertyName = category.getText();
|
||||
if(!myLanguageHosts.isValidCategory(category.getPsi(), propertyName)) {
|
||||
final Annotation a = myHolder.createErrorAnnotation(category, "Unknown character category");
|
||||
a.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
|
||||
return;
|
||||
}
|
||||
ASTNode valueNode = property.getValueNode();
|
||||
if (valueNode != null && !myLanguageHosts.isValidPropertyValue(category.getPsi(), propertyName, valueNode.getText())) {
|
||||
final Annotation a = myHolder.createErrorAnnotation(valueNode, "Unknown property value");
|
||||
a.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user