diff --git a/src/main/kotlin/org/move/bytecode/AptosDecompiler.kt b/src/main/kotlin/org/move/bytecode/AptosDecompiler.kt index 7daf6bfa..ea4b7aa4 100644 --- a/src/main/kotlin/org/move/bytecode/AptosDecompiler.kt +++ b/src/main/kotlin/org/move/bytecode/AptosDecompiler.kt @@ -12,14 +12,12 @@ import com.intellij.openapi.vfs.newvfs.BulkFileListener import com.intellij.openapi.vfs.newvfs.events.VFileEvent import org.move.cli.runConfigurations.aptos.Aptos import org.move.openapiext.pathAsPath -import org.move.openapiext.rootPath import org.move.openapiext.rootPluginDisposable import org.move.stdext.RsResult import org.move.stdext.unwrapOrElse import java.io.File import java.nio.file.Path import kotlin.io.path.exists -import kotlin.io.path.relativeTo // todo: this is disabled for now, it's a process which requires ReadAction, and that's why // it needs to be run in the indexing phase @@ -33,13 +31,6 @@ class AptosBytecodeDecompiler: BinaryFileDecompiler { val bytes = file.readBytes() return LoadTextUtil.getTextByBinaryPresentation(bytes, file) } -// val project = -// ProjectLocator.getInstance().getProjectsForFile(file).firstOrNull { it?.isAptosConfigured == true } -// ?: ProjectManager.getInstance().defaultProject.takeIf { it.isAptosConfigured } -// ?: return file.readText() -// val targetFileDir = getDecompilerTargetFileDirOnTemp(project, file) ?: return file.readText() -// val targetFile = decompileFile(project, file, targetFileDir) ?: return file.readText() -// return LoadTextUtil.loadText(targetFile) } fun decompileFileToTheSameDir(aptos: Aptos, file: VirtualFile): RsResult { @@ -79,15 +70,6 @@ class AptosBytecodeDecompiler: BinaryFileDecompiler { return RsResult.Ok(decompiledFile) } - fun getDecompilerTargetFileDirOnTemp(project: Project, file: VirtualFile): Path? { - val rootDecompilerDir = decompiledArtifactsFolder() - val projectDecompilerDir = rootDecompilerDir.resolve(project.name) - val root = project.rootPath ?: return null - val relativeFilePath = file.parent.pathAsPath.relativeTo(root) - val targetFileDir = projectDecompilerDir.toPath().resolve(relativeFilePath) - return targetFileDir - } - fun sourceFileName(file: VirtualFile): String { val fileName = file.name return "$fileName.move" @@ -97,12 +79,10 @@ class AptosBytecodeDecompiler: BinaryFileDecompiler { val fileName = file.name return "$fileName#decompiled.move" } - - companion object { - fun decompiledArtifactsFolder() = File(FileUtil.getTempDirectory(), "intellij-move-decompiled-artifacts") - } } +val DECOMPILED_ARTIFACTS_FOLDER = File(FileUtil.getTempDirectory(), "intellij-move-decompiled-artifacts") + fun Project.createDisposableOnFileChange(file: VirtualFile): Disposable { val filePath = file.path val disposable = Disposer.newDisposable("Dispose on any change to the ${file.name} file") 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 e3f07dc1..c8c8cd1f 100644 --- a/src/main/kotlin/org/move/cli/runConfigurations/aptos/Aptos.kt +++ b/src/main/kotlin/org/move/cli/runConfigurations/aptos/Aptos.kt @@ -3,11 +3,13 @@ package org.move.cli.runConfigurations.aptos import com.fasterxml.jackson.core.JacksonException import com.intellij.execution.configuration.EnvironmentVariablesData import com.intellij.execution.process.CapturingProcessHandler +import com.intellij.execution.process.ProcessEvent import com.intellij.execution.process.ProcessListener import com.intellij.execution.process.ProcessOutput import com.intellij.openapi.Disposable import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.execution.ParametersListUtil import org.move.cli.MoveProject @@ -15,6 +17,9 @@ import org.move.cli.MvConstants import org.move.cli.externalLinter.ExternalLinter import org.move.cli.runConfigurations.AptosCommandLine import org.move.cli.settings.moveSettings +import org.move.cli.update.AptosTool +import org.move.cli.update.UpdateCheckResult +import org.move.cli.update.UpdateCheckResult.* import org.move.openapiext.* import org.move.openapiext.common.isUnitTestMode import org.move.stdext.RsResult @@ -79,7 +84,7 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp fun checkProject( project: Project, linterArgs: AptosExternalLinterArgs - ): RsResult { + ): RsResult { val lintCommand = linterArgs.linter.command val extraArguments = ParametersListUtil.parse(linterArgs.extraArguments) val arguments = when (linterArgs.linter) { @@ -175,6 +180,62 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp return executeCommandLine(commandLine) } + fun checkForToolUpdate(tool: AptosTool): RsResult { + val commandLine = + AptosCommandLine( + subCommand = "update", + arguments = buildList { add(tool.id); add("--check") }, + workingDirectory = null + ) + val processOutput = executeAptosCommandLine(commandLine) + .unwrapOrElse { return Err(it) } + + val checkResult = when (val exitStatus = processOutput.exitStatus) { + is AptosExitStatus.Result -> { + val message = exitStatus.message + run { + when { + message.startsWith("Update is available") -> { + val match = APTOS_VERSION_REGEX.find(message) ?: return@run MalformedResult(message) + UpdateIsAvailable(match.value) + } + message.startsWith("Already up to date") -> { + val match = APTOS_VERSION_REGEX.find(message) ?: return@run MalformedResult(message) + UpToDate(match.value) + } + else -> MalformedResult(message) + } + } + } + is AptosExitStatus.Error -> UpdateError(exitStatus.message) + is AptosExitStatus.Malformed -> MalformedResult(exitStatus.message) + } + return Ok(checkResult) + } + + fun doToolUpdate(tool: AptosTool, processListener: ProcessListener): AptosProcessResult { + val commandLine = + AptosCommandLine( + subCommand = "update", + arguments = buildList { add(tool.id) }, + workingDirectory = null + ) + val yesNoListener = object: ProcessListener { + override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { + if (event.text.contains("Do you want to continue? [Y/n]")) { + val handler = event.processHandler + handler.processInput?.use { it.write("Y".toByteArray()) } + } + } + } + val compositeListener = CompositeProcessListener(yesNoListener, processListener) + val output = executeAptosCommandLine(commandLine, listener = compositeListener) + .unwrapOrElse { + return Err(it) + } + return Ok(output) + } + private fun executeCommandLine( commandLine: AptosCommandLine, colored: Boolean = false, @@ -230,4 +291,6 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp } -val MoveProject.workingDirectory: Path get() = this.currentPackage.contentRoot.pathAsPath \ No newline at end of file +val MoveProject.workingDirectory: Path get() = this.currentPackage.contentRoot.pathAsPath + +val APTOS_VERSION_REGEX = Regex("""\d+.\d+.\d""") \ No newline at end of file diff --git a/src/main/kotlin/org/move/cli/sdks/DownloadAptosSdkDialog.kt b/src/main/kotlin/org/move/cli/sdks/DownloadAptosSdkDialog.kt index 8fb7f649..71c85096 100644 --- a/src/main/kotlin/org/move/cli/sdks/DownloadAptosSdkDialog.kt +++ b/src/main/kotlin/org/move/cli/sdks/DownloadAptosSdkDialog.kt @@ -8,6 +8,7 @@ import com.intellij.ui.components.JBTextField import com.intellij.ui.dsl.builder.AlignX import com.intellij.ui.dsl.builder.columns import com.intellij.ui.dsl.builder.panel +import org.move.cli.runConfigurations.aptos.APTOS_VERSION_REGEX import org.move.openapiext.pathField import javax.swing.JComponent @@ -36,7 +37,7 @@ class DownloadAptosSdkDialog(val project: Project?): DialogWrapper(project, true .align(AlignX.FILL) .columns(10) .validationOnApply { field -> - if (!field.text.matches(VERSION_REGEX)) { + if (!field.text.matches(APTOS_VERSION_REGEX)) { ValidationInfo("Version is invalid. Should be in form of MAJOR.MINOR.PATCH") } else { null @@ -54,8 +55,4 @@ class DownloadAptosSdkDialog(val project: Project?): DialogWrapper(project, true } } } - - companion object { - private val VERSION_REGEX = Regex("""\d+.\d+.\d""") - } } diff --git a/src/main/kotlin/org/move/cli/settings/MvProjectSettingsService.kt b/src/main/kotlin/org/move/cli/settings/MvProjectSettingsService.kt index 34a9c029..91e1e702 100644 --- a/src/main/kotlin/org/move/cli/settings/MvProjectSettingsService.kt +++ b/src/main/kotlin/org/move/cli/settings/MvProjectSettingsService.kt @@ -1,11 +1,11 @@ package org.move.cli.settings import com.intellij.openapi.Disposable +import com.intellij.openapi.components.Service import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.openapi.components.StoragePathMacros import com.intellij.openapi.components.service -import com.intellij.openapi.options.advanced.AdvancedSettings import com.intellij.openapi.project.Project import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.vfs.VirtualFile @@ -13,6 +13,9 @@ import org.move.bytecode.createDisposableOnFileChange import org.move.cli.runConfigurations.aptos.Aptos import org.move.cli.settings.MvProjectSettingsService.MoveProjectSettings import org.move.cli.settings.aptos.AptosExecType +import org.move.cli.update.PeriodicCheckForUpdatesService +import org.move.ide.notifications.updateAllNotifications +import org.move.openapiext.common.isUnitTestMode import org.move.stdext.exists import org.move.stdext.isExecutableFile import java.nio.file.Path @@ -70,6 +73,17 @@ class MvProjectSettingsService( } } + init { + // load update tool check scheduler + if (!isUnitTestMode) { + if (PeriodicCheckForUpdatesService.isEnabled) { + // do not load in tests + project.service() + updateAllNotifications(project) + } + } + } + override fun createSettingsChangedEvent( oldEvent: MoveProjectSettings, newEvent: MoveProjectSettings diff --git a/src/main/kotlin/org/move/cli/update/PeriodicCheckForUpdatesService.kt b/src/main/kotlin/org/move/cli/update/PeriodicCheckForUpdatesService.kt new file mode 100644 index 00000000..fc0d86d7 --- /dev/null +++ b/src/main/kotlin/org/move/cli/update/PeriodicCheckForUpdatesService.kt @@ -0,0 +1,188 @@ +package org.move.cli.update + +import com.intellij.execution.process.ProcessEvent +import com.intellij.execution.process.ProcessListener +import com.intellij.notification.NotificationType +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.* +import com.intellij.openapi.options.advanced.AdvancedSettings +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.Key +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.move.cli.runConfigurations.aptos.AptosExitStatus +import org.move.cli.settings.getAptosCli +import org.move.cli.update.PeriodicCheckForUpdatesService.LastTimeChecked +import org.move.ide.notifications.showBalloon +import org.move.ide.notifications.showDebugBalloon +import org.move.ide.notifications.updateAllNotifications +import org.move.stdext.capitalized +import org.move.stdext.now +import org.move.stdext.unwrapOrElse +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.seconds + +@State( + name = "org.move.PeriodicCheckForUpdatesService2", + storages = [Storage(StoragePathMacros.WORKSPACE_FILE)] +) +@Service(Service.Level.PROJECT) +class PeriodicCheckForUpdatesService( + private val project: Project, + cs: CoroutineScope +): + SimplePersistentStateComponent(LastTimeChecked()), + Disposable.Default { + + val aptosNewVersion: String? get() = state.aptosNewVersion + val revelaNewVersion: String? get() = state.revelaNewVersion + val movefmtNewVersion: String? get() = state.movefmtNewVersion + + class LastTimeChecked: BaseState() { + var lastChecked: Long by property(0.toLong()) + var aptosNewVersion: String? by string() + var revelaNewVersion: String? by string() + var movefmtNewVersion: String? by string() + } + + init { + cs.launch { + delay(INITIAL_POLLING_DELAY) + while (true) { + val now = now() + if (now - state.lastChecked.seconds > TIME_BETWEEN_UPDATE_CHECKS) { + checkForToolUpdate(AptosTool.APTOS) + checkForToolUpdate(AptosTool.REVELA) + checkForToolUpdate(AptosTool.MOVEFMT) + + state.lastChecked = now.inWholeSeconds + updateAllNotifications(project) + } + delay(TIME_BETWEEN_POLLING_FOR_UPDATE_CHECK) + } + } + } + + fun checkForToolUpdate(tool: AptosTool): UpdateCheckResult? { + val aptos = project.getAptosCli(parentDisposable = this) ?: return null // aptos not configured + val checkResult = + aptos.checkForToolUpdate(tool).unwrapOrElse { + // command wasn't started or return cannot be deserialized + it.printStackTrace() + project.showDebugBalloon( + "Error", + "error in checkForToolUpdate(), see stacktrace", + NotificationType.INFORMATION + ) + return null + } + val newVersion = when (checkResult) { + is UpdateCheckResult.UpdateIsAvailable -> checkResult.version + else -> null + } + setToolVersion(tool, newVersion) + project.showDebugBalloon( + "tool update: ${tool.id}", + "$checkResult\n$state", + NotificationType.INFORMATION + ) + return checkResult + } + + fun doToolUpdate(tool: AptosTool) { + val cancelDisposable = Disposer.newDisposable() + val aptos = project.getAptosCli(cancelDisposable) ?: return + + val updateService = this + // blocks + object: Task.Backgroundable(project, "Updating ${tool.id.capitalized()} to the latest version", true) { + override fun onCancel() { + Disposer.dispose(cancelDisposable) + } + + override fun run(indicator: ProgressIndicator) { + val indicatorPrefix = "${tool.id.capitalized()} update:" + indicator.text = "$indicatorPrefix: fetching latest version..." + val aptosOutput = aptos.doToolUpdate( + tool, + processListener = object: ProcessListener { + override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { + indicator.checkCanceled() + val line = event.text + when { + line.contains("Downloading") -> + indicator.text = "$indicatorPrefix: ${line.lowercase()}" + line.contains("Extracting") -> + indicator.text = "$indicatorPrefix: ${line.lowercase()}" + } + } + }) + .unwrapOrElse { + project.showBalloon("aptos update", it.message ?: "", NotificationType.ERROR) + return + } + + indicator.checkCanceled() + + when (val exitStatus = aptosOutput.exitStatus) { + is AptosExitStatus.Result -> { + project.showBalloon( + "aptos update success", + exitStatus.message, + NotificationType.INFORMATION + ) + updateService.setToolVersion(tool, null) + updateAllNotifications(project) + } + is AptosExitStatus.Error -> + project.showBalloon("aptos update failure", exitStatus.message, NotificationType.ERROR) + is AptosExitStatus.Malformed -> + project.showBalloon( + "aptos update malformed", + exitStatus.message, + NotificationType.ERROR + ) + } + } + }.queue() + } + + fun setToolVersion(tool: AptosTool, version: String?) { + when (tool) { + AptosTool.APTOS -> state.aptosNewVersion = version + AptosTool.REVELA -> state.revelaNewVersion = version + AptosTool.MOVEFMT -> state.movefmtNewVersion = version + } + } + + companion object { + private val INITIAL_POLLING_DELAY = 5.seconds + + // how often to ask the service whether it's time to run the update check + private val TIME_BETWEEN_POLLING_FOR_UPDATE_CHECK = 10.seconds + + // time between update checks for tools + private val TIME_BETWEEN_UPDATE_CHECKS = 1.hours + + val isEnabled get() = AdvancedSettings.getBoolean(PERIODIC_UPDATE_CHECK_SETTING_KEY) + + private const val PERIODIC_UPDATE_CHECK_SETTING_KEY: String = "org.move.aptos.update.check" + } +} + +val Project.toolUpdateService: PeriodicCheckForUpdatesService get() = service() + +enum class AptosTool(val id: String) { + APTOS("aptos"), REVELA("revela"), MOVEFMT("movefmt"); +} + +sealed class UpdateCheckResult { + data class UpToDate(val version: String): UpdateCheckResult() + data class UpdateIsAvailable(val version: String): UpdateCheckResult() + data class MalformedResult(val resultText: String): UpdateCheckResult() + data class UpdateError(val errorText: String): UpdateCheckResult() +} \ No newline at end of file diff --git a/src/main/kotlin/org/move/ide/notifications/InvalidAptosCliConfigurationNotification.kt b/src/main/kotlin/org/move/ide/notifications/InvalidAptosCliConfigurationNotification.kt index 1c127ccb..f9c430f5 100644 --- a/src/main/kotlin/org/move/ide/notifications/InvalidAptosCliConfigurationNotification.kt +++ b/src/main/kotlin/org/move/ide/notifications/InvalidAptosCliConfigurationNotification.kt @@ -33,14 +33,11 @@ class InvalidAptosCliConfigurationNotification(project: Project): MvAptosEditorN } } } - createActionLabel("Configure") { project.showSettingsDialog() } - createActionLabel("Do not show again") { - disableNotification(file) - updateAllNotifications(project) - } + + doNotShowAgainLabel(file) } } diff --git a/src/main/kotlin/org/move/ide/notifications/MvNotificationProvider.kt b/src/main/kotlin/org/move/ide/notifications/MvNotificationProvider.kt index be45fcc9..d00cf7c9 100644 --- a/src/main/kotlin/org/move/ide/notifications/MvNotificationProvider.kt +++ b/src/main/kotlin/org/move/ide/notifications/MvNotificationProvider.kt @@ -5,11 +5,12 @@ import com.intellij.ide.scratch.ScratchUtil import com.intellij.ide.util.PropertiesComponent import com.intellij.openapi.fileEditor.FileEditor import com.intellij.openapi.project.Project +import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.ui.EditorNotificationPanel import com.intellij.ui.EditorNotificationProvider import com.intellij.ui.EditorNotifications -import org.move.bytecode.AptosBytecodeDecompiler +import org.move.bytecode.DECOMPILED_ARTIFACTS_FOLDER import org.move.cli.settings.MvProjectSettingsServiceBase.* import org.move.lang.isMoveFile import org.move.lang.isMoveTomlManifestFile @@ -17,7 +18,6 @@ import org.move.lang.toNioPathOrNull import org.move.openapiext.common.isUnitTestMode import java.util.function.Function import javax.swing.JComponent -import kotlin.io.relativeToOrNull fun updateAllNotifications(project: Project) { EditorNotifications.getInstance(project).updateAllNotifications() @@ -83,11 +83,10 @@ abstract class MvAptosEditorNotificationProvider(project: Project): MvNotificati // skip non-physical file if (nioFile == null) return null - if (!enableForDecompiledFiles) { - // check whether file is a decompiler artifact - val decompiledArtifactsFolder = AptosBytecodeDecompiler.decompiledArtifactsFolder() - // belongs to the decompiled artifacts directory - if (nioFile.relativeToOrNull(decompiledArtifactsFolder) != null) return null + if (!enableForDecompiledFiles + && FileUtil.startsWith(nioFile.canonicalPath, DECOMPILED_ARTIFACTS_FOLDER.canonicalPath) + ) { + return null } // explicitly disabled in file @@ -96,4 +95,10 @@ abstract class MvAptosEditorNotificationProvider(project: Project): MvNotificati return createAptosNotificationPanel(file, project) } + protected fun EditorNotificationPanel.doNotShowAgainLabel(file: VirtualFile) { + createActionLabel("Do not show again") { + disableNotification(file) + updateAllNotifications(project) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/org/move/ide/notifications/NoAptosProjectDetectedNotification.kt b/src/main/kotlin/org/move/ide/notifications/NoAptosProjectDetectedNotification.kt index bbf20e50..d566f75d 100644 --- a/src/main/kotlin/org/move/ide/notifications/NoAptosProjectDetectedNotification.kt +++ b/src/main/kotlin/org/move/ide/notifications/NoAptosProjectDetectedNotification.kt @@ -47,20 +47,16 @@ class NoAptosProjectDetectedNotification(project: Project): MvAptosEditorNotific // no move projects available return EditorNotificationPanel().apply { text = "No Aptos projects found" - createActionLabel("Do not show again") { - disableNotification(file) - updateAllNotifications(project) - } + + doNotShowAgainLabel(file) } } if (moveProjectsService.findMoveProjectForFile(file) == null) { return EditorNotificationPanel().apply { text = "File does not belong to any known Aptos project" - createActionLabel("Do not show again") { - disableNotification(file) - updateAllNotifications(project) - } + + doNotShowAgainLabel(file) } } diff --git a/src/main/kotlin/org/move/ide/notifications/UpdateAvailableNotification.kt b/src/main/kotlin/org/move/ide/notifications/UpdateAvailableNotification.kt new file mode 100644 index 00000000..b9b27ade --- /dev/null +++ b/src/main/kotlin/org/move/ide/notifications/UpdateAvailableNotification.kt @@ -0,0 +1,45 @@ +package org.move.ide.notifications + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.ui.EditorNotificationPanel +import org.move.cli.update.AptosTool +import org.move.cli.update.PeriodicCheckForUpdatesService +import org.move.cli.update.toolUpdateService +import org.move.stdext.capitalized + +abstract class MvToolUpdateAvailableNotificationBase(project: Project): MvAptosEditorNotificationProvider(project) { + abstract val tool: AptosTool + + override val notificationProviderId: String + get() = "org.move.${tool.id.capitalized()}UpdateAvailableNotification" + + override fun createAptosNotificationPanel(file: VirtualFile, project: Project): EditorNotificationPanel? { + if (!PeriodicCheckForUpdatesService.isEnabled) return null + + val toolUpdateService = project.toolUpdateService + val newVersionAvailable = when (tool) { + AptosTool.APTOS -> toolUpdateService.aptosNewVersion + AptosTool.REVELA -> toolUpdateService.revelaNewVersion + AptosTool.MOVEFMT -> toolUpdateService.movefmtNewVersion + } + if (newVersionAvailable == null) return null + return EditorNotificationPanel().apply { + text("New ${tool.id.capitalized()} is available: $newVersionAvailable") + createActionLabel("Update ${tool.id.capitalized()}") { + toolUpdateService.doToolUpdate(tool) + } + doNotShowAgainLabel(file) + } + } +} + +class AptosUpdateAvailableNotification(project: Project): MvToolUpdateAvailableNotificationBase(project) { + override val tool: AptosTool get() = AptosTool.APTOS +} +class RevelaUpdateAvailableNotification(project: Project): MvToolUpdateAvailableNotificationBase(project) { + override val tool: AptosTool get() = AptosTool.REVELA +} +class MovefmtUpdateAvailableNotification(project: Project): MvToolUpdateAvailableNotificationBase(project) { + override val tool: AptosTool get() = AptosTool.MOVEFMT +} diff --git a/src/main/kotlin/org/move/openapiext/CommandLineExt.kt b/src/main/kotlin/org/move/openapiext/CommandLineExt.kt index 5491485b..533b9bb7 100644 --- a/src/main/kotlin/org/move/openapiext/CommandLineExt.kt +++ b/src/main/kotlin/org/move/openapiext/CommandLineExt.kt @@ -162,7 +162,8 @@ private fun GeneralCommandLine.showCommandLineBalloon(processOutput: ProcessOutp else -> { showBalloonWithoutProject( "Execution successful", - "$command $atWorkDir", + "

stdout=${processOutput.stdout}

" + + "

stderr=${processOutput.stderr}

", INFORMATION ) } diff --git a/src/main/kotlin/org/move/openapiext/CompositeProcessListener.kt b/src/main/kotlin/org/move/openapiext/CompositeProcessListener.kt new file mode 100644 index 00000000..7f1187fe --- /dev/null +++ b/src/main/kotlin/org/move/openapiext/CompositeProcessListener.kt @@ -0,0 +1,37 @@ +package org.move.openapiext + +import com.intellij.execution.process.ProcessEvent +import com.intellij.execution.process.ProcessListener +import com.intellij.openapi.util.Key + +class CompositeProcessListener(vararg val listeners: ProcessListener): ProcessListener { + override fun startNotified(event: ProcessEvent) { + for (listener in listeners) { + listener.startNotified(event) + } + } + + override fun processNotStarted() { + for (listener in listeners) { + listener.processNotStarted() + } + } + + override fun processWillTerminate(event: ProcessEvent, willBeDestroyed: Boolean) { + for (listener in listeners) { + listener.processWillTerminate(event, willBeDestroyed) + } + } + + override fun processTerminated(event: ProcessEvent) { + for (listener in listeners) { + listener.processTerminated(event) + } + } + + override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { + for (listener in listeners) { + listener.onTextAvailable(event, outputType) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/move/stdext/DurationUtils.kt b/src/main/kotlin/org/move/stdext/DurationUtils.kt new file mode 100644 index 00000000..018eea30 --- /dev/null +++ b/src/main/kotlin/org/move/stdext/DurationUtils.kt @@ -0,0 +1,6 @@ +package org.move.stdext + +import kotlin.time.Duration +import kotlin.time.Duration.Companion.nanoseconds + +fun now(): Duration = System.nanoTime().nanoseconds diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index daa732cb..67a24f43 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -172,14 +172,14 @@ - - + + + nameKey="inlay.hints.types" /> + + + + Move @@ -429,6 +436,12 @@ groupKey="advanced.setting.aptos.group" descriptionKey="advanced.setting.org.move.aptos.test.tool.window.description" /> + diff --git a/src/main/resources/messages/MvBundle.properties b/src/main/resources/messages/MvBundle.properties index 4e9511ba..79531d72 100644 --- a/src/main/resources/messages/MvBundle.properties +++ b/src/main/resources/messages/MvBundle.properties @@ -4,3 +4,6 @@ advanced.setting.aptos.group=Aptos advanced.setting.org.move.aptos.test.tool.window=Show test results in Test Tool Window advanced.setting.org.move.aptos.test.tool.window.description=The feature implementation is incomplete, test error messages are skipped + +advanced.setting.org.move.aptos.update.check=Check for CLI updates +advanced.setting.org.move.aptos.update.check.description=Enables periodic checks for the aptos tooling updates with aptos update $TOOLNAME --check. Requires IDE restart to work.