package org.jetbrains.plugins.cucumber.psi.annotator;
import com.intellij.lang.ASTNode;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.cucumber.psi.*;
import org.jetbrains.plugins.cucumber.psi.impl.*;
import org.jetbrains.plugins.cucumber.steps.AbstractStepDefinition;
import org.jetbrains.plugins.cucumber.steps.reference.CucumberStepReference;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Roman.Chernyatchik
*/
public class GherkinAnnotatorVisitor extends GherkinElementVisitor {
private final AnnotationHolder myHolder;
public GherkinAnnotatorVisitor(@NotNull final AnnotationHolder holder) {
myHolder = holder;
}
private void highlight(final PsiElement element, final TextAttributesKey colorKey) {
myHolder.createInfoAnnotation(element, null).setTextAttributes(colorKey);
}
private void highlight(final PsiElement element, TextRange range, final TextAttributesKey colorKey) {
range = range.shiftRight(element.getTextOffset());
myHolder.createInfoAnnotation(range, null).setTextAttributes(colorKey);
}
@Override
public void visitElement(final PsiElement element) {
ProgressManager.checkCanceled();
super.visitElement(element);
}
@Override
public void visitStep(GherkinStep step) {
final PsiReference[] references = step.getReferences();
if (references.length != 1 || !(references[0] instanceof CucumberStepReference)) return;
CucumberStepReference reference = (CucumberStepReference)references[0];
final AbstractStepDefinition definition = reference.resolveToDefinition();
if (definition != null) {
final List<TextRange> parameterRanges = GherkinPsiUtil.buildParameterRanges(step, definition,
reference.getRangeInElement().getStartOffset());
if (parameterRanges == null) return;
for (TextRange range : parameterRanges) {
if (range.getLength() > 0) {
highlight(step, range, GherkinHighlighter.REGEXP_PARAMETER);
}
}
highlightOutlineParams(step, reference);
}
}
@Override
public void visitScenarioOutline(GherkinScenarioOutline outline) {
super.visitScenarioOutline(outline);
final GherkinStepParameter[] params = PsiTreeUtil.getChildrenOfType(outline, GherkinStepParameter.class);
if (params != null) {
for (GherkinStepParameter param : params) {
highlight(param, GherkinHighlighter.OUTLINE_PARAMETER_SUBSTITUTION);
}
}
final ASTNode[] braces = outline.getNode().getChildren(TokenSet.create(GherkinTokenTypes.STEP_PARAMETER_BRACE));
for (ASTNode brace : braces) {
highlight(brace.getPsi(), GherkinHighlighter.REGEXP_PARAMETER);
}
}
@Override
public void visitTableHeaderRow(GherkinTableHeaderRowImpl row) {
super.visitTableRow(row);
ProgressManager.checkCanceled();
final GherkinTableImpl table = GherkinTableNavigator.getTableByRow(row);
final GherkinExamplesBlockImpl examplesSection = table != null
? GherkinExamplesNavigator.getExamplesByTable(table)
: null;
if (examplesSection == null) {
// do noting if table isn't in Examples section
return;
}
final List<GherkinTableCell> cells = row.getPsiCells();
for (PsiElement cell : cells) {
highlight(cell, GherkinHighlighter.TABLE_HEADER_CELL);
}
}
private void highlightOutlineParams(@NotNull final GherkinStep step, @NotNull final CucumberStepReference reference) {
final List<String> realSubstitutions = getRealSubstitutions(step);
if (realSubstitutions != null && !realSubstitutions.isEmpty()) {
// regexp for searching outline parameters substitutions
final StringBuilder regexp = new StringBuilder();
regexp.append("<(");
for (String substitution : realSubstitutions) {
if (regexp.length() > 2) {
regexp.append("|");
}
regexp.append(Pattern.quote(substitution));
}
regexp.append(")>");
// for each substitution - add highlighting
final Pattern pattern = Pattern.compile(regexp.toString());
// highlight in step name
final int textStartOffset = reference.getRangeInElement().getStartOffset();
highlightOutlineParamsForText(step.getStepName(), textStartOffset, pattern, step);
// highlight in pystring
final GherkinPystring pystring = step.getPystring();
if (pystring != null) {
final int textOffset = pystring.getTextOffset() - step.getTextOffset();
highlightOutlineParamsForText(pystring.getText(), textOffset, pattern, step);
}
// highlight in table
final PsiElement table = step.getTable();
if (table != null) {
final int textOffset = table.getTextOffset() - step.getTextOffset();
highlightOutlineParamsForText(table.getText(), textOffset, pattern, step);
}
}
}
private void highlightOutlineParamsForText(final String text, final int textStartInElementOffset, final Pattern pattern,
final GherkinStep step) {
if (StringUtil.isEmpty(text)) {
return;
}
final Matcher matcher = pattern.matcher(text);
boolean result = matcher.find();
if (result) {
do {
final String substitution = matcher.group(1);
if (!StringUtil.isEmpty(substitution)) {
final int start = matcher.start(1);
final int end = matcher.end(1);
final TextRange range = new TextRange(start, end).shiftRight(textStartInElementOffset);
highlight(step, range, GherkinHighlighter.OUTLINE_PARAMETER_SUBSTITUTION);
}
result = matcher.find();
} while (result);
}
}
@Nullable
private static List<String> getRealSubstitutions(@NotNull final GherkinStep step) {
final List<String> possibleSubstitutions = step.getParamsSubstitutions();
if (!possibleSubstitutions.isEmpty()) {
// get step definition
final GherkinStepsHolder holder = step.getStepHolder();
// if step is in Scenario Outline
if (holder instanceof GherkinScenarioOutlineImpl) {
// then get header cell
final GherkinScenarioOutlineImpl outline = (GherkinScenarioOutlineImpl)holder;
final List<GherkinExamplesBlock> examplesBlocks = outline.getExamplesBlocks();
if (examplesBlocks.isEmpty()) {
return null;
}
final GherkinTable table = examplesBlocks.get(0).getTable();
if (table == null) {
return null;
}
final GherkinTableRow header = table.getHeaderRow();
assert header != null;
final List<GherkinTableCell> headerCells = header.getPsiCells();
// fetch headers
final List<String> headers = new ArrayList<>(headerCells.size() + 1);
for (PsiElement headerCell : headerCells) {
headers.add(headerCell.getText().trim());
}
// filter used substitutions names
final List<String> realSubstitutions = new ArrayList<>(possibleSubstitutions.size() + 1);
for (String substitution : possibleSubstitutions) {
if (headers.contains(substitution)) {
realSubstitutions.add(substitution);
}
}
return realSubstitutions.isEmpty() ? null : realSubstitutions;
}
}
return null;
}
}