Created
April 19, 2022 16:47
-
-
Save atty303/f050746f9c1cedf69a4f70774bd3d509 to your computer and use it in GitHub Desktop.
mill + JediTerm integration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import $cp.`terminal-2.68.jar` | |
import $ivy.`com.googlecode.lanterna:lanterna:3.1.1` | |
import com.jediterm.terminal.TerminalColor | |
import com.jediterm.terminal.model.TerminalModelListener | |
import scala.util.Using | |
import com.jediterm.terminal.model.CharBuffer | |
import com.jediterm.terminal.util.CharUtils | |
import com.jediterm.terminal.{StyledTextConsumer, TerminalMode, TextStyle} | |
import com.googlecode.lanterna | |
import com.jediterm.terminal.emulator.JediEmulator | |
import com.jediterm.terminal.emulator.mouse.MouseMode | |
import com.jediterm.terminal.model.{JediTerminal, StyleState, TerminalSelection, TerminalTextBuffer} | |
import com.jediterm.terminal.{CursorShape, ProcessTtyConnector, RequestOrigin, TerminalDisplay, TtyBasedArrayDataStream} | |
import mill._ | |
import java.awt.Dimension | |
import java.nio.charset.Charset | |
import scala.jdk.CollectionConverters._ | |
trait Workspace extends Module { | |
def run() = T.command { | |
val curses = new lanterna.terminal.DefaultTerminalFactory().createHeadlessTerminal() | |
curses.enterPrivateMode() | |
try { | |
val p1 = os.proc("cat", "README.md").spawn(mergeErrIntoOut = true) | |
val t1 = SubprocessTerminal(curses, 10, 10, 80, 24, p1) | |
val p2 = os.proc("cat", "maskfile.md").spawn(mergeErrIntoOut = true) | |
val t2 = SubprocessTerminal(curses, 0, 40, 80, 24, p2) | |
Using.resources(t1, t2) { (_, _) => | |
Thread.sleep(5000) | |
} | |
} finally { | |
curses.exitPrivateMode() | |
curses.close() | |
} | |
} | |
} | |
case class SubprocessTerminal(curses: lanterna.terminal.Terminal, ox: Int, oy: Int, width: Int, height: Int, proc: os.SubProcess) extends AutoCloseable { | |
private val styleState = new StyleState() | |
styleState.setDefaultStyle(new TextStyle(TerminalColor.BLACK, TerminalColor.WHITE)) | |
private val terminalTextBuffer = new TerminalTextBuffer(width, height, styleState, 1000, null) | |
private val terminalDisplay = new LanternaTerminalDisplay(curses, terminalTextBuffer, ox, oy) | |
private val jediTerminal = new JediTerminal(terminalDisplay, terminalTextBuffer, styleState) | |
jediTerminal.setModeEnabled(TerminalMode.AutoNewLine, true) | |
private val ttyConnector = new ProcessTtyConnector(proc.wrapped, Charset.defaultCharset()) with AutoCloseable { | |
override def getName: String = "Local" | |
} | |
private val jediEmulator = new JediEmulator(new TtyBasedArrayDataStream(ttyConnector), jediTerminal) | |
private val emulatorThread: Thread = { | |
val t = new Thread(() => { | |
Using.resource(ttyConnector) { _ => | |
try { | |
while (!Thread.currentThread().isInterrupted && jediEmulator.hasNext) { | |
jediEmulator.next() | |
} | |
} catch { | |
case e: InterruptedException => | |
case e => e.printStackTrace(System.err) | |
} | |
} | |
}) | |
t.start() | |
t | |
} | |
def close(): Unit = emulatorThread.interrupt() | |
} | |
class LanternaTerminalDisplay(curses: lanterna.terminal.Terminal, terminalTextBuffer: TerminalTextBuffer, ox: Int, oy: Int) extends TerminalDisplay with AutoCloseable { | |
override def getRowCount = terminalTextBuffer.getHeight | |
override def getColumnCount = terminalTextBuffer.getWidth | |
override def setCursor(x: Int, y: Int) = curses.setCursorPosition(ox + x, oy + y) | |
override def setCursorShape(cursorShape: CursorShape) = () | |
override def beep() = () | |
override def requestResize(dimension: Dimension, requestOrigin: RequestOrigin, i: Int, i1: Int, resizeHandler: JediTerminal.ResizeHandler) = () | |
override def scrollArea(scrollRegionTop: Int, scrollRegioSize: Int, dy: Int) = () | |
override def setCursorVisible(shouldDrawCursor: Boolean) = curses.setCursorVisible(shouldDrawCursor) | |
override def setScrollingEnabled(enabled: Boolean) = () | |
override def setBlinkingCursor(enabled: Boolean) = () | |
override def getWindowTitle: String = "" | |
override def setWindowTitle(s: String) = () | |
override def terminalMouseModeSet(mouseMode: MouseMode) = () | |
override def getSelection: TerminalSelection = null | |
override def ambiguousCharsAreDoubleWidth(): Boolean = false | |
def close(): Unit = terminalTextBuffer.removeModelListener(modelListener) | |
private val modelListener: TerminalModelListener = () => { | |
curses.synchronized { | |
renderTerminalTextBuffer() | |
} | |
} | |
terminalTextBuffer.addModelListener(modelListener) | |
private def renderTerminalTextBuffer(): Unit = { | |
clearRegion() | |
terminalTextBuffer.lock() | |
terminalTextBuffer.processScreenLines(0, terminalTextBuffer.getHeight, new StyledTextConsumer { | |
override def consume(x: Int, y: Int, style: TextStyle, characters: CharBuffer, startRow: Int): Unit = { | |
curses.setCursorPosition(ox + x, oy + y) | |
// dont' draw second part(fake one) of double width character | |
characters.chars().iterator().asScala.foreach(n => if (n.toChar != CharUtils.DWC) curses.putCharacter(n.toChar)) | |
curses.flush() | |
} | |
override def consumeNul(x: Int, y: Int, nulIndex: Int, style: TextStyle, characters: CharBuffer, startRow: Int): Unit = () | |
override def consumeQueue(x: Int, y: Int, nulIndex: Int, startRow: Int): Unit = () | |
}) | |
terminalTextBuffer.unlock() | |
curses.flush() | |
} | |
private def clearRegion(): Unit = { | |
0.until(terminalTextBuffer.getHeight).foreach(y => { | |
curses.setCursorPosition(ox, oy + y) | |
curses.putString(" ".repeat(terminalTextBuffer.getWidth)) | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment