/* * Copyright 2011-present Greg Shrago * * 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.jflex.editor; import com.intellij.codeInsight.TailType; 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.Language; import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.text.StringUtil; import com.intellij.patterns.StandardPatterns; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileFactory; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.impl.source.tree.LeafPsiElement; 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 org.intellij.grammar.parser.GeneratedParserUtilBase; import org.intellij.jflex.parser.JFlexFileType; import org.intellij.jflex.psi.*; import org.intellij.jflex.psi.impl.JFlexFileImpl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; import static com.intellij.patterns.PlatformPatterns.psiElement; import static org.intellij.jflex.psi.JFlexTypes.*; /** * @author gregsh */ public class JFlexCompletionContributor extends CompletionContributor { public JFlexCompletionContributor() { extend(CompletionType.BASIC, psiElement().inFile(StandardPatterns.instanceOf(JFlexFileImpl.class)), new CompletionProvider<CompletionParameters>() { @Override protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) { PsiElement position = parameters.getPosition(); JFlexCompositeElement parent = PsiTreeUtil.getParentOfType( position, JFlexDeclarationsSection.class, JFlexRule.class, JFlexJavaCode.class, JFlexJavaType.class); boolean inJava = parent instanceof JFlexJavaCode || parent instanceof JFlexJavaType; if (!inJava && parameters.getInvocationCount() < 2) { int start = position.getTextRange().getStartOffset(); CompletionResultSet result2 = start > 0 && parameters.getEditor().getDocument().getText().charAt(start - 1) == '%' ? result.withPrefixMatcher(result.getPrefixMatcher().cloneWithPrefix("%" + result.getPrefixMatcher().getPrefix())) : result; for (String keyword : suggestKeywords(parameters.getPosition())) { result2.addElement(createKeywordLookupItem(keyword)); } } } }); } @Override public void beforeCompletion(@NotNull CompletionInitializationContext context) { context.setDummyIdentifier(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED); } private static Collection<String> suggestKeywords(PsiElement position) { if (position instanceof LeafPsiElement && ((LeafPsiElement)position).getElementType() == FLEX_STRING) return Collections.emptySet(); if (PsiTreeUtil.getParentOfType(position, JFlexUserCodeSection.class) != null) return Collections.emptySet(); boolean inDeclare = PsiTreeUtil.getParentOfType(position, JFlexDeclarationsSection.class) != null; PsiFile flexFile = position.getContainingFile(); Language language = flexFile.getLanguage(); String flexFileText = flexFile.getText(); int positionOffset = position.getTextRange().getEndOffset(); JFlexExpression expr = PsiTreeUtil.getParentOfType(position, JFlexExpression.class); final boolean inMacro = expr != null && expr.getText().substring(0, positionOffset - expr.getTextRange().getStartOffset()).indexOf('\n') == -1; String fragment = (inDeclare ? "%%\n" : "%%\n%%\n") + flexFileText.substring(flexFileText.lastIndexOf("%%", positionOffset-1) + 2, positionOffset); boolean empty = StringUtil.isEmptyOrSpaces(fragment); final String text = empty ? CompletionInitializationContext.DUMMY_IDENTIFIER : fragment; int completionOffset = empty ? 0 : fragment.length(); PsiFile file = PsiFileFactory.getInstance(flexFile.getProject()).createFileFromText("a.flex", language, text, true, false); GeneratedParserUtilBase.CompletionState state = new GeneratedParserUtilBase.CompletionState(completionOffset) { @Nullable @Override public String convertItem(Object o) { if (o == null) return null; if (o instanceof IElementType[]) return super.convertItem(o); if (o == FLEX_ID || o == FLEX_CHAR || o == FLEX_STRING || o == FLEX_NUMBER || o == FLEX_RAW || o == FLEX_VERSION) return null; String text = o.toString(); return text.length() == 1 || inMacro && text.startsWith("%") || !inMacro && text.startsWith("[") ? null : text; } }; file.putUserData(GeneratedParserUtilBase.COMPLETION_STATE_KEY, state); TreeUtil.ensureParsed(file.getNode()); return state.items; } private static LookupElement createKeywordLookupItem(String keyword) { LookupElementBuilder builder = LookupElementBuilder.create(keyword.toLowerCase()).withCaseSensitivity(false).bold(); boolean braces = keyword.endsWith("{") || keyword.endsWith("}"); if (!braces) { return keyword.startsWith("%") ? TailTypeDecorator.withTail(builder, TailType.SPACE) : builder; } else { final String closing = keyword.endsWith("{") ? keyword.substring(0, keyword.length()-1) + "}" : null; return PrioritizedLookupElement.withPriority(builder.withInsertHandler((context, item) -> { int caret = context.getTailOffset(); Document document = context.getDocument(); StringBuilder sb = new StringBuilder("\n"); caret += sb.length(); if (closing != null) { int indentSize = CodeStyleSettingsManager.getInstance(context.getProject()). getCurrentSettings().getIndentSize(JFlexFileType.INSTANCE); sb.append(StringUtil.repeat(" ", indentSize)); caret += indentSize; sb.append("\n").append(closing).append("\n"); } document.insertString(context.getTailOffset(), sb); context.getEditor().getCaretModel().moveToOffset(caret); }), 1.d / keyword.length()); } } }