Skip to content

Commit

Permalink
feat(provider): add RunService interface and implementation
Browse files Browse the repository at this point in the history
Add RunService interface and implementation to handle running files within a project.
  • Loading branch information
phodal committed Jun 1, 2024
1 parent 22a33ee commit a509717
Show file tree
Hide file tree
Showing 13 changed files with 613 additions and 4 deletions.
18 changes: 16 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,18 @@ fun prop(name: String): String =
extra.properties[name] as? String
?: error("Property `$name` is not defined in gradle.properties")

val ideaPlatformVersion = prop("ideaVersion").toInt()
val ideaPlatformVersion = prop("ideaPlatformVersion")
val pluginProjects: List<Project> get() = rootProject.allprojects.toList()
val basePluginArchiveName = "intellij-shire"
val ideaPlugins =
listOf(
"org.jetbrains.plugins.terminal",
"com.intellij.java",
"org.jetbrains.plugins.gradle",
"org.jetbrains.kotlin",
"JavaScript"
)


// Configure project's dependencies
repositories {
Expand Down Expand Up @@ -89,6 +98,11 @@ project(":core") {
}

project(":languages:java") {
intellij {
version.set(prop("ideaVersion"))
plugins.set(ideaPlugins)
}

dependencies {
}
}
Expand Down Expand Up @@ -148,7 +162,7 @@ project(":plugin") {
plugin("org.jetbrains.changelog")
}

version = prop("pluginVersion") + "-" + prop("ideaVersion")
version = prop("pluginVersion")

intellij {
pluginName.set(basePluginArchiveName)
Expand Down
19 changes: 19 additions & 0 deletions core/src/main/kotlin/com/phodal/shirelang/ShireCoreBundle.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.phodal.shirelang

import com.intellij.DynamicBundle
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.PropertyKey

@NonNls
private const val BUNDLE = "messages.ShireCoreBundle"

object ShireCoreBundle : DynamicBundle(BUNDLE) {
@Suppress("SpreadOperator")
@JvmStatic
fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = getMessage(key, *params)

@Suppress("SpreadOperator", "unused")
@JvmStatic
fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) =
getLazyMessage(key, *params)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.phodal.shirelang

import com.intellij.notification.NotificationGroup
import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.project.Project

object ShirelangNotifications {
private fun group(): NotificationGroup? {
return NotificationGroupManager.getInstance().getNotificationGroup("Shirelang.notification.group")
}

fun notify(project: Project, msg: String) {

Check warning on line 13 in core/src/main/kotlin/com/phodal/shirelang/ShirelangNotifications.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Function "notify" is never used
group()?.createNotification(msg, NotificationType.INFORMATION)?.notify(project)
}

fun error(project: Project, msg: String) {

Check warning on line 17 in core/src/main/kotlin/com/phodal/shirelang/ShirelangNotifications.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Function "error" is never used
group()?.createNotification(msg, NotificationType.ERROR)?.notify(project)
}

fun warn(project: Project, msg: String) {
group()?.createNotification(msg, NotificationType.WARNING)?.notify(project)
}
}
140 changes: 140 additions & 0 deletions core/src/main/kotlin/com/phodal/shirelang/provider/RunService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.phodal.shirelang.provider

import com.intellij.execution.RunManager
import com.intellij.execution.RunnerAndConfigurationSettings
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.execution.configurations.RunProfile
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiFile
import com.phodal.shirelang.runner.RunServiceTask

interface RunService {
private val logger: Logger get() = logger<RunService>()

/**
* Retrieves the run configuration class for the given project.
*
* @param project The project for which to retrieve the run configuration class.
* @return The run configuration class for the project.
*/
fun runConfigurationClass(project: Project): Class<out RunProfile>?

/**
* Creates a new run configuration for the given project and virtual file.
*
* 1. Looks up the PSI file from the virtual file using `PsiManager.getInstance(project).findFile(virtualFile)`.
* 2. Creates a RunConfigurationSettings instance with the name "name" and the specified RunConfigurationType using `RunManager.getInstance(project).createConfiguration("name", RunConfigurationType)`.
*
* @param project The project for which to create the run configuration.
* @param virtualFile The virtual file to associate with the run configuration.
* @return The newly created RunConfiguration, or `null` if creation failed.
*/
fun createConfiguration(project: Project, virtualFile: VirtualFile): RunConfiguration? = null

/**
* Creates a new run configuration settings for the given project and virtual file.
*
* If a configuration with the same name already exists, it will be returned.
* Otherwise, a new configuration is created and added to the run manager.
*
* @param project The project for which the configuration should be created.
* @param virtualFile The virtual file for which the configuration should be created.
* @return The created or found run configuration settings, or `null` if no suitable configuration could be
*/
fun createRunSettings(project: Project, virtualFile: VirtualFile, testElement: PsiElement?): RunnerAndConfigurationSettings? {
if (testElement != null) {
val settings = createDefaultTestConfigurations(project, testElement)
if (settings != null) {
return settings
}
}

val runManager = RunManager.getInstance(project)
var testConfig = runManager.allConfigurationsList.firstOrNull {
val runConfigureClass = runConfigurationClass(project)
it.name == virtualFile.nameWithoutExtension && (it.javaClass == runConfigureClass)
}

var isTemporary = false

// try to create config if not founds
if (testConfig == null) {
isTemporary = true
testConfig = createConfiguration(project, virtualFile)
}

if (testConfig == null) {
logger.warn("Failed to find test configuration for: ${virtualFile.nameWithoutExtension}")
return null
}

val settings = runManager.findConfigurationByTypeAndName(testConfig.type, testConfig.name)
if (settings == null) {
logger.warn("Failed to find test configuration for: ${virtualFile.nameWithoutExtension}")
return null
}

if (isTemporary) {
settings.isTemporary = true
}

runManager.selectedConfiguration = settings

return settings
}

fun PsiFile.collectPsiError(): MutableList<String> {

Check warning on line 94 in core/src/main/kotlin/com/phodal/shirelang/provider/RunService.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Function "collectPsiError" is never used
val errors = mutableListOf<String>()
val visitor = object : PsiSyntaxCheckingVisitor() {
override fun visitElement(element: PsiElement) {
if (element is PsiErrorElement) {
errors.add("Syntax error at position ${element.textRange.startOffset}: ${element.errorDescription}")
}
super.visitElement(element)
}
}

this.accept(visitor)
return errors
}

abstract class PsiSyntaxCheckingVisitor : com.intellij.psi.PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
runReadAction {
element.children.forEach { it.accept(this) }
}
}
}

private fun createDefaultTestConfigurations(project: Project, element: PsiElement): RunnerAndConfigurationSettings? {
return ConfigurationContext(element).configurationsFromContext?.firstOrNull()?.configurationSettings
}

/**
* This function is responsible for running a file within a specified project and virtual file.
* It creates a run configuration using the provided parameters and then attempts to execute it using the `ExecutionManager`. The function returns `null` if an error occurs during the configuration creation or execution process.
*
* @param project The project within which the file is to be run.
* @param virtualFile The virtual file that represents the file to be run.
* @return The result of the run operation, or `null` if an error occurred.
*/
fun runFile(project: Project, virtualFile: VirtualFile, psiElement: PsiElement?): String? {

Check warning on line 129 in core/src/main/kotlin/com/phodal/shirelang/provider/RunService.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Function "runFile" is never used
try {
val runTask = RunServiceTask(project, virtualFile, psiElement, this)
ProgressManager.getInstance().run(runTask)
} catch (e: Exception) {
logger.error("Failed to run file: ${virtualFile.name}", e)
return e.message
}

return null
}
}
20 changes: 20 additions & 0 deletions core/src/main/kotlin/com/phodal/shirelang/runner/RunContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.phodal.shirelang.runner

import com.intellij.execution.ExecutionListener
import com.intellij.execution.Executor
import com.intellij.execution.process.ProcessListener
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.runners.ProgramRunner
import com.intellij.openapi.Disposable
import java.util.concurrent.CountDownLatch

class RunContext(
val processListener: ProcessListener?,
val executionListener: ExecutionListener?,
val latch: CountDownLatch,
val executor: Executor? = null,

Check warning on line 15 in core/src/main/kotlin/com/phodal/shirelang/runner/RunContext.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Property "executor" is never used
val runner: ProgramRunner<*>? = null,

Check warning on line 16 in core/src/main/kotlin/com/phodal/shirelang/runner/RunContext.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Property "runner" is never used
) : Disposable {
val environments: MutableList<ExecutionEnvironment> = mutableListOf()
override fun dispose() {}
}
59 changes: 59 additions & 0 deletions core/src/main/kotlin/com/phodal/shirelang/runner/RunServiceExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.phodal.shirelang.runner

import com.intellij.execution.ExecutionListener
import com.intellij.execution.process.ProcessHandler
import com.intellij.execution.runners.ExecutionEnvironment

class CheckExecutionListener(
private val executorId: String,
private val runContext: RunContext,
) : ExecutionListener {
override fun processStartScheduled(executorId: String, env: ExecutionEnvironment) {
checkAndExecute(executorId, env) {
runContext.executionListener?.processStartScheduled(executorId, env)
}
}

override fun processNotStarted(executorId: String, env: ExecutionEnvironment) {
checkAndExecute(executorId, env) {
runContext.latch.countDown()
runContext.executionListener?.processNotStarted(executorId, env)
}
}

override fun processStarting(executorId: String, env: ExecutionEnvironment) {
checkAndExecute(executorId, env) {
runContext.executionListener?.processStarting(executorId, env)
}
}

override fun processStarted(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) {
checkAndExecute(executorId, env) {
runContext.executionListener?.processStarted(executorId, env, handler)
}
}

override fun processTerminating(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) {
checkAndExecute(executorId, env) {
runContext.executionListener?.processTerminating(executorId, env, handler)
}
}

override fun processTerminated(
executorId: String,
env: ExecutionEnvironment,
handler: ProcessHandler,
exitCode: Int
) {
checkAndExecute(executorId, env) {
runContext.executionListener?.processTerminated(executorId, env, handler, exitCode)
}
}

private fun checkAndExecute(executorId: String, env: ExecutionEnvironment, action: () -> Unit) {
if (this.executorId == executorId && env in runContext.environments) {
action()
}
}
}
Loading

0 comments on commit a509717

Please sign in to comment.