/* * Copyright 2012-2014 Sergey Ignatov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.intellij.erlang.completion; import com.intellij.codeInsight.completion.*; import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiComment; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileFactory; import com.intellij.psi.impl.source.tree.TreeUtil; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ProcessingContext; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.CaseInsensitiveStringHashingStrategy; import gnu.trove.THashSet; import org.intellij.erlang.ErlangLanguage; import org.intellij.erlang.parser.ErlangLexer; import org.intellij.erlang.parser.ErlangParserUtil; import org.intellij.erlang.psi.ErlangColonQualifiedExpression; import org.intellij.erlang.psi.ErlangExport; import org.intellij.erlang.psi.ErlangFile; import org.intellij.erlang.psi.ErlangStringLiteral; import org.jetbrains.annotations.NotNull; import java.util.Collection; import static com.intellij.patterns.PlatformPatterns.psiElement; import static com.intellij.patterns.StandardPatterns.instanceOf; public class ErlangKeywordsCompletionContributor extends CompletionContributor implements DumbAware { private static final THashSet<String> KEYWORDS_WITH_PARENTHESIS = ContainerUtil.newTroveSet(CaseInsensitiveStringHashingStrategy.INSTANCE, "include", "include_lib", "module", "export", "export_type", "import", "define", "record", "behaviour", "behavior", "optional_callbacks" ); public ErlangKeywordsCompletionContributor() { extend(CompletionType.BASIC, psiElement().inFile(instanceOf(ErlangFile.class)), new CompletionProvider<CompletionParameters>() { @Override protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { PsiFile file = parameters.getOriginalFile(); if (ErlangParserUtil.isApplicationConfigFileType(file)) return; PsiElement position = parameters.getPosition(); //noinspection unchecked if (PsiTreeUtil.getParentOfType(position, ErlangExport.class, ErlangColonQualifiedExpression.class, ErlangStringLiteral.class, PsiComment.class) != null) return; for (String keyword : suggestKeywords(position)) { result.addElement(createKeywordLookupElement(keyword)); } } }); } @NotNull private static Collection<String> suggestKeywords(@NotNull PsiElement position) { TextRange posRange = position.getTextRange(); ErlangFile posFile = (ErlangFile) position.getContainingFile(); TextRange range = new TextRange(0, posRange.getStartOffset()); String text = range.isEmpty() ? CompletionInitializationContext.DUMMY_IDENTIFIER : range.substring(posFile.getText()); PsiFile file = PsiFileFactory.getInstance(posFile.getProject()).createFileFromText("a.erl", ErlangLanguage.INSTANCE, text, true, false); int completionOffset = posRange.getStartOffset() - range.getStartOffset(); ErlangParserUtil.CompletionState state = new ErlangParserUtil.CompletionState(completionOffset) { @Override public String convertItem(Object o) { if (o instanceof IElementType && ErlangLexer.KEYWORDS.contains((IElementType) o)) return o.toString(); return o instanceof String ? (String) o : null; } }; //noinspection StaticFieldReferencedViaSubclass file.putUserData(ErlangParserUtil.COMPLETION_STATE_KEY, state); TreeUtil.ensureParsed(file.getNode()); return state.items; } @NotNull private static LookupElement createKeywordLookupElement(@NotNull String keyword) { boolean needHandler = KEYWORDS_WITH_PARENTHESIS.contains(keyword); boolean needQuotas = "include".equalsIgnoreCase(keyword) || "include_lib".equalsIgnoreCase(keyword); boolean needBrackets = "export".equalsIgnoreCase(keyword) || "export_type".equalsIgnoreCase(keyword) || "optional_callbacks".equalsIgnoreCase(keyword); return PrioritizedLookupElement.withPriority(LookupElementBuilder.create(keyword) .withInsertHandler(needHandler ? new ErlangKeywordInsertHandler(needQuotas, needBrackets) : null) .withTailText(needHandler ? "()" : null) .bold(), ErlangCompletionContributor.KEYWORD_PRIORITY); } private static class ErlangKeywordInsertHandler extends ParenthesesInsertHandler<LookupElement> { private final boolean myNeedQuotas; private final boolean myInsertBrackets; public ErlangKeywordInsertHandler(boolean needQuotas, boolean insertBrackets) { myNeedQuotas = needQuotas; myInsertBrackets = insertBrackets; } @Override protected boolean placeCaretInsideParentheses(InsertionContext context, LookupElement item) { return true; } @Override public void handleInsert(@NotNull InsertionContext context, LookupElement item) { super.handleInsert(context, item); Editor editor = context.getEditor(); Document document = editor.getDocument(); if (!document.getText().substring(context.getTailOffset()).startsWith(".")) { document.insertString(context.getTailOffset(), "."); } if (insertQuotas()) doInsert(editor, document, "\"", "\""); if (insertBrackets()) doInsert(editor, document, "[", "]"); } private static void doInsert(@NotNull Editor editor, @NotNull Document document, @NotNull String open, @NotNull String closed) { int offset = editor.getCaretModel().getOffset(); document.insertString(offset, open); document.insertString(offset + 1, closed); editor.getCaretModel().moveToOffset(offset + 1); } private boolean insertBrackets() { return myInsertBrackets; } private boolean insertQuotas() { return myNeedQuotas; } } }