package com.siberika.idea.pascal.editor; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.intellij.codeInsight.intention.HighPriorityAction; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.LowPriorityAction; import com.intellij.codeInsight.intention.impl.BaseIntentionAction; import com.intellij.codeInsight.template.Template; import com.intellij.codeInsight.template.TemplateEditingListener; import com.intellij.codeInsight.template.TemplateManager; import com.intellij.codeInspection.SmartHashMap; import com.intellij.lang.ASTNode; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.source.tree.TreeUtil; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.SmartList; import com.siberika.idea.pascal.PascalBundle; import com.siberika.idea.pascal.ide.actions.SectionToggle; import com.siberika.idea.pascal.lang.psi.PasArgumentList; import com.siberika.idea.pascal.lang.psi.PasAssignPart; import com.siberika.idea.pascal.lang.psi.PasBlockBody; import com.siberika.idea.pascal.lang.psi.PasBlockGlobal; import com.siberika.idea.pascal.lang.psi.PasBlockLocal; import com.siberika.idea.pascal.lang.psi.PasClassProperty; import com.siberika.idea.pascal.lang.psi.PasClassPropertySpecifier; import com.siberika.idea.pascal.lang.psi.PasCompoundStatement; import com.siberika.idea.pascal.lang.psi.PasConstDeclaration; import com.siberika.idea.pascal.lang.psi.PasConstSection; import com.siberika.idea.pascal.lang.psi.PasEnumType; import com.siberika.idea.pascal.lang.psi.PasExpr; import com.siberika.idea.pascal.lang.psi.PasFormalParameter; import com.siberika.idea.pascal.lang.psi.PasFormalParameterSection; import com.siberika.idea.pascal.lang.psi.PasFullyQualifiedIdent; import com.siberika.idea.pascal.lang.psi.PasImplDeclSection; import com.siberika.idea.pascal.lang.psi.PasReferenceExpr; import com.siberika.idea.pascal.lang.psi.PasRoutineImplDecl; import com.siberika.idea.pascal.lang.psi.PasTypeDeclaration; import com.siberika.idea.pascal.lang.psi.PasTypeSection; import com.siberika.idea.pascal.lang.psi.PasTypes; import com.siberika.idea.pascal.lang.psi.PasUnitInterface; import com.siberika.idea.pascal.lang.psi.PasVarSection; import com.siberika.idea.pascal.lang.psi.PascalNamedElement; 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.PascalExpression; import com.siberika.idea.pascal.lang.psi.impl.PascalRoutineImpl; import com.siberika.idea.pascal.util.DocUtil; import com.siberika.idea.pascal.util.EditorUtil; import com.siberika.idea.pascal.util.PosUtil; import com.siberika.idea.pascal.util.PreserveCaretTemplateAdapter; import com.siberika.idea.pascal.util.PsiUtil; import com.siberika.idea.pascal.util.StrUtil; import org.jetbrains.annotations.NotNull; import java.util.Collections; import java.util.List; import java.util.Map; import static com.siberika.idea.pascal.PascalBundle.message; /** * Author: George Bakhtadze * Date: 06/10/2013 */ public abstract class PascalActionDeclare extends BaseIntentionAction { private static final Logger LOG = Logger.getInstance(PascalActionDeclare.class.getName()); public static final int MAX_SECTION_LEVELS = 20; final List<FixActionData> fixActionDataArray; private final String name; protected final PsiElement scope; private static final String TPL_VAR_RETURN_TYPE = "RETURN_TYPE"; private static final String TPL_VAR_CODE = "CODE"; private static final String TPL_VAR_TYPE = "TYPE"; private static final String TPL_VAR_TYPES = "TYPE"; private static final String TPL_VAR_ARGS = "ARG"; private static final String TPL_VAR_CONST_EXPR = "CONST_EXPR"; private static final String TPL_VAR_PARAMS = "PARAMS"; abstract void calcData(final PsiFile file, final FixActionData data); public PascalActionDeclare(String name, PascalNamedElement element, PsiElement scope) { this.name = name; this.scope = scope; this.fixActionDataArray = new SmartList<FixActionData>(data(element)); } public static FixActionData data(PascalNamedElement element) { return new FixActionData(element); } public void addData(FixActionData data) { fixActionDataArray.add(data); } protected void onInvoke() { } @NotNull @Override public String getText() { return name; } @NotNull @Override public String getFamilyName() { return PascalBundle.message("action.familyName"); } @Override public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { return true; } @Override public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { onInvoke(); final Document document = editor.getDocument(); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { final List<FixActionData> sorted = new SmartList<FixActionData>(fixActionDataArray); Collections.sort(sorted); final RangeMarker marker = document.createRangeMarker(editor.getCaretModel().getOffset(), editor.getCaretModel().getOffset()); new WriteCommandAction(project, name) { @Override protected void run(@NotNull Result result) throws Throwable { Editor globalTemplateEditor = null; boolean templated = false; for (final FixActionData actionData : sorted) { calcData(file, actionData); templated = templated | (actionData.dataType == FixActionData.DataType.TEMPLATE) | (actionData.dataType == FixActionData.DataType.COMPLEX_TEMPLATE); if (!StringUtil.isEmpty(actionData.text)) { Document doc = DocUtil.getDocument(actionData.parent); final Editor edit; if ((doc != null) && (doc != document)) { // Another document, open editor edit = FileEditorManager.getInstance(project).openTextEditor( new OpenFileDescriptor(project, actionData.parent.getContainingFile().getVirtualFile(), actionData.offset), true); } else { if (actionData.parent.getContainingFile() != file) { EditorUtil.showErrorHint(PascalBundle.message("action.error.cantmodify"), EditorUtil.getHintPos(editor)); return; // Another file without document } doc = document; edit = editor; } if (actionData.dataType == FixActionData.DataType.COMPLEX_TEMPLATE) { globalTemplateEditor = edit; } if (doc.isWritable()) { doModify(project, edit, doc, actionData, new PreserveCaretTemplateAdapter(editor, file, marker, actionData.parent, PascalActionDeclare.this)); if ((actionData.dataType == FixActionData.DataType.TEXT) && (globalTemplateEditor == null)) { DocUtil.reformat(actionData.parent, true); } } else { EditorUtil.showErrorHint(PascalBundle.message("action.error.cantmodify"), EditorUtil.getHintPos(edit)); } } } if (globalTemplateEditor != null) { FixActionData globalTemplateData = sorted.iterator().next(); handleGlobalTemplateEditor(project, globalTemplateEditor, globalTemplateData, new PreserveCaretTemplateAdapter(editor, file, marker, globalTemplateData.parent, PascalActionDeclare.this)); } if (!templated) { afterExecution(editor, file); } } }.execute(); } }); } public void afterExecution(Editor editor, PsiFile file) { } private void handleGlobalTemplateEditor(Project project, final Editor editor, final FixActionData data, final TemplateEditingListener templateEditingListener) { final TemplateManager templateManager = TemplateManager.getInstance(project); final Template template = DocUtil.createTemplate(data.parent.getText(), data.variableDefaults, true); DocUtil.removeTemplateVariables(editor.getDocument(), data.parent.getTextRange()); editor.getCaretModel().moveToOffset(data.parent.getTextRange().getStartOffset()); templateManager.startTemplate(editor, template, false, null, templateEditingListener); } private void doModify(final Project project, final Editor editor, final Document doc, final FixActionData actionData, final TemplateEditingListener templateEditingListener) { final TemplateManager templateManager = TemplateManager.getInstance(project); final Template template = actionData.dataType == FixActionData.DataType.TEMPLATE ? DocUtil.createTemplate(actionData.text, actionData.variableDefaults, false) : null; actionData.offset = DocUtil.expandRangeStart(doc, actionData.offset, DocUtil.RE_WHITESPACE); cutLFs(doc, actionData); if (editor != null) { if (template != null) { editor.getCaretModel().moveToOffset(actionData.offset); templateManager.startTemplate(editor, template, templateEditingListener); } else { DocUtil.adjustDocument(editor, actionData.offset, actionData.text); } } else { DocUtil.adjustDocument(doc, actionData.offset, actionData.text.replace(DocUtil.PLACEHOLDER_CARET, "")); } PsiDocumentManager.getInstance(project).commitDocument(doc); } private void cutLFs(Document document, FixActionData data) { if (StringUtil.isEmpty(data.text)) { return; } final int MAX = 2; int l = Math.max(0, data.offset - MAX); int r = Math.min(document.getTextLength(), data.offset + 1 + MAX); String chars = document.getText(TextRange.create(l, r)); // get text [offset - MAX, offset + MAX] // remove from data.text start sequence of linefeeds if it present both at data.text start and text starting back from offset // remove from data.text end sequence of linefeeds if it present both at data.text end and text starting from offset // TODO: possible reference chars[-1]? int cl = 0; while ((cl < Math.min(MAX, Math.min(data.offset - l, data.text.length()))) && (chars.charAt(data.offset - l - cl - 1) == '\n') && (data.text.charAt(cl) == '\n')) { cl++; } int cr = 0; while ((cr < Math.min(MAX, Math.min(chars.length() - r + data.offset + 1, data.text.length() - cl))) && (chars.charAt(r - data.offset - 1 + cr) == '\n') && (data.text.charAt(data.text.length() - 1 - cr) == '\n')) { cr++; } data.text = data.text.substring(cl, data.text.length() - cr - cl); } @Override public boolean startInWriteAction() { return false; } // Fills data parent and offset and returns True if section for new member already exists private static boolean fillMemberPlace(PsiElement scope, FixActionData data, int targetVisibility, PasField.FieldType type, Class<? extends PsiElement> sectionClass, Class<? extends PsiElement> sectionItemClass) { if (scope instanceof PascalStructType) { data.text = "\n" + PLACEHOLDER_DATA; data.parent = scope; Pair<Integer, Boolean> res = PosUtil.findPosInStruct((PascalStructType) scope, type, targetVisibility); data.offset = res.first; return res.second; } else if (scope instanceof PsiFile) { return fillParent(scope, data, sectionClass, sectionItemClass); } return false; } private static PsiElement getScope(PsiFile file, PsiElement scope) { return (scope instanceof PascalStructType) ? scope : file; } public static final Map<String, String> TYPE_VAR_DEFAULTS = StrUtil.getParams(Collections.singletonList(Pair.create(TPL_VAR_TYPE, "T"))); String calcType(FixActionData data) { String res = "T"; if (PsiUtil.isAssignLeftPart(data.element)) { String type = PascalExpression.calcAssignStatementType(PsiUtil.skipToExpressionParent(data.element)); res = type != null ? type : res; } return res; } public static class ActionCreateParameter extends PascalActionDeclare { private FixActionData otherSectionData = null; private PascalRoutineImpl routine; public ActionCreateParameter(String name, PascalNamedElement element, PsiElement scope) { super(name, element, scope); } @Override protected void onInvoke() { if (scope instanceof PascalRoutineImpl) { routine = (PascalRoutineImpl) scope; PsiElement other = SectionToggle.retrieveDeclaration((PascalRoutineImpl) scope, true); if (other instanceof PascalRoutineImpl) { if (!SectionToggle.hasParametersOrReturnType((PascalRoutineImpl) scope) && SectionToggle.hasParametersOrReturnType((PascalRoutineImpl) other)) { routine = (PascalRoutineImpl) other; } else { otherSectionData = new FixActionData(fixActionDataArray.get(0).element); otherSectionData.parent = other; addData(otherSectionData); } } } } @Override void calcData(final PsiFile file, final FixActionData data) { if (null == routine) { return; } final String tpl = "%s: $%s$"; if (data == otherSectionData) { routine = (PascalRoutineImpl) data.parent; } PasFormalParameterSection section = routine.getFormalParameterSection(); if (section != null) { data.parent = section; if (section.getFormalParameterList().isEmpty()) { data.offset = section.getTextRange().getStartOffset() + 1; data.text = tpl; } else { List<PasFormalParameter> params = section.getFormalParameterList(); data.offset = params.get(params.size() - 1).getTextRange().getEndOffset(); data.text = "; " + tpl; } } else if (routine.getNameIdentifier() != null) { data.parent = routine; data.offset = routine.getNameIdentifier().getTextRange().getEndOffset(); data.text = "(" + tpl + ")"; } if (data.parent != null) { data.text = String.format(data.text, data.element.getName(), TPL_VAR_TYPE); data.variableDefaults = StrUtil.getParams(Collections.singletonList(Pair.create(TPL_VAR_TYPE, calcType(data)))); if (otherSectionData != null) { // Modify both sections data.parent = data.element.getContainingFile(); if (data == otherSectionData) { data.dataType = FixActionData.DataType.COMPLEX_TEMPLATE; } } else { data.dataType = FixActionData.DataType.TEMPLATE; } } } } public static class ActionCreateVar extends PascalActionDeclare { private final String defaultType; public ActionCreateVar(String name, PascalNamedElement element, PsiElement scope, String defaultType) { super(name, element, scope); this.defaultType = defaultType; } @Override void calcData(final PsiFile file, final FixActionData data) { String prefix = ""; if (!fillMemberPlace(getScope(file, scope), data, PasField.Visibility.PRIVATE.ordinal(), PasField.FieldType.VARIABLE, PasVarSection.class, null)) { prefix = "\nvar "; } String type = defaultType != null ? defaultType : calcType(data); if (data.parent != null) { data.createTemplate(data.text.replace(PLACEHOLDER_DATA, String.format("%s%s: $%s$;", prefix, data.element.getName(), TPL_VAR_TYPE)), StrUtil.getParams(Collections.singletonList(Pair.create(TPL_VAR_TYPE, type)))); } } } public static class ActionCreateVarHP extends ActionCreateVar implements HighPriorityAction { public ActionCreateVarHP(String name, PascalNamedElement element, PsiElement scope, String defaultType) { super(name, element, scope, defaultType); } } public static IntentionAction newActionCreateVar(String message, PascalNamedElement namedElement, PsiElement scope, boolean priority, String defaultType) { if (priority) { return new PascalActionDeclare.ActionCreateVarHP(message, namedElement, scope, defaultType); } else { return new PascalActionDeclare.ActionCreateVar(message, namedElement, scope, defaultType); } } public static class ActionCreateProperty extends PascalActionDeclare { private FixActionData varData; public ActionCreateProperty(String name, PascalNamedElement element, @NotNull PsiElement scope) { super(name, element, scope); varData = new FixActionData(element); addData(varData); } @Override void calcData(final PsiFile file, final FixActionData data) { if (data == varData) { if (fillMemberPlace(scope, data, PasField.Visibility.PRIVATE.ordinal(), PasField.FieldType.VARIABLE, PasVarSection.class, null)) { data.text = data.text.replace(PLACEHOLDER_DATA, String.format("F%s: $%s$;", StringUtil.capitalize(data.element.getName()), TPL_VAR_TYPE)); data.dataType = FixActionData.DataType.COMPLEX_TEMPLATE; } } else { if (fillMemberPlace(scope, data, PasField.Visibility.PUBLIC.ordinal(), PasField.FieldType.PROPERTY, PasVarSection.class, null)) { data.text = data.text.replace(PLACEHOLDER_DATA, String.format("property %1$s: $%2$s$ read F%1$s write F%1$s;", StringUtil.capitalize(data.element.getName()), TPL_VAR_TYPE)); } } data.variableDefaults = StrUtil.getParams(Collections.singletonList(Pair.create(TPL_VAR_TYPE, calcType(data)))); } } public static class ActionCreatePropertyHP extends ActionCreateProperty implements HighPriorityAction { public ActionCreatePropertyHP(String name, PascalNamedElement element, @NotNull PsiElement scope) { super(name, element, scope); } } public static IntentionAction newActionCreateProperty(String message, PascalNamedElement namedElement, PsiElement scope, boolean priority) { if (priority) { return new PascalActionDeclare.ActionCreatePropertyHP(message, namedElement, scope); } else { return new PascalActionDeclare.ActionCreateProperty(message, namedElement, scope); } } public static class ActionCreateConst extends PascalActionDeclare { public ActionCreateConst(String name, PascalNamedElement element, PsiElement scope) { super(name, element, scope); } @Override void calcData(final PsiFile file, final FixActionData data) { String prefix = ""; if (!fillMemberPlace(getScope(file, scope), data, PasField.Visibility.PRIVATE.ordinal(), PasField.FieldType.CONSTANT, PasConstSection.class, PasConstDeclaration.class)) { prefix = "\nconst "; } if (data.parent != null) { data.createTemplate(data.text.replace(PLACEHOLDER_DATA, String.format("%s%s = $%s$;", prefix, data.element.getName(), TPL_VAR_CONST_EXPR)), null); } } } public static class ActionCreateConstHP extends ActionCreateConst implements HighPriorityAction { public ActionCreateConstHP(String name, PascalNamedElement element, PsiElement scope) { super(name, element, scope); } } public static class ActionCreateConstLP extends ActionCreateConst implements LowPriorityAction { public ActionCreateConstLP(String name, PascalNamedElement element, PsiElement scope) { super(name, element, scope); } } public static IntentionAction newActionCreateConst(PascalNamedElement namedElement, PsiElement scope, boolean priority) { String name = (scope instanceof PascalStructType) ? message("action.create.nestedConst") : message("action.create.const"); if (priority) { return new PascalActionDeclare.ActionCreateConstHP(name, namedElement, scope); } else { return new PascalActionDeclare.ActionCreateConstLP(name, namedElement, scope); } } public static class ActionCreateEnum extends PascalActionDeclare { public ActionCreateEnum(String name, PascalNamedElement element, PsiElement scope) { super(name, element, scope); } @Override void calcData(final PsiFile file, final FixActionData data) { PasEnumType enumType = (PasEnumType) scope; PsiElement last = PsiUtil.sortByStart(Iterables.getLast(enumType.getNamedIdentList(), null), Iterables.getLast(enumType.getExpressionList(), null), false).getFirst(); data.parent = enumType; data.offset = -1; if (last != null) { data.createTemplate(", " + data.element.getName(), null); data.offset = last.getTextRange().getEndOffset(); } else { data.createTemplate(data.element.getName(), null); ASTNode rParen = enumType.getNode().findChildByType(PasTypes.RPAREN); if (rParen != null) { data.offset = rParen.getTextRange().getStartOffset(); } } } } public static class ActionCreateType extends PascalActionDeclare { public ActionCreateType(String name, PascalNamedElement element, PsiElement scope) { super(name, element, scope); } @Override void calcData(final PsiFile file, final FixActionData data) { String prefix = ""; if (!fillMemberPlace(getScope(file, scope), data, PasField.Visibility.PRIVATE.ordinal(), PasField.FieldType.TYPE, PasTypeSection.class, PasTypeDeclaration.class)) { prefix = "\ntype "; } if (data.parent != null) { data.createTemplate(data.text.replace(PLACEHOLDER_DATA, String.format("%s%s = $%s$;", prefix, data.element.getName(), TPL_VAR_TYPE)), null); } } } public static class ActionCreateTypeHP extends ActionCreateType implements HighPriorityAction { public ActionCreateTypeHP(String name, PascalNamedElement element, PsiElement scope) { super(name, element, scope); } } public static class ActionCreateTypeLP extends ActionCreateType implements LowPriorityAction { public ActionCreateTypeLP(String name, PascalNamedElement element, PsiElement scope) { super(name, element, scope); } } public static IntentionAction newActionCreateType(PascalNamedElement namedElement, PsiElement scope, boolean priority) { String name = (scope instanceof PascalStructType) ? message("action.create.nestedType") : message("action.create.type"); if (priority) { return new PascalActionDeclare.ActionCreateTypeHP(name, namedElement, scope); } else { return new PascalActionDeclare.ActionCreateTypeLP(name, namedElement, scope); } } public static class ActionCreateRoutine extends PascalActionDeclare { private final PsiElement callScope; private final PascalNamedElement namedElement; private final PasClassPropertySpecifier spec; public ActionCreateRoutine(String name, PascalNamedElement element, PsiElement scope, PsiElement callScope, PasClassPropertySpecifier spec) { super(name, element, scope); this.callScope = callScope; this.namedElement = element; this.spec = spec; } @Override void calcData(final PsiFile file, final FixActionData data) { String type = null; if (spec != null) { PasClassProperty prop = (PasClassProperty) spec.getParent(); type = prop.getTypeID() != null ? prop.getTypeID().getText() : null; } else { PsiElement parent = PsiUtil.skipToExpressionParent(namedElement); if (parent instanceof PasAssignPart) { type = PascalExpression.calcAssignExpectedType(parent.getParent()); type = type != null ? type : ""; } } if ((scope instanceof PascalStructType) && (null == callScope)) { addToInterface(data, type); } else { addToImplementation(data, type); } } private void addToImplementation(FixActionData data, String returnType) { PsiElement block; if (scope instanceof PasRoutineImplDecl) { block = scope; } else { block = (callScope instanceof PascalRoutineImpl) ? callScope : PsiTreeUtil.getParentOfType(data.element, PasBlockBody.class); } block = block != null ? block : PsiTreeUtil.getParentOfType(data.element, PasCompoundStatement.class); if (block != null) { data.offset = block.getTextRange().getStartOffset(); data.parent = block.getParent(); Pair<String, Map<String, String>> arguments = calcArguments(data); String prefix = scope instanceof PascalStructType ? ((PascalStructType) scope).getName() + "." : ""; if (returnType != null) { arguments.getSecond().put(TPL_VAR_RETURN_TYPE, returnType); data.createTemplate(String.format("\n\nfunction %s%s(%s): $%s$;\nbegin\n$%s$\nend;", prefix, data.element.getName(), arguments.getFirst(), TPL_VAR_RETURN_TYPE, TPL_VAR_CODE), arguments.getSecond()); } else { data.createTemplate(String.format("\n\nprocedure %s%s(%s);\nbegin\n$%s$\nend;", prefix, data.element.getName(), arguments.getFirst(), TPL_VAR_CODE), arguments.getSecond()); } } } private void addToInterface(FixActionData data, String returnType) { fillMemberPlace(scope, data, PasField.Visibility.PUBLIC.ordinal(), PasField.FieldType.ROUTINE, null, null); Pair<String, Map<String, String>> arguments; if (spec != null) { if (PsiUtil.isPropertyGetter(spec)) { Map<String, String> defaults = new SmartHashMap<String, String>(); arguments = Pair.create("", defaults); } else { Map<String, String> defaults = ImmutableMap.of(TPL_VAR_TYPE, returnType); arguments = Pair.create(String.format("const value: $%s$", TPL_VAR_TYPE), defaults); returnType = null; } } else { arguments = calcArguments(data); } if (returnType != null) { arguments.getSecond().put(TPL_VAR_RETURN_TYPE, returnType); data.createTemplate(String.format("\nfunction %s(%s): $%s$;", data.element.getName(), arguments.getFirst(), TPL_VAR_RETURN_TYPE), arguments.getSecond()); } else { data.createTemplate(String.format("\nprocedure %s(%s);", data.element.getName(), arguments.getFirst()), arguments.getSecond()); } } private Pair<String, Map<String, String>> calcArguments(FixActionData data) { PasExpr expression = PsiTreeUtil.getParentOfType(data.element, PasExpr.class); StringBuilder params = new StringBuilder(); Map<String, String> defaults = new SmartHashMap<String, String>(); if ((expression != null) && (expression.getNextSibling() instanceof PasArgumentList)) { PasArgumentList args = (PasArgumentList) expression.getNextSibling(); int count = 0; for (PasExpr expr : args.getExprList()) { if (count != 0) { params.append("; "); } params.append("const $").append(TPL_VAR_ARGS).append(count).append("$").append(": $").append(TPL_VAR_TYPES).append(count).append("$"); String type = PascalExpression.infereType(expr); defaults.put(TPL_VAR_TYPES + count, type); if (expr instanceof PasReferenceExpr) { PasFullyQualifiedIdent ident = ((PasReferenceExpr) expr).getFullyQualifiedIdent(); defaults.put(TPL_VAR_ARGS + count, ident.getNamePart()); } else if (StringUtil.isNotEmpty(type)) { if (type.startsWith("T")) { defaults.put(TPL_VAR_ARGS + count, type.substring(1)); } else { defaults.put(TPL_VAR_ARGS + count, type.substring(0, 1).toLowerCase() + (count+1)); } } count++; } } return Pair.create(params.length() != 0 ? params.toString() : "$" + TPL_VAR_PARAMS + "$", defaults); } @Override public void afterExecution(Editor editor, PsiFile file) { if ((null == editor.getProject()) || fixActionDataArray.isEmpty()) { return; } FixActionData data = fixActionDataArray.iterator().next(); try { if (data.parent != null) { PsiElement routine = PsiUtil.findElementAt(data.parent, data.offset - data.parent.getTextRange().getStartOffset()); if (!(routine instanceof PascalRoutineImpl)) { routine = routine != null ? routine.getParent() : null; } if (scope instanceof PascalStructType) { if (null == callScope) { // Scope specified as FQN part if (routine instanceof PascalRoutineImpl) { PascalRoutineActions.ActionImplement act = new PascalRoutineActions.ActionImplement(message("action.implement"), (PascalNamedElement) routine); act.invoke(editor.getProject(), editor, routine.getContainingFile()); } } else { // Called within method if (routine instanceof PascalRoutineImpl) { PascalRoutineActions.ActionDeclare act = new PascalRoutineActions.ActionDeclare(message("action.declare.routine"), (PascalNamedElement) routine); act.invoke(editor.getProject(), editor, routine.getContainingFile()); } } } } } catch (ProcessCanceledException e) { throw e; } catch (Exception e) { LOG.info("Error in PascalActionDeclare.afterExecution()", e); } } } public static class ActionCreateRoutineHP extends ActionCreateRoutine implements HighPriorityAction { public ActionCreateRoutineHP(String name, PascalNamedElement element, PsiElement scope, PsiElement callScope, PasClassPropertySpecifier spec) { super(name, element, scope, callScope, spec); } } public static IntentionAction newActionCreateRoutine(String message, PascalNamedElement namedElement, PsiElement scope, PsiElement callScope, boolean priority, PasClassPropertySpecifier spec) { if (priority) { return new PascalActionDeclare.ActionCreateRoutineHP(message, namedElement, scope, callScope, spec); } else { return new PascalActionDeclare.ActionCreateRoutine(message, namedElement, scope, callScope, spec); } } private static final String PLACEHOLDER_DATA = "---"; @SuppressWarnings("unchecked") // Fills data parent and offset and returns True if section for new member already exists private static boolean fillParent(PsiElement file, FixActionData data, Class<? extends PsiElement> sectionClass, Class<? extends PsiElement> sectionItemClass) { PsiElement section = PsiUtil.getNearestAffectingDeclarationsRoot(data.element); data.text = PLACEHOLDER_DATA + "\n"; for (int i = 0; i < MAX_SECTION_LEVELS; i++) { section = section != null ? section : file; data.parent = PsiUtil.findInSameSection(section, sectionClass); if (!canAffect(data.parent, data.element)) { data.parent = PsiUtil.findInSameSection(section, PasImplDeclSection.class, PasBlockGlobal.class, PasBlockLocal.class, PasUnitInterface.class); if (canAffect(data.parent, data.element)) { data.offset = data.parent.getTextOffset(); PasUnitInterface intf = PsiTreeUtil.findChildOfType(data.parent, PasUnitInterface.class, false); // Move after "INTERFACE" ASTNode pos = intf != null ? TreeUtil.skipElements(intf.getNode().getFirstChildNode(), TokenSet.create(PasTypes.INTERFACE)) : null; if (pos != null) { data.offset = intf.getUsesClause() != null ? intf.getUsesClause().getTextRange().getEndOffset() : pos.getTextRange().getStartOffset(); data.text = "\n" + data.text; } return false; } } else { // section found and can affect target data.offset = data.parent.getTextRange().getEndOffset(); data.text = "\n" + PLACEHOLDER_DATA; if ((sectionItemClass != null) && (PsiTreeUtil.getParentOfType(data.element, sectionClass) == data.parent)) { PsiElement sectionItem = PsiTreeUtil.getParentOfType(data.element, sectionItemClass); if (sectionItem != null) { data.offset = sectionItem.getTextRange().getStartOffset(); data.text = data.text + "\n"; } } return true; } section = PsiUtil.getNearestAffectingDeclarationsRoot(section); if (i == MAX_SECTION_LEVELS - 1) { throw new RuntimeException("Error finding section"); } } data.parent = null; return false; } private static boolean canAffect(PsiElement parent, PascalNamedElement element) { return (parent != null) && (parent.getTextOffset() <= element.getTextOffset()); } /** * Author: George Bakhtadze * Date: 24/03/2015 */ static final class FixActionData implements Comparable<FixActionData> { final PascalNamedElement element; // The parent will be formatted PsiElement parent; String text = null; int offset = 0; DataType dataType = DataType.TEXT; Map<String, String> variableDefaults; public FixActionData(PascalNamedElement element) { this.element = element; } @Override public int compareTo(@NotNull FixActionData data) { return data.offset - offset; } public void createTemplate(String text, Map<String, String> variableDefaults) { assert dataType != DataType.COMPLEX_TEMPLATE; this.variableDefaults = variableDefaults; dataType = DataType.TEMPLATE; this.text = text; } private enum DataType { // the action data will insert some text TEXT, // the action data will use template engine TEMPLATE, // all data of an action will form a complex inline template (parent fields should be the same) COMPLEX_TEMPLATE } } }