package com.jetbrains.lang.dart.ide.annotator;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.lang.ASTNode;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.AnnotationSession;
import com.intellij.lang.annotation.Annotator;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.jetbrains.lang.dart.DartBundle;
import com.jetbrains.lang.dart.DartTokenTypes;
import com.jetbrains.lang.dart.DartTokenTypesSets;
import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService;
import com.jetbrains.lang.dart.analyzer.DartServerData;
import com.jetbrains.lang.dart.fixes.DartQuickFixSet;
import com.jetbrains.lang.dart.highlight.DartSyntaxHighlighterColors;
import com.jetbrains.lang.dart.psi.DartSymbolLiteralExpression;
import com.jetbrains.lang.dart.psi.DartTernaryExpression;
import com.jetbrains.lang.dart.sdk.DartSdk;
import com.jetbrains.lang.dart.sdk.DartSdkLibUtil;
import gnu.trove.THashMap;
import org.dartlang.analysis.server.protocol.AnalysisErrorSeverity;
import org.dartlang.analysis.server.protocol.HighlightRegionType;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class DartAnnotator implements Annotator {
private static final Key<Boolean> DART_SERVER_DATA_HANDLED = Key.create("DART_SERVER_DATA_HANDLED");
private static final Map<String, String> HIGHLIGHTING_TYPE_MAP = new THashMap<>();
static {
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.ANNOTATION, DartSyntaxHighlighterColors.DART_ANNOTATION);
// handled by DartAnnotator without server
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.BUILT_IN, DartSyntaxHighlighterColors.DART_KEYWORD);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.CLASS, DartSyntaxHighlighterColors.DART_CLASS);
// handled by DartSyntaxHighlighter
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.COMMENT_BLOCK, DartSyntaxHighlighterColors.DART_BLOCK_COMMENT);
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.COMMENT_DOCUMENTATION, DartSyntaxHighlighterColors.DART_DOC_COMMENT);
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.COMMENT_DOCUMENTATION, DartSyntaxHighlighterColors.DART_LINE_COMMENT);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.CONSTRUCTOR, DartSyntaxHighlighterColors.DART_CONSTRUCTOR);
// No need in special highlighting of the whole region. Individual child regions are highlighted.
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.DIRECTIVE, DartSyntaxHighlighterColors.);
// HighlightRegionType.DYNAMIC_TYPE - Only for version 1 of highlight.
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.DYNAMIC_LOCAL_VARIABLE_DECLARATION,
DartSyntaxHighlighterColors.DART_DYNAMIC_LOCAL_VARIABLE_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.DYNAMIC_LOCAL_VARIABLE_REFERENCE,
DartSyntaxHighlighterColors.DART_DYNAMIC_LOCAL_VARIABLE_REFERENCE);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.DYNAMIC_PARAMETER_DECLARATION,
DartSyntaxHighlighterColors.DART_DYNAMIC_PARAMETER_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.DYNAMIC_PARAMETER_REFERENCE,
DartSyntaxHighlighterColors.DART_DYNAMIC_PARAMETER_REFERENCE);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.ENUM, DartSyntaxHighlighterColors.DART_ENUM);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.ENUM_CONSTANT, DartSyntaxHighlighterColors.DART_ENUM_CONSTANT);
// HighlightRegionType.FIELD - Only for version 1 of highlight.
// HighlightRegionType.FIELD_STATIC - Only for version 1 of highlight.
// HighlightRegionType.FUNCTION - Only for version 1 of highlight.
// HighlightRegionType.FUNCTION_DECLARATION - Only for version 1 of highlight.
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.FUNCTION_TYPE_ALIAS, DartSyntaxHighlighterColors.DART_FUNCTION_TYPE_ALIAS);
// HighlightRegionType.GETTER_DECLARATION - Only for version 1 of highlight.
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.IDENTIFIER_DEFAULT, DartSyntaxHighlighterColors.DART_IDENTIFIER);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.IMPORT_PREFIX, DartSyntaxHighlighterColors.DART_IMPORT_PREFIX);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.INSTANCE_FIELD_DECLARATION, DartSyntaxHighlighterColors.DART_INSTANCE_FIELD_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.INSTANCE_FIELD_REFERENCE, DartSyntaxHighlighterColors.DART_INSTANCE_FIELD_REFERENCE);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.INSTANCE_GETTER_DECLARATION,
DartSyntaxHighlighterColors.DART_INSTANCE_GETTER_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.INSTANCE_GETTER_REFERENCE, DartSyntaxHighlighterColors.DART_INSTANCE_GETTER_REFERENCE);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.INSTANCE_METHOD_DECLARATION,
DartSyntaxHighlighterColors.DART_INSTANCE_METHOD_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.INSTANCE_METHOD_REFERENCE, DartSyntaxHighlighterColors.DART_INSTANCE_METHOD_REFERENCE);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.INSTANCE_SETTER_DECLARATION,
DartSyntaxHighlighterColors.DART_INSTANCE_SETTER_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.INSTANCE_SETTER_REFERENCE, DartSyntaxHighlighterColors.DART_INSTANCE_SETTER_REFERENCE);
// handled by DartAnnotator without server
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.INVALID_STRING_ESCAPE, DartSyntaxHighlighterColors.DART_INVALID_STRING_ESCAPE);
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.KEYWORD, DartSyntaxHighlighterColors.DART_KEYWORD);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.LABEL, DartSyntaxHighlighterColors.DART_LABEL);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.LIBRARY_NAME, DartSyntaxHighlighterColors.DART_LIBRARY_NAME);
// handled by DartSyntaxHighlighter
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.LITERAL_BOOLEAN, DartSyntaxHighlighterColors.DART_KEYWORD);
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.LITERAL_DOUBLE, DartSyntaxHighlighterColors.DART_NUMBER);
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.LITERAL_INTEGER, DartSyntaxHighlighterColors.DART_NUMBER);
// No need in special highlighting of the whole map/list literal.
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.LITERAL_LIST, DartSyntaxHighlighterColors.);
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.LITERAL_MAP, DartSyntaxHighlighterColors.);
// handled by DartSyntaxHighlighter
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.LITERAL_STRING, DartSyntaxHighlighterColors.DART_STRING);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.LOCAL_FUNCTION_DECLARATION, DartSyntaxHighlighterColors.DART_LOCAL_FUNCTION_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.LOCAL_FUNCTION_REFERENCE, DartSyntaxHighlighterColors.DART_LOCAL_FUNCTION_REFERENCE);
// HighlightRegionType.LOCAL_VARIABLE - Only for version 1 of highlight.
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.LOCAL_VARIABLE_DECLARATION, DartSyntaxHighlighterColors.DART_LOCAL_VARIABLE_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.LOCAL_VARIABLE_REFERENCE, DartSyntaxHighlighterColors.DART_LOCAL_VARIABLE_REFERENCE);
// HighlightRegionType.METHOD - Only for version 1 of highlight.
// HighlightRegionType.METHOD_DECLARATION - Only for version 1 of highlight.
// HighlightRegionType.METHOD_DECLARATION_STATIC - Only for version 1 of highlight.
// HighlightRegionType.METHOD_STATIC - Only for version 1 of highlight.
// HighlightRegionType.PARAMETER - Only for version 1 of highlight.
// HighlightRegionType.SETTER_DECLARATION - Only for version 1 of highlight.
// HighlightRegionType.TOP_LEVEL_VARIABLE - Only for version 1 of highlight.
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.PARAMETER_DECLARATION, DartSyntaxHighlighterColors.DART_PARAMETER_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.PARAMETER_REFERENCE, DartSyntaxHighlighterColors.DART_PARAMETER_REFERENCE);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.STATIC_FIELD_DECLARATION, DartSyntaxHighlighterColors.DART_STATIC_FIELD_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.STATIC_GETTER_DECLARATION, DartSyntaxHighlighterColors.DART_STATIC_GETTER_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.STATIC_GETTER_REFERENCE, DartSyntaxHighlighterColors.DART_STATIC_GETTER_REFERENCE);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.STATIC_METHOD_DECLARATION, DartSyntaxHighlighterColors.DART_STATIC_METHOD_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.STATIC_METHOD_REFERENCE, DartSyntaxHighlighterColors.DART_STATIC_METHOD_REFERENCE);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.STATIC_SETTER_DECLARATION, DartSyntaxHighlighterColors.DART_STATIC_SETTER_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.STATIC_SETTER_REFERENCE, DartSyntaxHighlighterColors.DART_STATIC_SETTER_REFERENCE);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.TOP_LEVEL_FUNCTION_DECLARATION,
DartSyntaxHighlighterColors.DART_TOP_LEVEL_FUNCTION_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.TOP_LEVEL_FUNCTION_REFERENCE,
DartSyntaxHighlighterColors.DART_TOP_LEVEL_FUNCTION_REFERENCE);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.TOP_LEVEL_GETTER_DECLARATION,
DartSyntaxHighlighterColors.DART_TOP_LEVEL_GETTER_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.TOP_LEVEL_GETTER_REFERENCE, DartSyntaxHighlighterColors.DART_TOP_LEVEL_GETTER_REFERENCE);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.TOP_LEVEL_SETTER_DECLARATION,
DartSyntaxHighlighterColors.DART_TOP_LEVEL_SETTER_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.TOP_LEVEL_SETTER_REFERENCE, DartSyntaxHighlighterColors.DART_TOP_LEVEL_SETTER_REFERENCE);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.TOP_LEVEL_VARIABLE_DECLARATION,
DartSyntaxHighlighterColors.DART_TOP_LEVEL_VARIABLE_DECLARATION);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.TYPE_NAME_DYNAMIC, DartSyntaxHighlighterColors.DART_TYPE_NAME_DYNAMIC);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.TYPE_PARAMETER, DartSyntaxHighlighterColors.DART_TYPE_PARAMETER);
HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.UNRESOLVED_INSTANCE_MEMBER_REFERENCE,
DartSyntaxHighlighterColors.DART_UNRESOLVED_INSTANCE_MEMBER_REFERENCE);
// handled by DartAnnotator without server
//HIGHLIGHTING_TYPE_MAP.put(HighlightRegionType.VALID_STRING_ESCAPE, DartSyntaxHighlighterColors.DART_VALID_STRING_ESCAPE);
}
@Contract("_, null -> false")
private static boolean canBeAnalyzedByServer(@NotNull final Project project, @Nullable final VirtualFile file) {
if (!DartAnalysisServerService.isLocalAnalyzableFile(file)) return false;
final DartSdk sdk = DartSdk.getDartSdk(project);
if (sdk == null || !DartAnalysisServerService.isDartSdkVersionSufficient(sdk)) return false;
// server can highlight files from Dart SDK, packages and from modules with enabled Dart support
final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
if (fileIndex.isInLibraryClasses(file)) return true;
final Module module = fileIndex.getModuleForFile(file);
return module != null && DartSdkLibUtil.isDartSdkEnabled(module);
}
public static boolean shouldIgnoreMessageFromDartAnalyzer(@NotNull final String filePath,
@NotNull final String analysisErrorFileSD) {
// workaround for https://github.com/dart-lang/sdk/issues/25034
if (!filePath.equals(FileUtil.toSystemIndependentName(analysisErrorFileSD))) return true;
return false;
}
@Override
public void annotate(@NotNull final PsiElement element, @NotNull final AnnotationHolder holder) {
if (holder.isBatchMode()) return;
final AnnotationSession session = holder.getCurrentAnnotationSession();
if (session.getUserData(DART_SERVER_DATA_HANDLED) != Boolean.TRUE) {
session.putUserData(DART_SERVER_DATA_HANDLED, Boolean.TRUE);
final VirtualFile vFile = element.getContainingFile().getVirtualFile();
final DartAnalysisServerService service = DartAnalysisServerService.getInstance(element.getProject());
if (canBeAnalyzedByServer(element.getProject(), vFile) && service.serverReadyForRequest(element.getProject())) {
service.updateFilesContent();
if (ApplicationManager.getApplication().isUnitTestMode()) {
service.waitForAnalysisToComplete_TESTS_ONLY(vFile);
}
applyServerHighlighting(vFile, holder);
}
}
if (DartTokenTypes.COLON == element.getNode().getElementType() && element.getParent() instanceof DartTernaryExpression) {
holder.createInfoAnnotation(element, null).setTextAttributes(DartSyntaxHighlighterColors.OPERATION_SIGN);
return;
}
if (DartTokenTypesSets.BUILT_IN_IDENTIFIERS.contains(element.getNode().getElementType())) {
if (element.getNode().getTreeParent().getElementType() != DartTokenTypes.ID) {
holder.createInfoAnnotation(element, null).setTextAttributes(DartSyntaxHighlighterColors.KEYWORD);
return;
}
}
// sync*, async* and yield*
if (DartTokenTypes.MUL == element.getNode().getElementType()) {
final ASTNode previous = element.getNode().getTreePrev();
if (previous != null && (previous.getElementType() == DartTokenTypes.SYNC ||
previous.getElementType() == DartTokenTypes.ASYNC ||
previous.getElementType() == DartTokenTypes.YIELD)) {
holder.createInfoAnnotation(element, null).setTextAttributes(DartSyntaxHighlighterColors.KEYWORD);
}
}
if (element.getNode().getElementType() == DartTokenTypes.REGULAR_STRING_PART) {
highlightEscapeSequences(element, holder);
return;
}
if (element instanceof DartSymbolLiteralExpression) {
holder.createInfoAnnotation(element, null).setTextAttributes(DartSyntaxHighlighterColors.SYMBOL_LITERAL);
//noinspection UnnecessaryReturnStatement
return;
}
}
private static void applyServerHighlighting(@NotNull final VirtualFile file, @NotNull final AnnotationHolder holder) {
final PsiFile psiFile = holder.getCurrentAnnotationSession().getFile();
final DartAnalysisServerService das = DartAnalysisServerService.getInstance(psiFile.getProject());
for (DartServerData.DartError error : das.getErrors(file)) {
if (shouldIgnoreMessageFromDartAnalyzer(file.getPath(), error.getAnalysisErrorFileSD())) continue;
final Annotation annotation = createAnnotation(holder, error, psiFile.getTextLength());
if (annotation != null) {
final DartQuickFixSet quickFixSet =
new DartQuickFixSet(psiFile.getManager(), file, error.getOffset(), error.getCode(), error.getSeverity());
for (IntentionAction quickFix : quickFixSet.getQuickFixes()) {
annotation.registerFix(quickFix);
}
if (error.getCode() != null) {
annotation.setProblemGroup(new DartProblemGroup(error.getCode(), error.getSeverity()));
}
}
}
for (DartServerData.DartHighlightRegion region : das.getHighlight(file)) {
final String attributeKey = HIGHLIGHTING_TYPE_MAP.get(region.getType());
if (attributeKey != null) {
final TextRange textRange = new TextRange(region.getOffset(), region.getOffset() + region.getLength());
holder.createInfoAnnotation(textRange, null).setTextAttributes(TextAttributesKey.find(attributeKey));
}
}
}
@Nullable
private static Annotation createAnnotation(@NotNull final AnnotationHolder holder,
@NotNull final DartServerData.DartError error,
final int fileTextLength) {
int highlightingStart = error.getOffset();
int highlightingEnd = error.getOffset() + error.getLength();
if (highlightingEnd > fileTextLength) highlightingEnd = fileTextLength;
if (highlightingStart > 0 && highlightingStart >= highlightingEnd) highlightingStart = highlightingEnd - 1;
final TextRange textRange = new TextRange(highlightingStart, highlightingEnd);
final String severity = error.getSeverity();
final String message = StringUtil.notNullize(error.getMessage());
final ProblemHighlightType specialHighlightType = getSpecialHighlightType(message);
final Annotation annotation;
if (AnalysisErrorSeverity.INFO.equals(severity) && specialHighlightType == null) {
annotation = holder.createWeakWarningAnnotation(textRange, message);
annotation.setTextAttributes(DartSyntaxHighlighterColors.HINT);
}
else if (AnalysisErrorSeverity.WARNING.equals(severity) ||
(AnalysisErrorSeverity.INFO.equals(severity) && specialHighlightType != null)) {
annotation = holder.createWarningAnnotation(textRange, message);
annotation.setTextAttributes(DartSyntaxHighlighterColors.WARNING);
}
else if (AnalysisErrorSeverity.ERROR.equals(severity)) {
annotation = holder.createErrorAnnotation(textRange, message);
annotation.setTextAttributes(DartSyntaxHighlighterColors.ERROR);
}
else {
annotation = null;
}
if (annotation != null && specialHighlightType != null) {
annotation.setTextAttributes(null);
annotation.setHighlightType(specialHighlightType);
}
return annotation;
}
@Nullable
private static ProblemHighlightType getSpecialHighlightType(@NotNull final String errorMessage) {
// see [Dart repo]/pkg/analyzer/lib/src/generated/error.dart
// todo it is now possible to switch to checking error code instead of error message
if (errorMessage.startsWith("Unused import") ||
errorMessage.startsWith("Duplicate import") ||
errorMessage.contains(" is not used") ||
errorMessage.contains(" isn't used") ||
errorMessage.startsWith("Dead code")) {
return ProblemHighlightType.LIKE_UNUSED_SYMBOL;
}
if (errorMessage.contains(" is deprecated")) {
return ProblemHighlightType.LIKE_DEPRECATED;
}
return null;
}
private static void highlightEscapeSequences(final PsiElement node, final AnnotationHolder holder) {
final List<Pair<TextRange, Boolean>> escapeSequenceRangesAndValidity = getEscapeSequenceRangesAndValidity(node.getText());
for (Pair<TextRange, Boolean> rangeAndValidity : escapeSequenceRangesAndValidity) {
final TextRange range = rangeAndValidity.first.shiftRight(node.getTextRange().getStartOffset());
final TextAttributesKey attribute =
rangeAndValidity.second ? DartSyntaxHighlighterColors.VALID_STRING_ESCAPE : DartSyntaxHighlighterColors.INVALID_STRING_ESCAPE;
if (rangeAndValidity.second) {
holder.createInfoAnnotation(range, null).setTextAttributes(attribute);
}
else {
holder.createErrorAnnotation(range, DartBundle.message("dart.color.settings.description.invalid.string.escape"))
.setTextAttributes(attribute);
}
}
}
@NotNull
private static List<Pair<TextRange, Boolean>> getEscapeSequenceRangesAndValidity(final @Nullable String text) {
// \\xFF 2 hex digits
// \\uFFFF 4 hex digits
// \\u{F} - \\u{FFFFFF} from 1 up to 6 hex digits
// \\. any char except 'x' and 'u'
if (StringUtil.isEmpty(text)) return Collections.emptyList();
final List<Pair<TextRange, Boolean>> result = new ArrayList<>();
int currentIndex = -1;
while ((currentIndex = text.indexOf('\\', currentIndex)) != -1) {
final int startIndex = currentIndex;
if (text.length() <= currentIndex + 1) {
result.add(Pair.create(new TextRange(startIndex, text.length()), false));
break;
}
final char nextChar = text.charAt(++currentIndex);
if (nextChar == 'x') {
if (text.length() <= currentIndex + 2) {
result.add(Pair.create(new TextRange(startIndex, text.length()), false));
break;
}
final char hexChar1 = text.charAt(++currentIndex);
final char hexChar2 = text.charAt(++currentIndex);
final boolean valid = StringUtil.isHexDigit(hexChar1) && StringUtil.isHexDigit(hexChar2);
currentIndex++;
result.add(Pair.create(new TextRange(startIndex, currentIndex), valid));
}
else if (nextChar == 'u') {
if (text.length() <= currentIndex + 1) {
result.add(Pair.create(new TextRange(startIndex, text.length()), false));
break;
}
final char hexOrBraceChar1 = text.charAt(++currentIndex);
if (hexOrBraceChar1 == '{') {
currentIndex++;
final int closingBraceIndex = text.indexOf('}', currentIndex);
if (closingBraceIndex == -1) {
result.add(Pair.create(new TextRange(startIndex, currentIndex), false));
}
else {
final String textInBrackets = text.substring(currentIndex, closingBraceIndex);
currentIndex = closingBraceIndex + 1;
final boolean valid = textInBrackets.length() > 0 && textInBrackets.length() <= 6 && isHexString(textInBrackets);
result.add(Pair.create(new TextRange(startIndex, currentIndex), valid));
}
}
else {
//noinspection UnnecessaryLocalVariable
final char hexChar1 = hexOrBraceChar1;
if (text.length() <= currentIndex + 3) {
result.add(Pair.create(new TextRange(startIndex, text.length()), false));
break;
}
final char hexChar2 = text.charAt(++currentIndex);
final char hexChar3 = text.charAt(++currentIndex);
final char hexChar4 = text.charAt(++currentIndex);
final boolean valid = StringUtil.isHexDigit(hexChar1) &&
StringUtil.isHexDigit(hexChar2) &&
StringUtil.isHexDigit(hexChar3) &&
StringUtil.isHexDigit(hexChar4);
currentIndex++;
result.add(Pair.create(new TextRange(startIndex, currentIndex), valid));
}
}
else {
// not 'x' and not 'u', just any other single character escape
currentIndex++;
result.add(Pair.create(new TextRange(startIndex, currentIndex), true));
}
}
return result;
}
private static boolean isHexString(final String text) {
if (StringUtil.isEmpty(text)) return false;
for (int i = 0; i < text.length(); i++) {
if (!StringUtil.isHexDigit(text.charAt(i))) return false;
}
return true;
}
}