From 904842a66668b6ea881a6d43a7c468f76b707cb9 Mon Sep 17 00:00:00 2001 From: tangcent Date: Sun, 29 Dec 2024 19:33:33 +0800 Subject: [PATCH] feat: implement IdeaConsoleLogger for enhanced logging in IntelliJ plugin --- .../intellij/context/BasicPluginModule.kt | 4 +- .../intellij/logger/ConsoleRunnerLogger.kt | 113 ------------------ .../intellij/logger/IdeaConsoleLogger.kt | 90 ++++++++++++++ .../intellij/logger/LogConsoleRunner.kt | 78 ------------ .../com/itangcent/intellij/logger/Logger.kt | 2 +- 5 files changed, 93 insertions(+), 194 deletions(-) delete mode 100644 guice-action/src/main/kotlin/com/itangcent/intellij/logger/ConsoleRunnerLogger.kt create mode 100644 guice-action/src/main/kotlin/com/itangcent/intellij/logger/IdeaConsoleLogger.kt delete mode 100644 guice-action/src/main/kotlin/com/itangcent/intellij/logger/LogConsoleRunner.kt diff --git a/guice-action/src/main/kotlin/com/itangcent/intellij/context/BasicPluginModule.kt b/guice-action/src/main/kotlin/com/itangcent/intellij/context/BasicPluginModule.kt index 9178ad92..2265b7c5 100644 --- a/guice-action/src/main/kotlin/com/itangcent/intellij/context/BasicPluginModule.kt +++ b/guice-action/src/main/kotlin/com/itangcent/intellij/context/BasicPluginModule.kt @@ -3,13 +3,13 @@ package com.itangcent.intellij.context import com.itangcent.intellij.extend.guice.KotlinModule import com.itangcent.intellij.extend.guice.singleton import com.itangcent.intellij.extend.guice.with -import com.itangcent.intellij.logger.ConsoleRunnerLogger +import com.itangcent.intellij.logger.IdeaConsoleLogger import com.itangcent.intellij.logger.Logger open class BasicPluginModule : KotlinModule() { override fun configure() { super.configure() - bind(Logger::class).with(ConsoleRunnerLogger::class).singleton() + bind(Logger::class).with(IdeaConsoleLogger::class).singleton() } } diff --git a/guice-action/src/main/kotlin/com/itangcent/intellij/logger/ConsoleRunnerLogger.kt b/guice-action/src/main/kotlin/com/itangcent/intellij/logger/ConsoleRunnerLogger.kt deleted file mode 100644 index d624c880..00000000 --- a/guice-action/src/main/kotlin/com/itangcent/intellij/logger/ConsoleRunnerLogger.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.itangcent.intellij.logger - -import com.google.inject.Inject -import com.google.inject.Singleton -import com.google.inject.name.Named -import com.intellij.execution.ExecutionException -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.Messages -import com.itangcent.common.exception.ProcessCanceledException -import com.itangcent.intellij.constant.CacheKey -import com.itangcent.intellij.constant.EventKey -import com.itangcent.intellij.context.ActionContext -import com.itangcent.intellij.io.PipedProcess -import org.apache.commons.lang3.exception.ExceptionUtils -import java.io.IOException -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock - -@Singleton -class ConsoleRunnerLogger : AbstractLogger() { - - private var pipedProcess: PipedProcess? = null - - private var logConsoleRunner: LogConsoleRunner? = null - - @Volatile - private var close = false - - @Inject - private val actionContext: ActionContext? = null - - @Inject - private lateinit var logConfig: LogConfig - - @Inject(optional = true) - @Named("plugin.name") - private val pluginName: String = "run" - - private val lock = ReentrantLock() - - private fun checkProcess(): PipedProcess { - if (pipedProcess == null || logConsoleRunner == null) { - lock.withLock { - if (close) { - throw ProcessCanceledException("logger closed") - } - if (pipedProcess == null) { - pipedProcess = PipedProcess() - actionContext!!.cache(CacheKey.LOGPROCESS, pipedProcess!!) - actionContext.on(EventKey.ON_COMPLETED) { - close = true - try { - Thread.sleep(100) - } catch (_: Exception) { - } - pipedProcess?.setExitValue(0) -// logConsoleRunner?.close() - clear() - } - } - - if (logConsoleRunner == null) { - val project = actionContext!!.instance(Project::class) - - try { - logConsoleRunner = LogConsoleRunner(project, pluginName, project.basePath!!, pipedProcess!!) - logConsoleRunner!!.initAndRun() - } catch (ex: ExecutionException) { - actionContext.runInWriteUI { - Messages.showMessageDialog( - project, "Error at:" + ex.message - + "trace:" + ExceptionUtils.getStackTrace(ex), - "Error", Messages.getInformationIcon() - ) - } - } - } - - } - } - - return pipedProcess!! - } - - private fun clear() { - lock.withLock { - this.pipedProcess = null - this.logConsoleRunner = null - } - } - - override fun processLog(logData: String?) { - if (logData == null || close) return - try { - val pipedProcess = checkProcess() - val bytes = logData.toByteArray(logConfig.charset()) - if (bytes.size > 1024) { - //split? - pipedProcess.getOutForInputStream()!!.write(bytes) - } else { - LOG.info(logData) - pipedProcess.getOutForInputStream()!!.write(bytes) - } - } catch (ex: IOException) { - LOG.warn("Error processLog:", ex) - } - } - - companion object { - //background idea log - private val LOG = com.intellij.openapi.diagnostic.Logger.getInstance(ConsoleRunnerLogger::class.java) - } -} diff --git a/guice-action/src/main/kotlin/com/itangcent/intellij/logger/IdeaConsoleLogger.kt b/guice-action/src/main/kotlin/com/itangcent/intellij/logger/IdeaConsoleLogger.kt new file mode 100644 index 00000000..dec7fd85 --- /dev/null +++ b/guice-action/src/main/kotlin/com/itangcent/intellij/logger/IdeaConsoleLogger.kt @@ -0,0 +1,90 @@ +package com.itangcent.intellij.logger + +import com.google.inject.Inject +import com.google.inject.Singleton +import com.intellij.execution.filters.TextConsoleBuilderFactory +import com.intellij.execution.ui.ConsoleView +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.openapi.project.Project +import com.intellij.openapi.wm.RegisterToolWindowTask +import com.intellij.openapi.wm.ToolWindowAnchor +import com.intellij.openapi.wm.ToolWindowManager +import com.itangcent.common.spi.SpiUtils +import com.itangcent.intellij.CustomInfo +import com.itangcent.intellij.context.ActionContext + +/** + * A logger implementation that outputs messages to the IntelliJ IDEA console window. + * This class creates and manages a tool window in the IDE's bottom panel for displaying log messages. + * + * @author tangcent + */ +@Singleton +class IdeaConsoleLogger : AbstractLogger() { + + @Inject + private lateinit var project: Project + + @Inject + private lateinit var actionContext: ActionContext + + /** + * The ID for the tool window. + */ + private val toolWindowId: String by lazy { + SpiUtils.loadService(CustomInfo::class)?.pluginName() ?: "Plugin Log" + } + + /** + * Lazily initialized console view that handles the actual display of log messages. + * Creates a tool window in the IDE's bottom panel if it doesn't exist, + * or updates the existing one if it does. + */ + private val consoleView: ConsoleView by lazy { + // Create a new console builder for the current project + val console = TextConsoleBuilderFactory.getInstance() + .createBuilder(project) + .console + + // Run UI operations in the Swing EDT thread + actionContext.runInSwingUI { + val toolWindowManager = ToolWindowManager.getInstance(project) + val toolWindow = toolWindowManager.getToolWindow(toolWindowId) + if (toolWindow == null) { + // Register a new tool window if it doesn't exist + toolWindowManager.registerToolWindow( + RegisterToolWindowTask( + id = toolWindowId, + anchor = ToolWindowAnchor.BOTTOM, + canCloseContent = false, + component = console.component + ) + ) + } else { + // Update existing tool window with the new console component + toolWindow.contentManager.getContent(0)?.component = console.component + } + } + + console + } + + /** + * Processes and displays a log message in the console. + * + * @param logData The message to be logged. If null, the method returns without action. + */ + override fun processLog(logData: String?) { + if (logData == null) return + + actionContext.runInSwingUI { + consoleView.print("$logData\n", ConsoleViewContentType.NORMAL_OUTPUT) + } + } +} + +/** + * @deprecated Use IdeaConsoleLogger instead + */ +@Deprecated("Use IdeaConsoleLogger instead", ReplaceWith("IdeaConsoleLogger")) +typealias ConsoleRunnerLogger = IdeaConsoleLogger \ No newline at end of file diff --git a/guice-action/src/main/kotlin/com/itangcent/intellij/logger/LogConsoleRunner.kt b/guice-action/src/main/kotlin/com/itangcent/intellij/logger/LogConsoleRunner.kt deleted file mode 100644 index 2334fb11..00000000 --- a/guice-action/src/main/kotlin/com/itangcent/intellij/logger/LogConsoleRunner.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.itangcent.intellij.logger - -import com.intellij.execution.ExecutionException -import com.intellij.execution.console.LanguageConsoleImpl -import com.intellij.execution.console.LanguageConsoleView -import com.intellij.execution.console.ProcessBackedConsoleExecuteActionHandler -import com.intellij.execution.process.ColoredProcessHandler -import com.intellij.execution.process.KillableProcessHandler -import com.intellij.execution.process.OSProcessHandler -import com.intellij.execution.runners.AbstractConsoleRunnerWithHistory -import com.intellij.openapi.fileTypes.PlainTextLanguage -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Key -import com.intellij.util.io.BaseOutputReader -import com.itangcent.common.spi.SpiUtils -import com.itangcent.intellij.CustomInfo - -class LogConsoleRunner : AbstractConsoleRunnerWithHistory { - - private var logProcess: Process? = null - - constructor(myProject: Project, consoleTitle: String, logProcess: Process) : super( - myProject, - consoleTitle, - myProject.basePath - ) { - this.logProcess = logProcess - } - - constructor(myProject: Project, consoleTitle: String, workingDir: String, logProcess: Process) : super( - myProject, - consoleTitle, - workingDir - ) { - this.logProcess = logProcess - } - - override fun createConsoleView(): LanguageConsoleView { - val consoleView = LanguageConsoleImpl(project, "Bash", PlainTextLanguage.INSTANCE) - consoleView.file.putUserData(LANGUAGE_CONSOLE_MARKER, true) - - consoleView.isEditable = false - return consoleView - } - - @Throws(ExecutionException::class) - override fun createProcess(): Process? { - return logProcess - } - - override fun createProcessHandler(process: Process): OSProcessHandler { - return MyColoredProcessHandler( - process, - "run " + (SpiUtils.loadService(CustomInfo::class)?.pluginName() ?: "intellij-plugin") - ) - } - - override fun createExecuteActionHandler(): ProcessBackedConsoleExecuteActionHandler { - return ProcessBackedConsoleExecuteActionHandler(processHandler, true) - } - - companion object { - - private val LANGUAGE_CONSOLE_MARKER = Key("Language console marker") - } - - fun close() { - (this.processHandler as? KillableProcessHandler)?.killProcess() - (this.processHandler)?.destroyProcess() - } - - private class MyColoredProcessHandler(process: Process, commandLine: String?) : - ColoredProcessHandler(process, commandLine) { - override fun readerOptions(): BaseOutputReader.Options { - return BaseOutputReader.Options.forMostlySilentProcess() - } - } -} diff --git a/guice-action/src/main/kotlin/com/itangcent/intellij/logger/Logger.kt b/guice-action/src/main/kotlin/com/itangcent/intellij/logger/Logger.kt index 086563ab..a31cb3e6 100644 --- a/guice-action/src/main/kotlin/com/itangcent/intellij/logger/Logger.kt +++ b/guice-action/src/main/kotlin/com/itangcent/intellij/logger/Logger.kt @@ -3,7 +3,7 @@ package com.itangcent.intellij.logger import com.google.inject.ImplementedBy -@ImplementedBy(ConsoleRunnerLogger::class) +@ImplementedBy(IdeaConsoleLogger::class) interface Logger : com.itangcent.common.logger.ILogger { override fun log(msg: String) { log(BasicLevel.ALL, msg)