blob: 19d22b24871b887d74f420b53a1f8d94a383dd13 [file]
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.grails;
import com.intellij.codeInsight.TailType;
import com.intellij.codeInsight.TailTypes;
import com.intellij.codeInsight.completion.CompletionContributor;
import com.intellij.codeInsight.completion.CompletionInitializationContext;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionProvider;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.completion.TagNameReferenceCompletionProvider;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInsight.lookup.TailTypeDecorator;
import com.intellij.lang.html.HTMLLanguage;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlTokenType;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.grails.fileType.GspFileType;
import org.jetbrains.plugins.grails.lang.gsp.GspLanguage;
import org.jetbrains.plugins.grails.lang.gsp.psi.groovy.api.GspOuterHtmlElement;
import org.jetbrains.plugins.grails.lang.gsp.psi.gsp.api.GspFile;
import org.jetbrains.plugins.grails.lang.gsp.psi.gsp.api.directive.GspDirective;
import org.jetbrains.plugins.grails.lang.gsp.psi.gsp.api.gtag.GspGrailsTag;
import org.jetbrains.plugins.grails.lang.gsp.psi.gsp.api.gtag.GspXmlRootTag;
import static com.intellij.patterns.XmlPatterns.not;
import static com.intellij.patterns.XmlPatterns.or;
import static com.intellij.patterns.XmlPatterns.psiElement;
import static com.intellij.patterns.XmlPatterns.psiFile;
import static com.intellij.patterns.XmlPatterns.string;
import static com.intellij.patterns.XmlPatterns.virtualFile;
import static com.intellij.patterns.XmlPatterns.xmlTag;
/**
* @author Maxim.Medvedev
*/
public final class GspCompletionContributor extends CompletionContributor {
public GspCompletionContributor() {
extend(CompletionType.BASIC,
psiElement(XmlTokenType.XML_NAME).withText(not(string().contains(":"))).withParent(
or(
xmlTag().inFile(
psiFile().withOriginalFile(
psiFile().withLanguage(HTMLLanguage.INSTANCE).withVirtualFile(
virtualFile().ofType(GspFileType.GSP_FILE_TYPE)))),
psiElement(PsiErrorElement.class).inFile(
psiFile().withOriginalFile(
psiFile().withLanguage(HTMLLanguage.INSTANCE).withVirtualFile(
virtualFile().ofType(GspFileType.GSP_FILE_TYPE))))
)
),
new CompletionProvider<>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
@NotNull ProcessingContext context,
@NotNull CompletionResultSet result) {
PsiElement element = parameters.getPosition();
if (PsiImplUtil.isLeafElementOfType(element.getPrevSibling(), XmlTokenType.XML_END_TAG_START)) {
PsiElement gspElement =
element.getContainingFile().getViewProvider().findElementAt(parameters.getOffset(), GspLanguage.INSTANCE);
assert gspElement instanceof GspOuterHtmlElement;
GspGrailsTag tag = PsiTreeUtil.getParentOfType(gspElement, GspGrailsTag.class, true);
if (tag != null) {
LookupElementBuilder builder = LookupElementBuilder.create(tag.getName());
result.addElement(TailTypeDecorator.withTail(AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE.applyPolicy(builder),
TailType.createSimpleTailType('>')));
}
}
else {
final GspFile gspFile = (GspFile)element.getContainingFile().getViewProvider().getPsi(GspLanguage.INSTANCE);
assert gspFile != null;
GspXmlRootTag rootTag = gspFile.getRootTag();
assert rootTag != null;
for (LookupElement e : TagNameReferenceCompletionProvider.getTagNameVariants(rootTag, "")) {
result.addElement(e);
}
}
}
});
extend(CompletionType.BASIC, psiElement().afterLeaf("<%@", "@{").inside(GspDirective.class),
new CompletionProvider<>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
@NotNull ProcessingContext context,
@NotNull CompletionResultSet result) {
for (String directiveName : ContainerUtil.ar("page", "taglib")) {
result.addElement(TailTypeDecorator.withTail(LookupElementBuilder.create(directiveName), TailTypes.spaceType()));
}
}
});
}
@Override
public void beforeCompletion(@NotNull CompletionInitializationContext context) {
final PsiFile file = context.getFile();
if (!(file instanceof GspFile)) {
return;
}
final int offset = context.getEditor().getCaretModel().getOffset();
final PsiElement element = file.findElementAt(offset);
if (element == null) {
return;
}
final IElementType elementType = element.getNode().getElementType();
if (elementType == XmlTokenType.XML_END_TAG_START && element.getTextRange().getStartOffset() < offset ||
elementType == XmlTokenType.XML_TAG_NAME) {
context.setDummyIdentifier("");
}
}
}