Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 <daniel@espendiller.net>
*/
public class SymfonyCommandRunConfiguration extends LocatableConfigurationBase<SymfonyCommandRunConfiguration.Config> {
// @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<? extends RunConfiguration> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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 <daniel@espendiller.net>
*/
public class SymfonyCommandRunConfigurationProducer extends LazyRunConfigurationProducer<SymfonyCommandRunConfiguration> {

@NotNull
@Override
public ConfigurationFactory getConfigurationFactory() {
return new PhpScrip().getConfigurationFactories()[0];
}

@Override
protected boolean setupConfigurationFromContext(@NotNull SymfonyCommandRunConfiguration configuration, @NotNull ConfigurationContext context, @NotNull Ref<PsiElement> 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";
}
}
}
Original file line number Diff line number Diff line change
@@ -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 <daniel@espendiller.net>
*/
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;
}
}
3 changes: 3 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@
<notificationGroup id="Symfony Notifications" displayType="STICKY_BALLOON"/>
<errorHandler implementation="fr.adrienbrault.idea.symfony2plugin.util.ide.SymfonyPluginErrorReporterSubmitter"/>

<runLineMarkerContributor language="PHP" implementationClass="fr.adrienbrault.idea.symfony2plugin.dic.command.SymfonyCommandTestRunLineMarkerProvider"/>
<runConfigurationProducer implementation="fr.adrienbrault.idea.symfony2plugin.dic.command.SymfonyCommandRunConfigurationProducer"/>

<localInspection groupPath="Symfony" shortName="PhpRouteMissingInspection" displayName="Route Missing"
groupName="Route"
enabledByDefault="true" level="WARNING"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package fr.adrienbrault.idea.symfony2plugin.tests.dic.command;

import com.jetbrains.php.lang.psi.PhpPsiElementFactory;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import fr.adrienbrault.idea.symfony2plugin.dic.command.SymfonyCommandTestRunLineMarkerProvider;
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;

/**
* @author Daniel Espendiller <daniel@espendiller.net>
* @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, "<?php\n" +
"class FoobarCommand extends \\Symfony\\Component\\Console\\Command\\Command {\n" +
" protected static $defaultName = 'app:create-user';\n" +
"}"
);

assertEquals("app:create-user", SymfonyCommandTestRunLineMarkerProvider.getCommandNameFromClass(phpClass));
}

public void testCommandNameFromDefaultPhpProperty() {
PhpClass phpClass = PhpPsiElementFactory.createFromText(getProject(), PhpClass.class, "<?php\n" +
"use Symfony\\Component\\Console\\Attribute\\AsCommand;\n" +
"\n" +
"#[AsCommand(\n" +
" name: 'app:create-user',\n" +
" description: 'Creates a new user.',\n" +
" hidden: false,\n" +
" aliases: ['app:add-user']\n" +
")]\n" +
"class FoobarCommand extends \\Symfony\\Component\\Console\\Command\\Command {\n" +
"}"
);

assertEquals("app:create-user", SymfonyCommandTestRunLineMarkerProvider.getCommandNameFromClass(phpClass));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Symfony\Component\Console\Command
{
class Command
{
}
}

namespace Symfony\Component\Console\Attribute
{

/**
* Service tag to autoconfigure commands.
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class AsCommand
{
public function __construct(
public string $name,
public ?string $description = null,
array $aliases = [],
bool $hidden = false,
) {}
}
}