diff --git a/core/src/main/kotlin/net/corda/core/utilities/Emoji.kt b/core/src/main/kotlin/net/corda/core/utilities/Emoji.kt index 0da89e4c8d..db70b2d6b8 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/Emoji.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/Emoji.kt @@ -7,7 +7,9 @@ import net.corda.core.codePointsString */ object Emoji { // Unfortunately only Apple has a terminal that can do colour emoji AND an emoji font installed by default. - val hasEmojiTerminal by lazy { listOf("Apple_Terminal", "iTerm.app").contains(System.getenv("TERM_PROGRAM")) } + val hasEmojiTerminal by lazy { + System.getenv("CORDA_FORCE_EMOJI") != null || System.getenv("TERM_PROGRAM") in listOf("Apple_Terminal", "iTerm.app") + } @JvmStatic val CODE_SANTA_CLAUS: String = codePointsString(0x1F385) @JvmStatic val CODE_DIAMOND: String = codePointsString(0x1F537) @@ -32,14 +34,21 @@ object Emoji { val diamond: String get() = if (emojiMode.get() != null) "$CODE_DIAMOND " else "" val bagOfCash: String get() = if (emojiMode.get() != null) "$CODE_BAG_OF_CASH " else "" val newspaper: String get() = if (emojiMode.get() != null) "$CODE_NEWSPAPER " else "" - val rightArrow: String get() = if (emojiMode.get() != null) "$CODE_RIGHT_ARROW " else "" val leftArrow: String get() = if (emojiMode.get() != null) "$CODE_LEFT_ARROW " else "" val paperclip: String get() = if (emojiMode.get() != null) "$CODE_PAPERCLIP " else "" val coolGuy: String get() = if (emojiMode.get() != null) "$CODE_COOL_GUY " else "" val books: String get() = if (emojiMode.get() != null) "$CODE_BOOKS " else "" + // These have old/non-emoji symbols with better platform support. + val greenTick: String get() = if (emojiMode.get() != null) "$CODE_GREEN_TICK " else "✓" + val rightArrow: String get() = if (emojiMode.get() != null) "$CODE_RIGHT_ARROW " else "▶︎" + val skullAndCrossbones: String get() = if (emojiMode.get() != null) "$CODE_SKULL_AND_CROSSBONES " else "☂" + val noEntry: String get() = if (emojiMode.get() != null) "$CODE_NO_ENTRY " else "✘" + inline fun renderIfSupported(body: () -> T): T { - emojiMode.set(this) // Could be any object. + if (hasEmojiTerminal) + emojiMode.set(this) // Could be any object. + try { return body() } finally { diff --git a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt index cbdb8704bc..0f99b23ae4 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt @@ -57,6 +57,7 @@ class ProgressTracker(vararg steps: Step) { open fun childProgressTracker(): ProgressTracker? = null } + // TODO: There's no actual way to create these steps anymore! /** This class makes it easier to relabel a step on the fly, to provide transient information. */ open inner class RelabelableStep(currentLabel: String) : Step(currentLabel) { override val changes: BehaviorSubject = BehaviorSubject.create() diff --git a/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt b/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt index 9bc478b1e4..e8b6de4269 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt @@ -1,9 +1,6 @@ package net.corda.node.utilities -import net.corda.core.utilities.Emoji.CODE_GREEN_TICK -import net.corda.core.utilities.Emoji.CODE_NO_ENTRY -import net.corda.core.utilities.Emoji.CODE_RIGHT_ARROW -import net.corda.core.utilities.Emoji.CODE_SKULL_AND_CROSSBONES +import net.corda.core.utilities.Emoji import net.corda.core.utilities.ProgressTracker import net.corda.node.utilities.ANSIProgressRenderer.progressTracker import org.apache.logging.log4j.LogManager @@ -43,12 +40,22 @@ object ANSIProgressRenderer { } // Reset the state when a new tracker is wired up. - prevMessagePrinted = null - prevLinesDrawn = 0 - draw(true) - subscription = value?.changes?.subscribe({ draw(true) }, { draw(true, it) }, { progressTracker = null; draw(true) }) + if (value != null) { + prevMessagePrinted = null + prevLinesDrawn = 0 + draw(true) + subscription = value.changes.subscribe({ draw(true) }, { done(it) }, { done(null) }) + } } + var onDone: () -> Unit = {} + + private fun done(error: Throwable?) { + if (error == null) progressTracker = null + draw(true, error) + onDone() + } + private fun setup() { AnsiConsole.systemInstall() @@ -62,7 +69,7 @@ object ANSIProgressRenderer { // than doing things the official way with a dedicated plugin, etc, as it avoids mucking around with all // the config XML and lifecycle goop. val manager = LogManager.getContext(false) as LoggerContext - val consoleAppender = manager.configuration.appenders.values.filterIsInstance().single() + val consoleAppender = manager.configuration.appenders.values.filterIsInstance().single { it.name == "Console-Appender" } val scrollingAppender = object : AbstractOutputStreamAppender( consoleAppender.name, consoleAppender.layout, consoleAppender.filter, consoleAppender.ignoreExceptions(), true, consoleAppender.manager) { @@ -115,39 +122,41 @@ object ANSIProgressRenderer { return } - // Handle the case where the number of steps in a progress tracker is changed during execution. - val ansi = Ansi.ansi() - if (prevLinesDrawn > 0 && moveUp) - ansi.cursorUp(prevLinesDrawn) + Emoji.renderIfSupported { + // Handle the case where the number of steps in a progress tracker is changed during execution. + val ansi = Ansi.ansi() + if (prevLinesDrawn > 0 && moveUp) + ansi.cursorUp(prevLinesDrawn) - // Put a blank line between any logging and us. - ansi.eraseLine() - ansi.newline() - val pt = progressTracker ?: return - var newLinesDrawn = 1 + pt.renderLevel(ansi, 0, error != null) - - if (error != null) { - // TODO: This should be using emoji only on supported platforms. - ansi.a("$CODE_SKULL_AND_CROSSBONES $error") - ansi.eraseLine(Ansi.Erase.FORWARD) + // Put a blank line between any logging and us. + ansi.eraseLine() ansi.newline() - newLinesDrawn++ - } + val pt = progressTracker ?: return + var newLinesDrawn = 1 + pt.renderLevel(ansi, 0, error != null) - if (newLinesDrawn < prevLinesDrawn) { - // If some steps were removed from the progress tracker, we don't want to leave junk hanging around below. - val linesToClear = prevLinesDrawn - newLinesDrawn - repeat(linesToClear) { - ansi.eraseLine() + if (error != null) { + ansi.a("${Emoji.skullAndCrossbones} ${error.message}") + ansi.eraseLine(Ansi.Erase.FORWARD) ansi.newline() + newLinesDrawn++ } - ansi.cursorUp(linesToClear) - } - prevLinesDrawn = newLinesDrawn - // Need to force a flush here in order to ensure stderr/stdout sync up properly. - System.out.print(ansi) - System.out.flush() + if (newLinesDrawn < prevLinesDrawn) { + // If some steps were removed from the progress tracker, we don't want to leave junk hanging around below. + val linesToClear = prevLinesDrawn - newLinesDrawn + repeat(linesToClear) { + ansi.eraseLine() + ansi.newline() + } + ansi.cursorUp(linesToClear) + } + prevLinesDrawn = newLinesDrawn + + // Need to force a flush here in order to ensure stderr/stdout sync up properly. + System.out.print(ansi) + System.out.flush() + } + } // Returns number of lines rendered. @@ -160,11 +169,11 @@ object ANSIProgressRenderer { if (indent > 0 && step == ProgressTracker.DONE) continue val marker = when { - index < stepIndex -> "$CODE_GREEN_TICK " - index == stepIndex && step == ProgressTracker.DONE -> "$CODE_GREEN_TICK " - index == stepIndex -> "$CODE_RIGHT_ARROW " - error -> "$CODE_NO_ENTRY " - else -> " " + index < stepIndex -> "${Emoji.greenTick} " + index == stepIndex && step == ProgressTracker.DONE -> "${Emoji.greenTick} " + index == stepIndex -> "${Emoji.rightArrow} " + error -> "${Emoji.noEntry} " + else -> " " // Not reached yet. } a(" ".repeat(indent)) a(marker)