Skip to content
Permalink
Browse files

Add fade-in effect when updating pretty-printed values

Requires us to handle the ANSI text -> HTML conversion
  • Loading branch information...
alexarchambault committed Apr 11, 2019
1 parent c17cd72 commit 7c4ce3e3a09a08a87dc1c469819ade3db11c8043
@@ -136,6 +136,7 @@ lazy val `scala-interpreter` = project
Nil
}
},
libraryDependencies += "org.fusesource.jansi" % "jansi" % "1.18",
crossVersion := CrossVersion.full,
testSettings
)
@@ -0,0 +1,128 @@

// adapted from https://github.com/fusesource/jansi/blob/70ff98d5cbd5fb005d8a44ed31050388b256f9c6/jansi/src/main/java/org/fusesource/jansi/HtmlAnsiOutputStream.java

package almond.internals;

import org.fusesource.jansi.AnsiOutputStream;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

public class HtmlAnsiOutputStream extends AnsiOutputStream {

private boolean concealOn = false;

@Override
public void close() throws IOException {
closeAttributes();
super.close();
}

private static final String[] ANSI_COLOR_MAP = {"black", "red",
"green", "yellow", "blue", "magenta", "cyan", "white",};

private static final byte[] BYTES_QUOT = """.getBytes();
private static final byte[] BYTES_AMP = "&".getBytes();
private static final byte[] BYTES_LT = "<".getBytes();
private static final byte[] BYTES_GT = ">".getBytes();

public HtmlAnsiOutputStream(OutputStream os) {
super(os);
}

private final List<String> closingAttributes = new ArrayList<String>();

private void write(String s) throws IOException {
super.out.write(s.getBytes());
}

private void writeAttribute(String s) throws IOException {
write("<" + s + ">");
closingAttributes.add(0, s.split(" ", 2)[0]);
}

private void closeAttributes() throws IOException {
for (String attr : closingAttributes) {
write("</" + attr + ">");
}
closingAttributes.clear();
}

public void write(int data) throws IOException {
switch (data) {
case 34: // "
out.write(BYTES_QUOT);
break;
case 38: // &
out.write(BYTES_AMP);
break;
case 60: // <
out.write(BYTES_LT);
break;
case 62: // >
out.write(BYTES_GT);
break;
default:
super.write(data);
}
}

public void writeLine(byte[] buf, int offset, int len) throws IOException {
write(buf, offset, len);
closeAttributes();
}

@Override
protected void processSetAttribute(int attribute) throws IOException {
switch (attribute) {
case ATTRIBUTE_CONCEAL_ON:
write("\u001B[8m");
concealOn = true;
break;
case ATTRIBUTE_INTENSITY_BOLD:
writeAttribute("b");
break;
case ATTRIBUTE_INTENSITY_NORMAL:
closeAttributes();
break;
case ATTRIBUTE_UNDERLINE:
writeAttribute("u");
break;
case ATTRIBUTE_UNDERLINE_OFF:
closeAttributes();
break;
case ATTRIBUTE_NEGATIVE_ON:
break;
case ATTRIBUTE_NEGATIVE_OFF:
break;
default:
break;
}
}

@Override
protected void processAttributeRest() throws IOException {
if (concealOn) {
write("\u001B[0m");
concealOn = false;
}
closeAttributes();
}

@Override
protected void processDefaultTextColor() throws IOException {
processAttributeRest();
}

@Override
protected void processSetForegroundColor(int color, boolean bright) throws IOException {
writeAttribute("span class=\"ansi-" + ANSI_COLOR_MAP[color] + "-fg\"");
}

@Override
protected void processSetBackgroundColor(int color, boolean bright) throws IOException {
writeAttribute("span class=\"ansi-" + ANSI_COLOR_MAP[color] + "-bg\"");
}
}
@@ -1,5 +1,7 @@
package almond

import java.io.ByteArrayOutputStream
import java.nio.charset.StandardCharsets
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.{Files, Path}

@@ -492,8 +494,20 @@ final class ScalaInterpreter(
case None =>
DisplayData.text(res0)
case Some(r) =>
val baos = new ByteArrayOutputStream
val haos = new HtmlAnsiOutputStream(baos)
haos.write(res0.getBytes(StandardCharsets.UTF_8))
haos.close()
val html =
s"""<div class="jp-RenderedText">
|<pre>${baos.toString("UTF-8")}</pre>
|</div>""".stripMargin
log.info(s"HTML: $html")
val d = r.add(
almond.display.Text(res0).displayData(),
almond.display.Data(
almond.display.Text.mimeType -> res0,
almond.display.Html.mimeType -> html
).displayData(),
variables
)
outputHandler match {
@@ -1,5 +1,7 @@
package almond.internals

import java.io.ByteArrayOutputStream
import java.nio.charset.StandardCharsets
import java.util.concurrent.ConcurrentHashMap

import almond.interpreter.api.DisplayData
@@ -33,7 +35,7 @@ final class UpdatableResults(
refs.put(k, ref)
k -> vOpt.fold(v)(_._1)
}
UpdatableResults.substituteVariables(data, variables0)
UpdatableResults.substituteVariables(data, variables0, isFirst = true)
}
}

@@ -42,7 +44,7 @@ final class UpdatableResults(
def updateRef(data: DisplayData, ref: Ref[Map[String, String]]): Unit = {
val m0 = ref()
val m = m0 + (k -> v)
val data0 = UpdatableResults.substituteVariables(data, m)
val data0 = UpdatableResults.substituteVariables(data, m, isFirst = false)
log.debug(s"Updating variable $k with $v: $data0")
ref() = m
Future(updateData(data0))(ec)
@@ -71,7 +73,7 @@ final class UpdatableResults(

object UpdatableResults {

def substituteVariables(d: DisplayData, m: Map[String, String]): DisplayData =
def substituteVariables(d: DisplayData, m: Map[String, String], isFirst: Boolean): DisplayData =
d.copy(
data = d.data.map {
case ("text/plain", t) =>
@@ -81,7 +83,24 @@ object UpdatableResults {
// needed
acc.replace(k, v)
}
case kv => kv
case ("text/html", t) =>
"text/html" -> m.foldLeft(t) {
case (acc, (k, v)) =>
val baos = new ByteArrayOutputStream
val haos = new HtmlAnsiOutputStream(baos)
haos.write(v.getBytes(StandardCharsets.UTF_8))
haos.close()

val (prefix, suffix) =
if (isFirst) ("", "")
else (
"""<style>@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }</style><span style="animation: fadein 2s;">""",
"</span>"
)
acc.replace(k, prefix + baos.toString("UTF-8") + suffix)
}
case kv =>
kv
}
)

0 comments on commit 7c4ce3e

Please sign in to comment.
You can’t perform that action at this time.