Some fixes to emoji/ANSI renderer

This commit is contained in:
Mike Hearn
2017-03-15 21:14:50 +01:00
parent f079c05e6a
commit eb21458885
3 changed files with 63 additions and 44 deletions

View File

@ -7,7 +7,9 @@ import net.corda.core.codePointsString
*/ */
object Emoji { object Emoji {
// Unfortunately only Apple has a terminal that can do colour emoji AND an emoji font installed by default. // 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_SANTA_CLAUS: String = codePointsString(0x1F385)
@JvmStatic val CODE_DIAMOND: String = codePointsString(0x1F537) @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 diamond: String get() = if (emojiMode.get() != null) "$CODE_DIAMOND " else ""
val bagOfCash: String get() = if (emojiMode.get() != null) "$CODE_BAG_OF_CASH " 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 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 leftArrow: String get() = if (emojiMode.get() != null) "$CODE_LEFT_ARROW " else ""
val paperclip: String get() = if (emojiMode.get() != null) "$CODE_PAPERCLIP " 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 coolGuy: String get() = if (emojiMode.get() != null) "$CODE_COOL_GUY " else ""
val books: String get() = if (emojiMode.get() != null) "$CODE_BOOKS " 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 <T> renderIfSupported(body: () -> T): T { inline fun <T> renderIfSupported(body: () -> T): T {
emojiMode.set(this) // Could be any object. if (hasEmojiTerminal)
emojiMode.set(this) // Could be any object.
try { try {
return body() return body()
} finally { } finally {

View File

@ -57,6 +57,7 @@ class ProgressTracker(vararg steps: Step) {
open fun childProgressTracker(): ProgressTracker? = null 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. */ /** This class makes it easier to relabel a step on the fly, to provide transient information. */
open inner class RelabelableStep(currentLabel: String) : Step(currentLabel) { open inner class RelabelableStep(currentLabel: String) : Step(currentLabel) {
override val changes: BehaviorSubject<Change> = BehaviorSubject.create() override val changes: BehaviorSubject<Change> = BehaviorSubject.create()

View File

@ -1,9 +1,6 @@
package net.corda.node.utilities package net.corda.node.utilities
import net.corda.core.utilities.Emoji.CODE_GREEN_TICK import net.corda.core.utilities.Emoji
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.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.node.utilities.ANSIProgressRenderer.progressTracker import net.corda.node.utilities.ANSIProgressRenderer.progressTracker
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
@ -43,12 +40,22 @@ object ANSIProgressRenderer {
} }
// Reset the state when a new tracker is wired up. // Reset the state when a new tracker is wired up.
prevMessagePrinted = null if (value != null) {
prevLinesDrawn = 0 prevMessagePrinted = null
draw(true) prevLinesDrawn = 0
subscription = value?.changes?.subscribe({ draw(true) }, { draw(true, it) }, { progressTracker = null; draw(true) }) 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() { private fun setup() {
AnsiConsole.systemInstall() 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 // than doing things the official way with a dedicated plugin, etc, as it avoids mucking around with all
// the config XML and lifecycle goop. // the config XML and lifecycle goop.
val manager = LogManager.getContext(false) as LoggerContext val manager = LogManager.getContext(false) as LoggerContext
val consoleAppender = manager.configuration.appenders.values.filterIsInstance<ConsoleAppender>().single() val consoleAppender = manager.configuration.appenders.values.filterIsInstance<ConsoleAppender>().single { it.name == "Console-Appender" }
val scrollingAppender = object : AbstractOutputStreamAppender<OutputStreamManager>( val scrollingAppender = object : AbstractOutputStreamAppender<OutputStreamManager>(
consoleAppender.name, consoleAppender.layout, consoleAppender.filter, consoleAppender.name, consoleAppender.layout, consoleAppender.filter,
consoleAppender.ignoreExceptions(), true, consoleAppender.manager) { consoleAppender.ignoreExceptions(), true, consoleAppender.manager) {
@ -115,39 +122,41 @@ object ANSIProgressRenderer {
return return
} }
// Handle the case where the number of steps in a progress tracker is changed during execution. Emoji.renderIfSupported {
val ansi = Ansi.ansi() // Handle the case where the number of steps in a progress tracker is changed during execution.
if (prevLinesDrawn > 0 && moveUp) val ansi = Ansi.ansi()
ansi.cursorUp(prevLinesDrawn) if (prevLinesDrawn > 0 && moveUp)
ansi.cursorUp(prevLinesDrawn)
// Put a blank line between any logging and us. // Put a blank line between any logging and us.
ansi.eraseLine() 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)
ansi.newline() ansi.newline()
newLinesDrawn++ val pt = progressTracker ?: return
} var newLinesDrawn = 1 + pt.renderLevel(ansi, 0, error != null)
if (newLinesDrawn < prevLinesDrawn) { if (error != null) {
// If some steps were removed from the progress tracker, we don't want to leave junk hanging around below. ansi.a("${Emoji.skullAndCrossbones} ${error.message}")
val linesToClear = prevLinesDrawn - newLinesDrawn ansi.eraseLine(Ansi.Erase.FORWARD)
repeat(linesToClear) {
ansi.eraseLine()
ansi.newline() ansi.newline()
newLinesDrawn++
} }
ansi.cursorUp(linesToClear)
}
prevLinesDrawn = newLinesDrawn
// Need to force a flush here in order to ensure stderr/stdout sync up properly. if (newLinesDrawn < prevLinesDrawn) {
System.out.print(ansi) // If some steps were removed from the progress tracker, we don't want to leave junk hanging around below.
System.out.flush() 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. // Returns number of lines rendered.
@ -160,11 +169,11 @@ object ANSIProgressRenderer {
if (indent > 0 && step == ProgressTracker.DONE) continue if (indent > 0 && step == ProgressTracker.DONE) continue
val marker = when { val marker = when {
index < stepIndex -> "$CODE_GREEN_TICK " index < stepIndex -> "${Emoji.greenTick} "
index == stepIndex && step == ProgressTracker.DONE -> "$CODE_GREEN_TICK " index == stepIndex && step == ProgressTracker.DONE -> "${Emoji.greenTick} "
index == stepIndex -> "$CODE_RIGHT_ARROW " index == stepIndex -> "${Emoji.rightArrow} "
error -> "$CODE_NO_ENTRY " error -> "${Emoji.noEntry} "
else -> " " else -> " " // Not reached yet.
} }
a(" ".repeat(indent)) a(" ".repeat(indent))
a(marker) a(marker)