package com.siberika.idea.pascal.ide.actions;
import com.intellij.lang.LanguageCodeInsightActionHandler;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.psi.util.PsiTreeUtil;
import com.siberika.idea.pascal.PascalBundle;
import com.siberika.idea.pascal.lang.psi.PasEntityScope;
import com.siberika.idea.pascal.lang.psi.PascalStructType;
import com.siberika.idea.pascal.lang.psi.impl.PasField;
import com.siberika.idea.pascal.lang.psi.impl.PascalRoutineImpl;
import com.siberika.idea.pascal.util.EditorUtil;
import com.siberika.idea.pascal.util.PsiUtil;
import com.siberika.idea.pascal.util.StrUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.LinkedHashSet;
/**
* Author: George Bakhtadze
* Date: 02/07/2015
*/
public class GotoSuper implements LanguageCodeInsightActionHandler {
private static final Logger LOG = Logger.getInstance(GotoSuper.class.getName());
public static final Integer LIMIT_NONE = null;
static final Integer LIMIT_FIRST_ATTEMPT = 5; // for first attempts
static Integer calcRemainingLimit(Collection<PasEntityScope> targets, Integer limit) {
return limit != null ? limit - targets.size() : null;
}
@Override
public boolean isValidFor(Editor editor, PsiFile file) {
return false;
}
@Override
public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
PsiElement el = file.findElementAt(editor.getCaretModel().getOffset());
Collection<PasEntityScope> targets = retrieveGotoSuperTargets(el);
if (!targets.isEmpty()) {
EditorUtil.navigateTo(editor, PascalBundle.message("navigate.title.goto.super"), targets);
}
}
public static Collection<PasEntityScope> retrieveGotoSuperTargets(PsiElement el) {
LinkedHashSet<PasEntityScope> targets = new LinkedHashSet<PasEntityScope>();
// cases el is: struct type, method decl, method impl
PascalRoutineImpl routine = PsiTreeUtil.getParentOfType(el, PascalRoutineImpl.class);
if (routine != null) {
getRoutineTarget(targets, routine);
} else {
getParentStructs(targets, PsiUtil.getStructByElement(el));
}
return targets;
}
public static void getParentStructs(Collection<PasEntityScope> targets, PasEntityScope struct) {
if (struct instanceof PascalStructType) {
for (SmartPsiElementPointer<PasEntityScope> parent : struct.getParentScope()) {
PasEntityScope el = parent.getElement();
addTarget(targets, el);
getParentStructs(targets, el);
}
}
}
private static void addTarget(Collection<PasEntityScope> targets, PasEntityScope target) {
if (target != null) {
targets.add(target);
}
}
private static void addTarget(Collection<PasEntityScope> targets, PasField target) {
if ((target != null) && (target.getElement() instanceof PasEntityScope)) {
targets.add((PasEntityScope) target.getElement());
}
}
private static void getRoutineTarget(Collection<PasEntityScope> targets, PascalRoutineImpl routine) {
if (null == routine) {
return;
}
PasEntityScope scope = routine.getContainingScope();
if (scope instanceof PascalStructType) {
extractMethodsByName(targets, PsiUtil.extractSmartPointers(scope.getParentScope()), routine, true, LIMIT_NONE, 0);
}
}
private static final int MAX_RECURSION_COUNT = 100;
/**
* Extracts methods with same name as routine from the given scopes and places them into targets collection
* @param targets target collection
* @param scopes scopes where to search methods
* @param routine routine which name to search
*/
static void extractMethodsByName(Collection<PasEntityScope> targets, Collection<PasEntityScope> scopes, PascalRoutineImpl routine, boolean handleParents, Integer limit, int recursionCount) {
if (recursionCount > MAX_RECURSION_COUNT) {
throw new IllegalStateException("Recursion limit reached");
}
for (PasEntityScope scope : scopes) {
if ((limit != null) && (limit <= targets.size())) {
return;
}
if (scope != null) {
if (scope instanceof PascalStructType) {
PasField field = scope.getField(StrUtil.getFieldName(PsiUtil.getFieldName(routine)));
if ((field != null) && (field.fieldType == PasField.FieldType.ROUTINE)) {
addTarget(targets, field);
}
if (handleParents) {
extractMethodsByName(targets, PsiUtil.extractSmartPointers(scope.getParentScope()), routine, true, calcRemainingLimit(targets, limit), recursionCount++);
}
}
} else {
LOG.info("Invalid scope pointer resolved while extracting methods for: " + routine);
}
}
}
@Override
public boolean startInWriteAction() {
return false;
}
}