package org.jetbrains.plugins.cucumber.groovy.steps; import com.intellij.codeInsight.CodeInsightUtilCore; import com.intellij.codeInsight.template.*; import com.intellij.codeInsight.template.impl.TemplateManagerImpl; import com.intellij.codeInsight.template.impl.TemplateState; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; 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.JavaCodeStyleManager; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ObjectUtils; import cucumber.runtime.groovy.GroovySnippet; import cucumber.runtime.snippets.SnippetGenerator; import gherkin.formatter.model.Step; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.cucumber.StepDefinitionCreator; import org.jetbrains.plugins.cucumber.groovy.GrCucumberUtil; import org.jetbrains.plugins.cucumber.psi.GherkinStep; import org.jetbrains.plugins.groovy.GroovyFileType; import org.jetbrains.plugins.groovy.actions.GroovyTemplatesFactory; import org.jetbrains.plugins.groovy.intentions.base.IntentionUtils; import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral; import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; import java.util.Collections; /** * @author Max Medvedev */ public class GrStepDefinitionCreator implements StepDefinitionCreator { public static final String GROOVY_STEP_DEFINITION_FILE_TMPL_1_0 = "GroovyStepDefinitionFile.groovy"; public static final String GROOVY_STEP_DEFINITION_FILE_TMPL_1_1 = "GroovyStepDefinitionFile1_1.groovy"; @NotNull @Override public PsiFile createStepDefinitionContainer(@NotNull PsiDirectory dir, @NotNull String name) { String fileName = name + '.' + GroovyFileType.DEFAULT_EXTENSION; if (GrCucumberUtil.isCucumber_1_1_orAbove(dir)) { return GroovyTemplatesFactory.createFromTemplate(dir, name, fileName, GROOVY_STEP_DEFINITION_FILE_TMPL_1_1, true); } else { return GroovyTemplatesFactory.createFromTemplate(dir, name, fileName, GROOVY_STEP_DEFINITION_FILE_TMPL_1_0, true); } } @Override public boolean createStepDefinition(@NotNull GherkinStep step, @NotNull final PsiFile file) { if (!(file instanceof GroovyFile)) return false; final Project project = file.getProject(); final VirtualFile vFile = ObjectUtils.assertNotNull(file.getVirtualFile()); final OpenFileDescriptor descriptor = new OpenFileDescriptor(project, vFile); FileEditorManager.getInstance(project).getAllEditors(vFile); FileEditorManager.getInstance(project).openTextEditor(descriptor, true); final Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); if (editor != null) { final TemplateManager templateManager = TemplateManager.getInstance(file.getProject()); final TemplateState templateState = TemplateManagerImpl.getTemplateState(editor); final Template template = templateManager.getActiveTemplate(editor); if (templateState != null && template != null) { templateState.gotoEnd(); } } // snippet text final GrMethodCall element = buildStepDefinitionByStep(step); GrMethodCall methodCall = (GrMethodCall)((GroovyFile)file).addStatementBefore(element, null); JavaCodeStyleManager.getInstance(project).shortenClassReferences(methodCall); methodCall = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(methodCall); PsiDocumentManager.getInstance(project).commitAllDocuments(); if (ApplicationManager.getApplication().isUnitTestMode()) return true; final TemplateBuilderImpl builder = (TemplateBuilderImpl)TemplateBuilderFactory.getInstance().createTemplateBuilder(methodCall); // regexp str GrLiteral pattern = GrCucumberUtil.getStepDefinitionPattern(methodCall); assert pattern != null; String patternText = pattern.getText(); builder.replaceElement(pattern, new TextRange(1, patternText.length() - 1), patternText.substring(1, patternText.length() - 1)); // block vars GrClosableBlock closure = methodCall.getClosureArguments()[0]; final GrParameter[] blockVars = closure.getAllParameters(); for (GrParameter var : blockVars) { PsiElement identifier = var.getNameIdentifierGroovy(); builder.replaceElement(identifier, identifier.getText()); } TemplateManager manager = TemplateManager.getInstance(project); final Editor editorToRunTemplate; if (editor == null) { editorToRunTemplate = IntentionUtils.positionCursor(project, file, methodCall); } else { editorToRunTemplate = editor; } Template template = builder.buildTemplate(); TextRange range = methodCall.getTextRange(); editorToRunTemplate.getDocument().deleteString(range.getStartOffset(), range.getEndOffset()); editorToRunTemplate.getCaretModel().moveToOffset(range.getStartOffset()); manager.startTemplate(editorToRunTemplate, template, new TemplateEditingAdapter() { @Override public void templateFinished(Template template, boolean brokenOff) { if (brokenOff) return; ApplicationManager.getApplication().runWriteAction(() -> { PsiDocumentManager.getInstance(project).commitDocument(editorToRunTemplate.getDocument()); final int offset = editorToRunTemplate.getCaretModel().getOffset(); GrMethodCall methodCall1 = PsiTreeUtil.findElementOfClassAtOffset(file, offset - 1, GrMethodCall.class, false); if (methodCall1 != null) { GrClosableBlock[] closures = methodCall1.getClosureArguments(); if (closures.length == 1) { GrClosableBlock closure1 = closures[0]; selectBody(closure1, editor); } } }); } }); return true; } private static void selectBody(GrClosableBlock closure, Editor editor) { PsiElement arrow = closure.getArrow(); PsiElement leftBound = PsiUtil.skipWhitespaces((arrow != null ? arrow : closure.getParameterList()).getNextSibling(), true); PsiElement rbrace = closure.getRBrace(); PsiElement rightBound = rbrace != null ? PsiUtil.skipWhitespaces(rbrace.getPrevSibling(), false) : null; if (leftBound != null && rightBound != null) { editor.getSelectionModel().setSelection(leftBound.getTextRange().getStartOffset(), rightBound.getTextRange().getEndOffset()); editor.getCaretModel().moveToOffset(leftBound.getTextRange().getStartOffset()); } } private static GrMethodCall buildStepDefinitionByStep(@NotNull final GherkinStep step) { final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(step.getProject()); final Step cucumberStep = new Step(Collections.emptyList(), step.getKeyword().getText(), step.getStepName(), 0, null, null); SnippetGenerator generator = new SnippetGenerator(new GroovySnippet()); final String fqnPendingException; if (GrCucumberUtil.isCucumber_1_1_orAbove(step)) { fqnPendingException = "cucumber.api.PendingException"; } else { fqnPendingException = "cucumber.runtime.PendingException"; } String snippet = generator.getSnippet(cucumberStep, null).replace("PendingException", fqnPendingException); return (GrMethodCall)factory.createStatementFromText(snippet, step); } @Override public boolean validateNewStepDefinitionFileName(@NotNull final Project project, @NotNull final String fileName) { return true; } @NotNull @Override public PsiDirectory getDefaultStepDefinitionFolder(@NotNull GherkinStep step) { final PsiFile featureFile = step.getContainingFile(); return ObjectUtils.assertNotNull(featureFile.getParent()); } @NotNull @Override public String getStepDefinitionFilePath(@NotNull PsiFile file) { final VirtualFile vFile = file.getVirtualFile(); if (file instanceof GroovyFile && vFile != null) { String packageName = ((GroovyFile)file).getPackageName(); if (StringUtil.isEmptyOrSpaces(packageName)) { return vFile.getNameWithoutExtension(); } else { return packageName + "." + vFile.getNameWithoutExtension(); } } return file.getName(); } @NotNull @Override public String getDefaultStepFileName(@NotNull final GherkinStep step) { return "StepDef"; } }