diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/command/SymfonyCommandRunConfiguration.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/command/SymfonyCommandRunConfiguration.java new file mode 100644 index 000000000..5cbb8fce0 --- /dev/null +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/command/SymfonyCommandRunConfiguration.java @@ -0,0 +1,62 @@ +package fr.adrienbrault.idea.symfony2plugin.dic.command; + +import com.intellij.execution.DefaultExecutionResult; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.configurations.LocatableConfigurationBase; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.impl.ConsoleViewImpl; +import com.intellij.execution.process.KillableProcessHandler; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.process.ScriptRunnerUtil; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.options.SettingsEditor; +import com.intellij.openapi.options.SettingsEditorGroup; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import fr.adrienbrault.idea.symfony2plugin.util.ProjectUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Daniel Espendiller + */ +public class SymfonyCommandRunConfiguration extends LocatableConfigurationBase { + // @TODO: empty config for now: we need binary and arguments input. find suitable existing "configuration groups" for this + public static class Config {} + + @Nullable + private String commandName; + + protected SymfonyCommandRunConfiguration(Project project, ConfigurationFactory factory, String name) { + super(project, factory, name); + } + + @Override + public @NotNull SettingsEditor getConfigurationEditor() { + return new SettingsEditorGroup<>(); + } + + @Override + public @Nullable RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) throws ExecutionException { + return (executor1, runner) -> { + VirtualFile projectDir = ProjectUtil.getProjectDir(getProject()); + + ProcessHandler processHandler = ScriptRunnerUtil.execute("bin/console", projectDir.getPath(), null, new String[] {this.commandName}, null, (commandLine) -> { + KillableProcessHandler handler = new KillableProcessHandler(commandLine); + handler.setShouldKillProcessSoftly(false); + return handler; + }); + + ConsoleViewImpl console = new ConsoleViewImpl(getProject(), true); + console.attachToProcess(processHandler); + return new DefaultExecutionResult(console, processHandler); + }; + } + + public void setCommandName(@NotNull String commandName) { + this.commandName = commandName; + } +} diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/command/SymfonyCommandRunConfigurationProducer.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/command/SymfonyCommandRunConfigurationProducer.java new file mode 100644 index 000000000..5a63d671c --- /dev/null +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/command/SymfonyCommandRunConfigurationProducer.java @@ -0,0 +1,103 @@ +package fr.adrienbrault.idea.symfony2plugin.dic.command; + +import com.intellij.execution.Location; +import com.intellij.execution.PsiLocation; +import com.intellij.execution.actions.ConfigurationContext; +import com.intellij.execution.actions.LazyRunConfigurationProducer; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.configurations.ConfigurationType; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.psi.PsiElement; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.jetbrains.php.run.PhpRunConfigurationFactoryBase; +import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * @author Daniel Espendiller + */ +public class SymfonyCommandRunConfigurationProducer extends LazyRunConfigurationProducer { + + @NotNull + @Override + public ConfigurationFactory getConfigurationFactory() { + return new PhpScrip().getConfigurationFactories()[0]; + } + + @Override + protected boolean setupConfigurationFromContext(@NotNull SymfonyCommandRunConfiguration configuration, @NotNull ConfigurationContext context, @NotNull Ref sourceElement) { + Location location = context.getLocation(); + if (location instanceof PsiLocation) { + PhpClass phpClass = SymfonyCommandTestRunLineMarkerProvider.getCommandContext(location.getPsiElement()); + if (phpClass != null) { + String commandNameFromClass = SymfonyCommandTestRunLineMarkerProvider.getCommandNameFromClass(phpClass); + if (commandNameFromClass != null) { + configuration.setCommandName(commandNameFromClass); + configuration.setName(commandNameFromClass); + return true; + } + } + } + + return false; + } + + @Override + public boolean isConfigurationFromContext(@NotNull SymfonyCommandRunConfiguration configuration, @NotNull ConfigurationContext context) { + Location location = context.getLocation(); + if (location instanceof PsiLocation) { + PhpClass phpClass = SymfonyCommandTestRunLineMarkerProvider.getCommandContext(location.getPsiElement()); + if (phpClass != null) { + return SymfonyCommandTestRunLineMarkerProvider.getCommandNameFromClass(phpClass) != null; + } + } + + return false; + } + + private static final class PhpScrip implements ConfigurationType, DumbAware { + private final ConfigurationFactory myFactory = new PhpRunConfigurationFactoryBase(this, "Symfony Command") { + @NotNull + public RunConfiguration createTemplateConfiguration(@NotNull Project project) { + return new SymfonyCommandRunConfiguration(project, this, "Symfony Command"); + } + + @NotNull + public String getName() { + return "Symfony Command"; + } + }; + + public PhpScrip() { + } + + @Override + public @NotNull @Nls(capitalization = Nls.Capitalization.Title) String getDisplayName() { + return "Symfony Command"; + } + + @Override + public @Nls(capitalization = Nls.Capitalization.Sentence) String getConfigurationTypeDescription() { + return "Symfony Command"; + } + + public Icon getIcon() { + return Symfony2Icons.SYMFONY; + } + + public ConfigurationFactory[] getConfigurationFactories() { + return new ConfigurationFactory[]{this.myFactory}; + } + + @NotNull + public String getId() { + return "symfony.command"; + } + } +} diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/command/SymfonyCommandTestRunLineMarkerProvider.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/command/SymfonyCommandTestRunLineMarkerProvider.java new file mode 100644 index 000000000..151338de7 --- /dev/null +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/command/SymfonyCommandTestRunLineMarkerProvider.java @@ -0,0 +1,95 @@ +package fr.adrienbrault.idea.symfony2plugin.dic.command; + +import com.intellij.execution.actions.BaseRunConfigurationAction; +import com.intellij.execution.actions.RunContextAction; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.lineMarker.RunLineMarkerContributor; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.psi.PsiElement; +import com.intellij.util.ObjectUtils; +import com.jetbrains.php.lang.lexer.PhpTokenTypes; +import com.jetbrains.php.lang.psi.PhpPsiUtil; +import com.jetbrains.php.lang.psi.elements.Field; +import com.jetbrains.php.lang.psi.elements.PhpAttribute; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.jetbrains.php.lang.psi.elements.PhpNamedElement; +import com.jetbrains.php.lang.psi.stubs.indexes.expectedArguments.PhpExpectedFunctionArgument; +import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; +import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +/** + * @author Daniel Espendiller + */ +public class SymfonyCommandTestRunLineMarkerProvider extends RunLineMarkerContributor { + @Override + public @Nullable Info getInfo(@NotNull PsiElement leaf) { + PhpClass phpClass = getCommandContext(leaf); + if (phpClass != null) { + String commandNameFromClass = getCommandNameFromClass(phpClass); + if (commandNameFromClass != null) { + BaseRunConfigurationAction baseRunConfigurationAction = new RunContextAction(DefaultRunExecutor.getRunExecutorInstance()); + return new Info(AllIcons.RunConfigurations.TestState.Run, new AnAction[]{baseRunConfigurationAction}, (psiElement) -> "Run Command"); + } + } + + return null; + } + + @Nullable + public static PhpClass getCommandContext(@NotNull PsiElement leaf) { + if (PhpPsiUtil.isOfType(leaf, PhpTokenTypes.IDENTIFIER)) { + PhpNamedElement element = ObjectUtils.tryCast(leaf.getParent(), PhpNamedElement.class); + if (element != null && element.getNameIdentifier() == leaf) { + if (element instanceof PhpClass) { + return (PhpClass) element; + } + } + } + + return null; + } + + @Nullable + public static String getCommandNameFromClass(@NotNull PhpClass phpClass) { + if (PhpElementsUtil.isInstanceOf(phpClass, "\\Symfony\\Component\\Console\\Command\\Command")) { + // lazy naming: + // protected static $defaultName = 'app:create-user' + Field defaultName = phpClass.findFieldByName("defaultName", false); + if (defaultName != null) { + PsiElement defaultValue = defaultName.getDefaultValue(); + if (defaultValue != null) { + return PhpElementsUtil.getStringValue(defaultValue); + } + } + + // php attributes: + // #[AsCommand(name: 'app:create-user')] + for (PhpAttribute attribute : phpClass.getAttributes("\\Symfony\\Component\\Console\\Attribute\\AsCommand")) { + for (PhpAttribute.PhpAttributeArgument argument : attribute.getArguments()) { + String name = argument.getName(); + if ("name".equals(name)) { + PhpExpectedFunctionArgument argument1 = argument.getArgument(); + if (argument1 != null) { + String value1 = PsiElementUtils.trimQuote(argument1.getValue()); + if (StringUtils.isNotBlank(value1)) { + return value1; + } + } + break; + } + } + } + + // @TODO: provide tag resolving here + // - { name: 'console.command', command: 'app:sunshine' } + } + + return null; + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 65bb7ea49..3ca9e3862 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -267,6 +267,9 @@ + + + + * @see SymfonyCommandTestRunLineMarkerProvider + */ +public class SymfonyCommandTestRunLineMarkerProviderTest extends SymfonyLightCodeInsightFixtureTestCase { + public void setUp() throws Exception { + super.setUp(); + myFixture.copyFileToProject("classes.php"); + } + + public String getTestDataPath() { + return "src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/command/fixtures"; + } + + public void testCommandNameFromDefaultNameProperty() { + PhpClass phpClass = PhpPsiElementFactory.createFromText(getProject(), PhpClass.class, "