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
@@ -1,8 +1,15 @@
package fr.adrienbrault.idea.symfony2plugin.integrations.terminal

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.terminal.completion.spec.ShellRuntimeContext
import com.jetbrains.php.lang.PhpLanguage
import fr.adrienbrault.idea.symfony2plugin.Settings
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil
import fr.adrienbrault.idea.symfony2plugin.util.SymfonyCommandUtil
Expand All @@ -16,6 +23,8 @@ import org.jetbrains.plugins.terminal.block.completion.spec.ShellCommandSpecsPro
import org.jetbrains.plugins.terminal.block.completion.spec.dsl.ShellChildCommandsContext
import org.jetbrains.plugins.terminal.block.completion.spec.project

private val COMMAND_DATA_CACHE = Key.create<CachedValue<List<CommandData>>>("SYMFONY_TERMINAL_COMMAND_DATA")

/**
* Provides terminal completion for Symfony console commands.
*
Expand Down Expand Up @@ -90,16 +99,32 @@ private suspend fun ShellChildCommandsContext.addSymfonyCommands(runtimeCtx: She
}
}

internal fun collectCommandData(project: Project): List<CommandData> =
ApplicationManager.getApplication().runReadAction<List<CommandData>> {
SymfonyCommandUtil.getCommands(project).map { command ->
val phpClass = PhpElementsUtil.getClassInterface(project, command.fqn)
CommandData(
name = command.name,
options = if (phpClass != null) SymfonyCommandUtil.getCommandOptions(phpClass) else emptyMap(),
arguments = if (phpClass != null) SymfonyCommandUtil.getCommandArguments(phpClass) else emptyMap(),
)
}
internal fun collectCommandData(project: Project): List<CommandData> {
if (DumbService.isDumb(project)) return emptyList()

return CachedValuesManager.getManager(project).getCachedValue(
project,
COMMAND_DATA_CACHE,
{
ReadAction.nonBlocking<CachedValueProvider.Result<List<CommandData>>> {
CachedValueProvider.Result.create(
collectCommandDataInner(project),
PsiModificationTracker.getInstance(project).forLanguage(PhpLanguage.INSTANCE)
)
}.expireWhen { project.isDisposed }.executeSynchronously()
},
false
)
}

private fun collectCommandDataInner(project: Project): List<CommandData> =
SymfonyCommandUtil.getCommands(project).map { command ->
val phpClass = PhpElementsUtil.getClassInterface(project, command.fqn)
CommandData(
name = command.name,
options = if (phpClass != null) SymfonyCommandUtil.getCommandOptions(phpClass) else emptyMap(),
arguments = if (phpClass != null) SymfonyCommandUtil.getCommandArguments(phpClass) else emptyMap(),
)
}

internal data class CommandData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ import com.intellij.ide.actions.runAnything.items.RunAnythingItem
import com.intellij.ide.actions.runAnything.items.RunAnythingItemBase
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import com.jetbrains.php.lang.PhpLanguage
import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent
import fr.adrienbrault.idea.symfony2plugin.dic.command.SymfonyCommandRunConfiguration
Expand All @@ -19,6 +27,8 @@ import fr.adrienbrault.idea.symfony2plugin.util.SymfonyCommandUtil
import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyCommand
import javax.swing.Icon

private val COMMAND_CACHE = Key.create<CachedValue<List<SymfonyCommand>>>("SYMFONY_RUN_ANYTHING_COMMANDS")

/**
* Run Anything provider for Symfony console commands.
*
Expand All @@ -31,17 +41,29 @@ import javax.swing.Icon
class SymfonyConsoleRunAnythingProvider : RunAnythingProviderBase<SymfonyCommand>() {

override fun getValues(dataContext: DataContext, pattern: String): Collection<SymfonyCommand> {
val project = CommonDataKeys.PROJECT.getData(dataContext)
if (!Symfony2ProjectComponent.isEnabled(project)) return emptyList()
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return emptyList()
if (!Symfony2ProjectComponent.isEnabled(project) || DumbService.isDumb(project)) return emptyList()

val lowerPattern = pattern.lowercase().trim()

return ApplicationManager.getApplication().runReadAction<Collection<SymfonyCommand>> {
SymfonyCommandUtil.getCommands(project!!)
.filter { lowerPattern in it.name.lowercase() }
}
return getCommands(project).filter { lowerPattern in it.name.lowercase() }
}

private fun getCommands(project: Project): List<SymfonyCommand> =
CachedValuesManager.getManager(project).getCachedValue(
project,
COMMAND_CACHE,
{
ReadAction.nonBlocking<CachedValueProvider.Result<List<SymfonyCommand>>> {
CachedValueProvider.Result.create(
SymfonyCommandUtil.getCommands(project).toList(),
PsiModificationTracker.getInstance(project).forLanguage(PhpLanguage.INSTANCE)
)
}.expireWhen { project.isDisposed }.executeSynchronously()
},
false
)

override fun execute(dataContext: DataContext, value: SymfonyCommand) {
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package fr.adrienbrault.idea.symfony2plugin.tests.integrations.terminal

import com.intellij.testFramework.DumbModeTestUtils
import fr.adrienbrault.idea.symfony2plugin.integrations.terminal.SymfonyShellCommandSpecsProvider
import fr.adrienbrault.idea.symfony2plugin.integrations.terminal.collectCommandData
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase
Expand Down Expand Up @@ -85,6 +86,37 @@ class SymfonyShellCommandSpecsProviderTest : SymfonyLightCodeInsightFixtureTestC
assertTrue("terminal:modern-options should be collected", "terminal:modern-options" in names)
}

fun testCollectCommandDataInvalidatesOnPhpModification() {
val before = collectCommandData(project).map { it.name }
assertFalse("terminal:after-cache should not exist before adding the PHP file", "terminal:after-cache" in before)

myFixture.addFileToProject(
"src/Command/AfterCacheCommand.php",
"""
<?php

namespace TerminalFixtures;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;

#[AsCommand(name: 'terminal:after-cache')]
class AfterCacheCommand extends Command {}
""".trimIndent()
)

val after = collectCommandData(project).map { it.name }
assertTrue("terminal:after-cache should be collected after the PHP modification", "terminal:after-cache" in after)
}

fun testCollectCommandDataReturnsEmptyInDumbMode() {
DumbModeTestUtils.runInDumbModeSynchronously(project) {
val data = collectCommandData(project)

assertTrue(data.isEmpty())
}
}

/**
* Options from `addOption()` calls are reflected with name and shortcut.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fr.adrienbrault.idea.symfony2plugin.tests.runAnything
import com.intellij.ide.actions.runAnything.items.RunAnythingItemBase
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.testFramework.DumbModeTestUtils
import fr.adrienbrault.idea.symfony2plugin.runAnything.SymfonyConsoleRunAnythingProvider
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase
import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyCommand
Expand Down Expand Up @@ -71,6 +72,34 @@ class SymfonyConsoleRunAnythingProviderTest : SymfonyLightCodeInsightFixtureTest
assertTrue(values.any { it.name == "cache:clear" })
}

fun testGetValuesInvalidatesOnPhpModification() {
val before = provider.getValues(createDataContext(), "app:after-cache").map { it.name }
assertFalse("app:after-cache should not exist before adding the PHP file", "app:after-cache" in before)

myFixture.addFileToProject(
"src/Command/AfterCacheCommand.php",
"""
<?php
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;

#[AsCommand(name: 'app:after-cache')]
class AfterCacheCommand extends Command {}
""".trimIndent()
)

val after = provider.getValues(createDataContext(), "app:after-cache").map { it.name }
assertTrue("app:after-cache should be collected after the PHP modification", "app:after-cache" in after)
}

fun testGetValuesReturnsEmptyInDumbMode() {
DumbModeTestUtils.runInDumbModeSynchronously(project) {
val values = provider.getValues(createDataContext(), "cache")

assertTrue(values.isEmpty())
}
}

// --- getMainListItem ---

fun testGetMainListItemDescriptionIsShortClassName() {
Expand Down
Loading