From 78b0fe1963ebea4b750b59700dfef794ebe0834a Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Tue, 12 Nov 2024 22:45:27 +0100 Subject: [PATCH] filesystem refresh after dependencies fetch should happen outside the read action --- .../org/move/cli/MoveProjectsSyncTask.kt | 98 ++++++++++++------- .../org/move/cli/manifest/TomlDependency.kt | 9 +- .../move/cli/runConfigurations/aptos/Aptos.kt | 4 +- .../org/move/openapiext/ProcessOutput.kt | 31 ++++++ 4 files changed, 99 insertions(+), 43 deletions(-) diff --git a/src/main/kotlin/org/move/cli/MoveProjectsSyncTask.kt b/src/main/kotlin/org/move/cli/MoveProjectsSyncTask.kt index 9594d429..8528cbe5 100644 --- a/src/main/kotlin/org/move/cli/MoveProjectsSyncTask.kt +++ b/src/main/kotlin/org/move/cli/MoveProjectsSyncTask.kt @@ -11,6 +11,8 @@ import com.intellij.build.events.MessageEvent import com.intellij.build.progress.BuildProgress import com.intellij.build.progress.BuildProgressDescriptor import com.intellij.execution.process.ProcessEvent +import com.intellij.execution.process.ProcessListener +import com.intellij.execution.process.ProcessOutput import com.intellij.icons.AllIcons import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.ActionManager @@ -26,17 +28,21 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Key import com.intellij.openapi.util.NlsContexts +import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.concurrency.annotations.RequiresReadLock import org.move.cli.MoveProject.UpdateStatus import org.move.cli.manifest.MoveToml +import org.move.cli.manifest.TomlDependency.Git.Companion.moveHome import org.move.cli.runConfigurations.aptos.AptosExitStatus import org.move.cli.settings.getAptosCli import org.move.cli.settings.moveSettings import org.move.lang.toNioPathOrNull import org.move.lang.toTomlFile +import org.move.openapiext.AnsiEscapedProcessAdapter import org.move.openapiext.TaskResult import org.move.openapiext.contentRoots +import org.move.openapiext.runProcessWithGlobalProgress import org.move.stdext.iterateFiles import org.move.stdext.unwrapOrElse import org.move.stdext.withExtended @@ -149,19 +155,34 @@ class MoveProjectsSyncTask( "Fetching dependencies disabled in the plugin settings", ) } else { - context.runWithChildProgress("Fetching dependencies") { childContext -> - val result = fetchDependencyPackages(childContext, projectRoot) - if (result is TaskResult.Err) { - moveProject = - moveProject.copy(fetchDepsStatus = UpdateStatus.UpdateFailed("Failed to fetch dependency packages")) + context.runWithChildProgress("Fetch dependencies") { childContext -> + val taskResult = fetchDependencyPackages(childContext, projectRoot) + if (taskResult is TaskResult.Err) { + moveProject = moveProject.copy( + fetchDepsStatus = UpdateStatus.UpdateFailed("Failed to fetch dependency packages") + ) + } + childContext.checkCanceled() + // fetched any dependencies + if (taskResult is TaskResult.Ok) { + val output = taskResult.value + if (output.stdout.contains("FETCHING GIT DEPENDENCY")) { + childContext.withProgressText("Preparing filesystem") + VfsUtil.markDirtyAndRefresh( + false, + true, + false, + moveHome().toFile() + ) + } } - result + taskResult } } context.checkCanceled() val deps = - (context.runWithChildProgress("Loading dependencies") { childContext -> + (context.runWithChildProgress("Load modules") { childContext -> // Blocks till completed or cancelled by the toml / file change runReadAction { val rootPackage = moveProject.currentPackage @@ -182,11 +203,9 @@ class MoveProjectsSyncTask( projects.add(moveProject.copy(dependencies = deps)) } - private fun fetchDependencyPackages(childContext: SyncContext, projectRoot: Path): TaskResult { - val syncListener = - SyncProcessListener(childContext) { event -> - childContext.syncProgress.output(event.text, true) - } + private fun fetchDependencyPackages( + childContext: SyncContext, projectRoot: Path + ): TaskResult { val skipLatest = project.moveSettings.skipFetchLatestGitDeps val aptos = project.getAptosCli(parentDisposable = this) return when { @@ -196,15 +215,40 @@ class MoveProjectsSyncTask( aptos.fetchPackageDependencies( projectRoot, skipLatest, - processListener = syncListener + runner = { + // populate progress bar + addProcessListener(object: AnsiEscapedProcessAdapter() { + override fun onColoredTextAvailable(text: String) { + // show progress bars for the long operations + val line = text.trim() + if (line.startsWith("FETCHING GIT DEPENDENCY")) { + val gitRepo = line.substring("FETCHING GIT DEPENDENCY".length) + childContext.withProgressText("Fetching $gitRepo") + } + if (line.startsWith("UPDATING GIT DEPENDENCY")) { + val gitRepo = line.substring("UPDATING GIT DEPENDENCY".length) + childContext.withProgressText("Updating $gitRepo") + } + } + }) + addProcessListener(object: ProcessListener { + override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { + childContext.syncProgress.output(event.text, true) + } + }) + runProcessWithGlobalProgress() + } ).unwrapOrElse { return TaskResult.Err( "Failed to fetch / update dependencies", it.message ) } + val output = aptosProcessOutput.output when (val exitStatus = aptosProcessOutput.exitStatus) { - is AptosExitStatus.Result -> TaskResult.Ok(Unit) + is AptosExitStatus.Result -> { + TaskResult.Ok(output) + } is AptosExitStatus.Error -> { if (exitStatus.message.contains("Unable to resolve packages for package")) { return TaskResult.Err( @@ -218,9 +262,9 @@ class MoveProjectsSyncTask( exitStatus.message.split(": ").joinToString(": \n") ) } - TaskResult.Ok(Unit) + TaskResult.Ok(output) } - is AptosExitStatus.Malformed -> TaskResult.Ok(Unit) + is AptosExitStatus.Malformed -> TaskResult.Ok(output) } } } @@ -364,28 +408,6 @@ class MoveProjectsSyncTask( } } -private class SyncProcessListener( - private val context: MoveProjectsSyncTask.SyncContext, - private val onTextAvailable: (ProcessEvent) -> Unit, -): ProcessProgressListener { - override fun onTextAvailable(event: ProcessEvent, outputType: Key) { - onTextAvailable(event) - // show progress bars for the long operations - val text = event.text.trim { it <= ' ' } - if (text.startsWith("FETCHING GIT DEPENDENCY")) { - val gitRepo = text.substring("FETCHING GIT DEPENDENCY".length) - context.withProgressText("Fetching $gitRepo") - } - if (text.startsWith("UPDATING GIT DEPENDENCY")) { - val gitRepo = text.substring("UPDATING GIT DEPENDENCY".length) - context.withProgressText("Updating $gitRepo") - } - } - - override fun error(title: String, message: String) = context.syncProgress.error(title, message) - override fun warning(title: String, message: String) = context.syncProgress.warning(title, message) -} - private fun BuildProgress.runWithChildProgress( @BuildEventsNls.Title title: String, createContext: (BuildProgress) -> T, diff --git a/src/main/kotlin/org/move/cli/manifest/TomlDependency.kt b/src/main/kotlin/org/move/cli/manifest/TomlDependency.kt index 00809f47..ffa28cfb 100644 --- a/src/main/kotlin/org/move/cli/manifest/TomlDependency.kt +++ b/src/main/kotlin/org/move/cli/manifest/TomlDependency.kt @@ -25,7 +25,7 @@ sealed class TomlDependency { @RequiresReadLock override fun rootDirectory(): RsResult { - val vFile = VfsUtil.findFile(localPath, true) + val vFile = VfsUtil.findFile(localPath, false) if (vFile == null) { return Err(DependencyError("Cannot find dependency folder: $localPath")) } @@ -42,13 +42,14 @@ sealed class TomlDependency { @RequiresReadLock override fun rootDirectory(): RsResult { - val moveHomePath = SystemProperties.getUserHome().toPath().resolve(".move") + val moveHomePath = moveHome() if (!moveHomePath.exists()) { return Err(DependencyError("$moveHomePath directory does not exist")) } val depDirName = dependencyDirName(repo, rev) val depRoot = moveHomePath.resolve(depDirName).resolve(subdir) - val depRootFile = VfsUtil.findFile(depRoot, true) + // NOTE: VFS cannot be refreshed from the read action + val depRootFile = VfsUtil.findFile(depRoot, false) if (depRootFile == null) { return Err(DependencyError("cannot find folder: $depRoot")) } @@ -56,6 +57,8 @@ sealed class TomlDependency { } companion object { + fun moveHome(): Path = SystemProperties.getUserHome().toPath().resolve(".move") + fun dependencyDirName(repo: String, rev: String): String { val sanitizedRepoName = repo.replace(Regex("[/:.@]"), "_") val aptosRevName = rev.replace("/", "_") diff --git a/src/main/kotlin/org/move/cli/runConfigurations/aptos/Aptos.kt b/src/main/kotlin/org/move/cli/runConfigurations/aptos/Aptos.kt index 97ecebaf..8ceed866 100644 --- a/src/main/kotlin/org/move/cli/runConfigurations/aptos/Aptos.kt +++ b/src/main/kotlin/org/move/cli/runConfigurations/aptos/Aptos.kt @@ -63,7 +63,7 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp fun fetchPackageDependencies( projectDir: Path, skipLatest: Boolean, - processListener: ProcessListener + runner: CapturingProcessHandler.() -> ProcessOutput = { runProcessWithGlobalProgress() } ): AptosProcessResult { val commandLine = AptosCommandLine( @@ -73,7 +73,7 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp ), workingDirectory = projectDir ) - return executeAptosCommandLine(commandLine, colored = true, listener = processListener) + return executeAptosCommandLine(commandLine, colored = true, runner = runner) } fun checkProject(args: AptosCompileArgs): RsResult { diff --git a/src/main/kotlin/org/move/openapiext/ProcessOutput.kt b/src/main/kotlin/org/move/openapiext/ProcessOutput.kt index cfe0c9ca..bd386e9d 100644 --- a/src/main/kotlin/org/move/openapiext/ProcessOutput.kt +++ b/src/main/kotlin/org/move/openapiext/ProcessOutput.kt @@ -1,5 +1,36 @@ package org.move.openapiext +import com.intellij.execution.process.AnsiEscapeDecoder +import com.intellij.execution.process.ProcessAdapter +import com.intellij.execution.process.ProcessEvent import com.intellij.execution.process.ProcessOutput +import com.intellij.openapi.util.Key val ProcessOutput.isSuccess: Boolean get() = !isTimeout && !isCancelled && exitCode == 0 + +/** + * Capturing adapter that removes ANSI escape codes from the output + */ +abstract class AnsiEscapedProcessAdapter: ProcessAdapter(), AnsiEscapeDecoder.ColoredTextAcceptor { + private val decoder = AnsiEscapeDecoder() + private val eolType: Key = Key.create("end of line") + + override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { + decoder.escapeText(event.text, outputType, this) + decoder.escapeText("", eolType, this) + } + + var buffer = StringBuilder() + + override fun coloredTextAvailable(text: String, attributes: Key<*>) { + if (attributes == eolType) { + onColoredTextAvailable(buffer.toString()) + buffer = StringBuilder() + } else { + buffer.append(text) + } + } + + abstract fun onColoredTextAvailable(text: String) +} +