Oh the days of yore! I remember when every village lad, who dreamed to become a Wizard, knew his ANSI control codes by heart. Surely those byte sequences allowed one in the knowledge to produce awesome visions and bright colors of exotic places on text displays lesser ones thought only as dumb terminals.
But the days are gone, and we have high resolution displays and 3D graphics, mouses and touchscreens. Time to go, you old fool! But wait! In the heart of every terminal window, (we are not speaking about Windows now) there beats a golden heart of old VT100 terminal. So yes, it is possible to control cursor positions and scroll areas with Java console output.
The idea is, that the terminal is not so dump after all. It only waits escape sequences for you to tell what special it should do. Instructions start with escape character, which is non-printable ASCII character 27, usually written in hexadecimal 0x1b
or octal 033
. After that some human readable stuff follows, most often bracket [
, some numerical information and a letter. If more numbers are needed, they are separated by semicolons.
For instance, to change font color to red, you output sequence <ESC>[31m
like this:
System.out.println("\033[31mThis should appear red");
You'd better open a separate terminal window for fiddling with the codes, because the display turns easily garbled. In fact the colors of shell prompt and directory listings are implemented by these codes and can easily be personalized.
If you want to separate areas from top and bottom of the window from scrolling, there's a code for that, <ESC>[<top>;<bottom>r
where you declare the lines from top and bottom, between where the scrolling happens, for instance: \033[2;22r
Links:
And of course answer is not an answer without running code. Right? I have to admit I got little carried away with this. Although it felt strange to use Java for terminal sequences. K&R C or COMMODORE BASIC would have been more fitting. I made very simple demo, but slowed down the printing speed to same level as old 1200 baud modem, so you can easily observe what's happening. Set the SPEED
to 0 to disable.
Escape sequences are same kind of markup as HTML text formatting tags, and what's best, The BLINK is still there!
Save as TerminalDemo.java
, compile with javac TerminalDemo.java
and run with command: java TerminalDemo
import java.io.*;
public class TerminalDemo {
// Default speed in bits per second for serial line simulation
// Set 0 to disable
static final int SPEED = 1200;
// ANSI Terminal codes
static final String ESC = "\033";
static String clearScreen() { return "\033[2J"; }
static String cursorHome() { return "\033[H"; }
static String cursorTo(int row, int column) {
return String.format("\033[%d;%dH", row, column);
}
static String cursorSave() { return "\033[s"; }
static String cursorRestore() { return "\033[u"; }
static String scrollScreen() { return "\033[r"; }
static String scrollSet(int top, int bottom) {
return String.format("\033[%d;%dr", top, bottom);
}
static String scrollUp() { return "\033D"; }
static String scrollDown() { return "\033D"; }
static String setAttribute(int attr) {
return String.format("\033[%dm", attr);
}
static final int ATTR_RESET = 0;
static final int ATTR_BRIGHT = 1;
static final int ATTR_USCORE = 4;
static final int ATTR_BLINK = 5;
static final int ATTR_REVERSE = 7;
static final int ATTR_FCOL_BLACK = 30;
static final int ATTR_FCOL_RED = 31;
static final int ATTR_FCOL_GREEN = 32;
static final int ATTR_FCOL_YELLOW = 33;
static final int ATTR__BCOL_BLACK = 40;
static final int ATTR__BCOL_RED = 41;
static final int ATTR__BCOL_GREEN = 42;
public static void main(String[] args) {
// example string showing some text attributes
String s = "This \033[31mstring\033[32m should \033[33mchange \033[33m color \033[41m and start \033[5m blinking!\033[0m Isn't that neat?\n";
// Reset scrolling, clear screen and bring cursor home
System.out.print(clearScreen());
System.out.print(scrollScreen());
// Print example string s
slowPrint(s);
// some text attributes
slowPrint("This "
+ setAttribute(ATTR_USCORE) + "should be undescored\n"
+ setAttribute(ATTR_RESET)
+ setAttribute(ATTR_FCOL_RED) + "and this red\n"
+ setAttribute(ATTR_RESET)
+ "some "
+ setAttribute(ATTR_BRIGHT)
+ setAttribute(ATTR_FCOL_YELLOW)
+ setAttribute(ATTR_BLINK) + "BRIGHT YELLOW BLINKIN\n"
+ setAttribute(ATTR_RESET)
+ "could be fun.\n\n"
+ "Please press ENTER");
// Wait for ENTER
try { System.in.read(); } catch(IOException e) {e.printStackTrace();}
// Set scroll area
slowPrint(""
+ clearScreen()
+ scrollSet(2,20)
+ cursorTo(1,1)
+ "Cleared screen and set scroll rows Top: 2 and Bottom: 20\n"
+ cursorTo(21,1)
+ "Bottom area starts here"
+ cursorTo(2,1)
+ "");
// print some random text
slowPrint(randomText(60));
// reset text attributes, reset scroll area and set cursor
// below scroll area
System.out.print(setAttribute(ATTR_RESET));
System.out.print(scrollScreen());
System.out.println(cursorTo(22,1));
}
// Slow things down to resemble old serial terminals
private static void slowPrint(String s) {
slowPrint(s, SPEED);
}
private static void slowPrint(String s, int bps) {
for (int i = 0; i < s.length(); i++) {
System.out.print(s.charAt(i));
if(bps == 0) continue;
try { Thread.sleep((int)(8000.0 / bps)); }
catch(InterruptedException ex)
{ Thread.currentThread().interrupt(); }
}
}
// Retursn a character representation of sin graph
private static String randomText(int lines) {
String r = "";
for(int i=0; i<lines; i++) {
int sin = (int)Math.abs((Math.sin(1.0/20 * i)*30));
r += setAttribute((sin / 4) + 30);
for(int j=0; j<80; j++) {
if(j > 40 + sin)
break;
r += (j < (40-sin)) ? " " : "X";
}
r += setAttribute(ATTR_RESET) + "\n";
}
return r;
}
}