package org.angularjs.codeInsight; import com.intellij.codeInsight.completion.*; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.codeInsight.lookup.LookupElementWeigher; import com.intellij.lang.javascript.JSTokenTypes; import com.intellij.lang.javascript.psi.JSCommaExpression; import com.intellij.lang.javascript.psi.JSExpressionStatement; import com.intellij.lang.javascript.psi.JSReferenceExpression; import com.intellij.openapi.editor.EditorModificationUtil; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import org.angularjs.lang.parser.AngularJSElementTypes; import org.angularjs.lang.parser.AngularJSMessageFormatParser; import org.angularjs.lang.psi.AngularJSMessageFormatExpression; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; /** * @author Irina.Chernushina on 12/7/2015. */ public class AngularMessageFormatCompletion { public static final Comparator<AngularJSPluralCategories> PLURAL_CATEGORIES_COMPARATOR = (o1, o2) -> new Integer(o1.getCompletionOrder()).compareTo(o2.getCompletionOrder()); public static final InsertHandler<LookupElement> MESSAGE_FORMAT_KEYWORD_INSERT_HANDLER = new InsertHandler<LookupElement>() { @Override public void handleInsert(InsertionContext context, LookupElement item) { context.setAddCompletionChar(false); final String string = item.getLookupString(); final int offset = context.getEditor().getCaretModel().getCurrentCaret().getOffset(); final String text = context.getDocument().getText(TextRange.create(offset, Math.min(offset + 100, context.getDocument().getTextLength()))).trim(); // we need this code to improve the situation with the case when selection keyword starts with = // default implementation does not take it as completion pattern part, since = is not a part of java identifier final int idOffset = context.getStartOffset(); if (idOffset > 0 && (idOffset + 1) < context.getDocument().getTextLength() && "==".equals(context.getDocument().getText(TextRange.create(idOffset - 1, idOffset + 1)))) { context.getDocument().deleteString(idOffset, idOffset + 1); } if (!text.startsWith("{")) { final int diff = context.getSelectionEndOffset() - idOffset; EditorModificationUtil.insertStringAtCaret(context.getEditor(), " {}", false, string.length() - diff + 2); } context.commitDocument(); } }; public static final LookupElementWeigher MESSAGE_FORMAT_KEYWORD_WEIGHER = new LookupElementWeigher("angular.message.format") { @Nullable @Override public Comparable weigh(@NotNull LookupElement element) { if (element.getObject() instanceof AngularJSPluralCategories) { return ((AngularJSPluralCategories)element.getObject()).getCompletionOrder(); } if (element.getObject() instanceof String && ((String)element.getObject()).startsWith("=")) { try { return 10 + Integer.parseInt(element.getObject().toString().substring(1)); } catch (NumberFormatException e) { // } } return Integer.MAX_VALUE; } }; static boolean messageFormatCompletion(CompletionParameters parameters, CompletionResultSet result) { final PsiElement originalPosition = parameters.getOriginalPosition(); if (originalPosition == null) return false; final PsiElement parent = originalPosition.getParent(); if (parent instanceof JSReferenceExpression && parent.getParent() instanceof JSCommaExpression) { final PsiElement[] children = parent.getParent().getChildren(); if (children.length >= 2 && children[1] == parent) { messageFormatExtensions(result); return true; } } if (parent instanceof AngularJSMessageFormatExpression) { final AngularJSMessageFormatExpression amfe = (AngularJSMessageFormatExpression)parent; if (originalPosition == amfe.getExtensionTypeElement()) { messageFormatExtensions(result); return true; } if (originalPosition.getNode().getElementType() == AngularJSElementTypes.MESSAGE_FORMAT_SELECTION_KEYWORD) { messageFormatSelectionKeywords(((AngularJSMessageFormatExpression)parent).getExtensionType(), result); return true; } if (originalPosition.getNode().getElementType() == JSTokenTypes.WHITE_SPACE) { if (originalPosition.getNextSibling() != null && originalPosition.getNextSibling().getNode().getElementType() == AngularJSElementTypes.MESSAGE_FORMAT_SELECTION_KEYWORD || originalPosition.getPrevSibling() != null && originalPosition.getPrevSibling().getNode().getElementType() == JSTokenTypes.RBRACE) { messageFormatSelectionKeywords(((AngularJSMessageFormatExpression)parent).getExtensionType(), result); return true; } } } final PsiElement sibling = originalPosition.getPrevSibling(); if (sibling instanceof AngularJSMessageFormatExpression) { messageFormatSelectionKeywords(((AngularJSMessageFormatExpression)sibling).getExtensionType(), result); } else if (sibling instanceof JSExpressionStatement && sibling.getFirstChild() instanceof AngularJSMessageFormatExpression) { messageFormatSelectionKeywords(((AngularJSMessageFormatExpression)sibling.getFirstChild()).getExtensionType(), result); } return false; } private static void messageFormatSelectionKeywords(AngularJSMessageFormatParser.ExtensionType type, CompletionResultSet result) { final CompletionResultSet set = result.withRelevanceSorter(CompletionSorter.emptySorter().weigh(MESSAGE_FORMAT_KEYWORD_WEIGHER)); if (AngularJSMessageFormatParser.ExtensionType.plural.equals(type)) { final List<AngularJSPluralCategories> values = new ArrayList<>(Arrays.asList(AngularJSPluralCategories.values())); Collections.sort(values, PLURAL_CATEGORIES_COMPARATOR); for (AngularJSPluralCategories category : values) { LookupElementBuilder element = LookupElementBuilder.create(category).withInsertHandler(MESSAGE_FORMAT_KEYWORD_INSERT_HANDLER); if (AngularJSPluralCategories.other.equals(category)) { element = element.withTypeText("Default selection keyword", true); } else { element = element.withTypeText("Plural category", true); } set.addElement(element); } for (int i = 0; i < 4; i++) { final LookupElementBuilder element = LookupElementBuilder.create("=" + i).withInsertHandler(MESSAGE_FORMAT_KEYWORD_INSERT_HANDLER); set.addElement(element); } } else { set.addElement(LookupElementBuilder.create("other").setTypeText("Default selection keyword", true).withInsertHandler(MESSAGE_FORMAT_KEYWORD_INSERT_HANDLER)); } set.stopHere(); } private static void messageFormatExtensions(CompletionResultSet result) { for (AngularJSMessageFormatParser.ExtensionType type : AngularJSMessageFormatParser.ExtensionType.values()) { final LookupElementBuilder elementBuilder = LookupElementBuilder.create(type.name()) .setTypeText("Message format extension", true); result.consume(elementBuilder); } result.stopHere(); } }