/*
* Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan
*
* 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 com.goide.completion;
import com.goide.GoConstants;
import com.goide.psi.*;
import com.goide.psi.impl.GoElementFactory;
import com.goide.runconfig.testing.GoTestFunctionType;
import com.goide.runconfig.testing.frameworks.gotest.GotestGenerateAction;
import com.goide.sdk.GoPackageUtil;
import com.goide.stubs.index.GoFunctionIndex;
import com.goide.stubs.index.GoIdFilter;
import com.goide.stubs.index.GoMethodIndex;
import com.goide.stubs.types.GoMethodDeclarationStubElementType;
import com.goide.util.GoUtil;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ProcessingContext;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.indexing.IdFilter;
import com.intellij.util.text.UniqueNameGenerator;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Set;
public class GoTestFunctionCompletionProvider extends CompletionProvider<CompletionParameters> {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {
Project project = parameters.getPosition().getProject();
PsiFile file = parameters.getOriginalFile();
PsiDirectory containingDirectory = file.getContainingDirectory();
if (file instanceof GoFile && containingDirectory != null) {
CompletionResultSet resultSet = result.withPrefixMatcher(new CamelHumpMatcher(result.getPrefixMatcher().getPrefix(), false));
Collection<String> allPackageFunctionNames = collectAllFunctionNames(containingDirectory);
Set<String> allTestFunctionNames = collectAllTestNames(allPackageFunctionNames, project, (GoFile)file);
String fileNameWithoutTestPrefix = StringUtil.trimEnd(file.getName(), GoConstants.TEST_SUFFIX_WITH_EXTENSION) + ".go";
GlobalSearchScope packageScope = GoPackageUtil.packageScope(containingDirectory, ((GoFile)file).getCanonicalPackageName());
GlobalSearchScope scope = new GoUtil.ExceptTestsScope(packageScope);
IdFilter idFilter = GoIdFilter.getFilesFilter(scope);
for (String functionName : allPackageFunctionNames) {
GoFunctionIndex.process(functionName, project, scope, idFilter, declaration -> {
addVariants(declaration, functionName, fileNameWithoutTestPrefix, allTestFunctionNames, resultSet);
return false;
});
}
Collection<String> methodKeys = ContainerUtil.newTroveSet();
StubIndex.getInstance().processAllKeys(GoMethodIndex.KEY, new CancellableCollectProcessor<>(methodKeys), scope, idFilter);
for (String key : methodKeys) {
Processor<GoMethodDeclaration> processor = declaration -> {
GoMethodDeclarationStubElementType.calcTypeText(declaration);
String typeText = key.substring(Math.min(key.indexOf('.') + 1, key.length()));
String methodName = declaration.getName();
if (methodName != null) {
if (!declaration.isPublic() || declaration.isBlank()) {
return true;
}
String lookupString = !typeText.isEmpty() ? StringUtil.capitalize(typeText) + "_" + methodName : methodName;
addVariants(declaration, lookupString, fileNameWithoutTestPrefix, allTestFunctionNames, resultSet);
}
return true;
};
GoMethodIndex.process(key, project, scope, idFilter, processor);
}
}
}
private static void addVariants(@NotNull GoFunctionOrMethodDeclaration declaration,
@NotNull String functionName,
@NotNull String fileNameWithoutTestPrefix,
@NotNull Set<String> allTestFunctionNames,
@NotNull CompletionResultSet resultSet) {
int priority = fileNameWithoutTestPrefix.equals(declaration.getContainingFile().getName()) ? 5 : 0;
addLookupElement(GoConstants.TEST_PREFIX + functionName, priority, allTestFunctionNames, resultSet);
addLookupElement(GoConstants.BENCHMARK_PREFIX + functionName, priority, allTestFunctionNames, resultSet);
addLookupElement(GoConstants.EXAMPLE_PREFIX + functionName, priority, allTestFunctionNames, resultSet);
}
private static void addLookupElement(@NotNull String lookupString,
int initialPriority,
@NotNull Set<String> allTestFunctionNames,
@NotNull CompletionResultSet result) {
int priority = initialPriority;
if (allTestFunctionNames.contains(lookupString)) {
priority -= 5;
lookupString = UniqueNameGenerator.generateUniqueName(lookupString, allTestFunctionNames);
}
result.addElement(PrioritizedLookupElement.withPriority(LookupElementBuilder.create(lookupString)
.withInsertHandler(GenerateTestInsertHandler.INSTANCE), priority));
}
@NotNull
private static Set<String> collectAllFunctionNames(@NotNull PsiDirectory directory) {
GlobalSearchScope packageScope = GoPackageUtil.packageScope(directory, null);
IdFilter packageIdFilter = GoIdFilter.getFilesFilter(packageScope);
Set<String> result = ContainerUtil.newHashSet();
StubIndex.getInstance().processAllKeys(GoFunctionIndex.KEY, new CancellableCollectProcessor<String>(result) {
@Override
protected boolean accept(String s) {
return !"_".equals(s) && StringUtil.isCapitalized(s);
}
}, packageScope, packageIdFilter);
return result;
}
@NotNull
private static Set<String> collectAllTestNames(@NotNull Collection<String> names, @NotNull Project project, @NotNull GoFile file) {
Set<String> result = ContainerUtil.newHashSet();
GlobalSearchScope packageScope = GoPackageUtil.packageScope(file);
GlobalSearchScope scope = new GoUtil.TestsScope(packageScope);
IdFilter idFilter = GoIdFilter.getFilesFilter(packageScope);
for (String name : names) {
if (GoTestFunctionType.fromName(name) != null) {
GoFunctionIndex.process(name, project, scope, idFilter, declaration -> {
result.add(name);
return false;
});
}
}
return result;
}
private static class GenerateTestInsertHandler implements InsertHandler<LookupElement> {
public static final InsertHandler<LookupElement> INSTANCE = new GenerateTestInsertHandler();
@Override
public void handleInsert(InsertionContext context, LookupElement item) {
PsiElement elementAt = context.getFile().findElementAt(context.getStartOffset());
GoFunctionOrMethodDeclaration declaration = PsiTreeUtil.getNonStrictParentOfType(elementAt, GoFunctionOrMethodDeclaration.class);
if (declaration != null) {
GoTestFunctionType testingType = GoTestFunctionType.fromName(declaration.getName());
if (testingType != null) {
String testingQualifier = null;
if (testingType.getParamType() != null) {
testingQualifier = GotestGenerateAction.importTestingPackageIfNeeded(declaration.getContainingFile());
}
GoBlock block = declaration.getBlock();
if (block == null) {
block = (GoBlock)declaration.add(GoElementFactory.createBlock(context.getProject()));
}
else if (block.getStatementList().isEmpty()) {
block = (GoBlock)block.replace(GoElementFactory.createBlock(context.getProject()));
}
GoSignature newSignature = GoElementFactory.createFunctionSignatureFromText(context.getProject(),
testingType.getSignature(testingQualifier));
GoSignature signature = declaration.getSignature();
if (signature == null) {
declaration.addBefore(newSignature, block);
}
else if (signature.getParameters().getParameterDeclarationList().isEmpty()) {
signature.replace(newSignature);
}
Document document = context.getDocument();
PsiDocumentManager.getInstance(context.getProject()).doPostponedOperationsAndUnblockDocument(document);
GoStatement firstStatement = ContainerUtil.getFirstItem(block.getStatementList());
if (firstStatement != null) {
context.getEditor().getCaretModel().moveToOffset(firstStatement.getTextRange().getStartOffset());
}
else {
PsiElement lbrace = block.getLbrace();
PsiElement rbrace = block.getRbrace();
int lbraceEndOffset = lbrace.getTextRange().getEndOffset();
int startLine = document.getLineNumber(lbraceEndOffset);
int endLine = rbrace != null ? document.getLineNumber(rbrace.getTextRange().getStartOffset()) : startLine;
int lineDiff = endLine - startLine;
if (lineDiff < 2) {
document.insertString(lbraceEndOffset, StringUtil.repeat("\n", 2 - lineDiff));
}
int offsetToMove = document.getLineStartOffset(startLine + 1);
context.getEditor().getCaretModel().moveToOffset(offsetToMove);
CodeStyleManager.getInstance(context.getProject()).adjustLineIndent(document, offsetToMove);
}
}
}
}
}
}