2 * Jexer - Java Text User Interface
6 * Author: Kevin Lamonte, <a href="mailto:kevin.lamonte@gmail.com">kevin.lamonte@gmail.com</a>
8 * License: LGPLv3 or later
10 * Copyright: This module is licensed under the GNU Lesser General
11 * Public License Version 3. Please see the file "COPYING" in this
12 * directory for more information about the GNU Lesser General Public
15 * Copyright (C) 2015 Kevin Lamonte
17 * This program is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU Lesser General Public License
19 * as published by the Free Software Foundation; either version 3 of
20 * the License, or (at your option) any later version.
22 * This program is distributed in the hope that it will be useful, but
23 * WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 * General Public License for more details.
27 * You should have received a copy of the GNU Lesser General Public
28 * License along with this program; if not, see
29 * http://www.gnu.org/licenses/, or write to the Free Software
30 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
35 import java
.io
.BufferedReader
;
36 import java
.io
.InputStream
;
37 import java
.io
.InputStreamReader
;
38 import java
.io
.IOException
;
39 import java
.io
.OutputStream
;
40 import java
.io
.OutputStreamWriter
;
41 import java
.io
.PrintWriter
;
42 import java
.io
.Reader
;
43 import java
.io
.UnsupportedEncodingException
;
44 import java
.util
.ArrayList
;
45 import java
.util
.Date
;
46 import java
.util
.List
;
47 import java
.util
.LinkedList
;
49 import jexer
.TKeypress
;
50 import jexer
.bits
.Color
;
51 import jexer
.event
.TInputEvent
;
52 import jexer
.event
.TKeypressEvent
;
53 import jexer
.event
.TMouseEvent
;
54 import jexer
.event
.TResizeEvent
;
55 import jexer
.session
.SessionInfo
;
56 import jexer
.session
.TSessionInfo
;
57 import jexer
.session
.TTYSessionInfo
;
58 import static jexer
.TKeypress
.*;
61 * This class has convenience methods for emitting output to ANSI
62 * X3.64 / ECMA-48 type terminals e.g. xterm, linux, vt100, ansi.sys,
65 public class ECMA48Terminal
{
68 * The session information
70 public SessionInfo session
;
73 * Parameters being collected. E.g. if the string is \033[1;3m, then
74 * params[0] will be 1 and params[1] will be 3.
76 private ArrayList
<String
> params
;
79 * params[paramI] is being appended to.
84 * States in the input parser
86 private enum ParseState
{
97 * Current parsing state
99 private ParseState state
;
102 * The time we entered ESCAPE. If we get a bare escape
103 * without a code following it, this is used to return that bare
106 private long escapeTime
;
109 * true if mouse1 was down. Used to report mouse1 on the release event.
111 private boolean mouse1
;
114 * true if mouse2 was down. Used to report mouse2 on the release event.
116 private boolean mouse2
;
119 * true if mouse3 was down. Used to report mouse3 on the release event.
121 private boolean mouse3
;
124 * Cache the cursor visibility value so we only emit the sequence when we
127 private boolean cursorOn
= true;
130 * Cache the last window size to figure out if a TResizeEvent needs to be
133 private TResizeEvent windowResize
= null;
136 * If true, then we changed System.in and need to change it back.
138 private boolean setRawMode
;
141 * The terminal's input. If an InputStream is not specified in the
142 * constructor, then this InputReader will be bound to System.in with
145 private Reader input
;
148 * The terminal's output. If an OutputStream is not specified in the
149 * constructor, then this PrintWriter will be bound to System.out with
152 private PrintWriter output
;
155 * When true, the terminal is sending non-UTF8 bytes when reporting mouse
158 * TODO: Add broken mouse detection back into the reader.
160 private boolean brokenTerminalUTFMouse
= false;
163 * Get the output writer.
167 public PrintWriter
getOutput() {
172 * Call 'stty cooked' to set cooked mode.
174 private void sttyCooked() {
179 * Call 'stty raw' to set raw mode.
181 private void sttyRaw() {
186 * Call 'stty' to set raw or cooked mode.
188 * @param mode if true, set raw mode, otherwise set cooked mode
190 private void doStty(boolean mode
) {
192 "/bin/sh", "-c", "stty raw < /dev/tty"
194 String
[] cmdCooked
= {
195 "/bin/sh", "-c", "stty cooked < /dev/tty"
198 System
.out
.println("spawn stty");
202 process
= Runtime
.getRuntime().exec(cmdRaw
);
204 process
= Runtime
.getRuntime().exec(cmdCooked
);
206 BufferedReader in
= new BufferedReader(new InputStreamReader(process
.getInputStream(), "UTF-8"));
207 String line
= in
.readLine();
208 if ((line
!= null) && (line
.length() > 0)) {
209 System
.err
.println("WEIRD?! Normal output from stty: " + line
);
212 BufferedReader err
= new BufferedReader(new InputStreamReader(process
.getErrorStream(), "UTF-8"));
213 line
= err
.readLine();
214 if ((line
!= null) && (line
.length() > 0)) {
215 System
.err
.println("Error output from stty: " + line
);
220 } catch (InterruptedException e
) {
224 int rc
= process
.exitValue();
226 System
.err
.println("stty returned error code: " + rc
);
228 } catch (IOException e
) {
234 * Constructor sets up state for getEvent()
236 * @param input an InputStream connected to the remote user, or null for
237 * System.in. If System.in is used, then on non-Windows systems it will
238 * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
239 * mode. input is always converted to a Reader with UTF-8 encoding.
240 * @param output an OutputStream connected to the remote user, or null
241 * for System.out. output is always converted to a Writer with UTF-8
244 public ECMA48Terminal(InputStream input
, OutputStream output
) throws UnsupportedEncodingException
{
252 this.input
= new InputStreamReader(System
.in
, "UTF-8");
256 this.input
= new InputStreamReader(input
);
258 // TODO: include TelnetSocket from NIB and have it implement
260 if (input
instanceof SessionInfo
) {
261 session
= (SessionInfo
)input
;
263 if (session
== null) {
265 // Reading right off the tty
266 session
= new TTYSessionInfo();
268 session
= new TSessionInfo();
272 if (output
== null) {
273 this.output
= new PrintWriter(new OutputStreamWriter(System
.out
,
276 this.output
= new PrintWriter(new OutputStreamWriter(output
,
280 // Enable mouse reporting and metaSendsEscape
281 this.output
.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
283 // Hang onto the window size
284 windowResize
= new TResizeEvent(TResizeEvent
.Type
.Screen
,
285 session
.getWindowWidth(), session
.getWindowHeight());
289 * Restore terminal to normal state
291 public void shutdown() {
296 // Disable mouse reporting and show cursor
297 output
.printf("%s%s%s", mouse(false), cursor(true), normal());
303 public void flush() {
308 * Reset keyboard/mouse input parser
310 private void reset() {
311 state
= ParseState
.GROUND
;
318 * Produce a control character or one of the special ones (ENTER, TAB,
321 * @param ch Unicode code point
322 * @return one KEYPRESS event, either a control character (e.g. isKey ==
323 * false, ch == 'A', ctrl == true), or a special key (e.g. isKey == true,
326 private TKeypressEvent
controlChar(char ch
) {
327 TKeypressEvent event
= new TKeypressEvent();
329 // System.err.printf("controlChar: %02x\n", ch);
345 // Make all other control characters come back as the alphabetic
346 // character with the ctrl field set. So SOH would be 'A' +
348 event
.key
= new TKeypress(false, 0, (char)(ch
+ 0x40),
356 * Produce special key from CSI Pn ; Pm ; ... ~
358 * @return one KEYPRESS event representing a special key
360 private TInputEvent
csiFnKey() {
363 if (params
.size() > 0) {
364 key
= Integer
.parseInt(params
.get(0));
366 if (params
.size() > 1) {
367 modifier
= Integer
.parseInt(params
.get(1));
369 TKeypressEvent event
= new TKeypressEvent();
427 event
.key
= kbShiftHome
;
430 event
.key
= kbShiftIns
;
433 event
.key
= kbShiftDel
;
436 event
.key
= kbShiftEnd
;
439 event
.key
= kbShiftPgUp
;
442 event
.key
= kbShiftPgDn
;
445 event
.key
= kbShiftF5
;
448 event
.key
= kbShiftF6
;
451 event
.key
= kbShiftF7
;
454 event
.key
= kbShiftF8
;
457 event
.key
= kbShiftF9
;
460 event
.key
= kbShiftF10
;
463 event
.key
= kbShiftF11
;
466 event
.key
= kbShiftF12
;
478 event
.key
= kbAltHome
;
481 event
.key
= kbAltIns
;
484 event
.key
= kbAltDel
;
487 event
.key
= kbAltEnd
;
490 event
.key
= kbAltPgUp
;
493 event
.key
= kbAltPgDn
;
511 event
.key
= kbAltF10
;
514 event
.key
= kbAltF11
;
517 event
.key
= kbAltF12
;
529 event
.key
= kbCtrlHome
;
532 event
.key
= kbCtrlIns
;
535 event
.key
= kbCtrlDel
;
538 event
.key
= kbCtrlEnd
;
541 event
.key
= kbCtrlPgUp
;
544 event
.key
= kbCtrlPgDn
;
547 event
.key
= kbCtrlF5
;
550 event
.key
= kbCtrlF6
;
553 event
.key
= kbCtrlF7
;
556 event
.key
= kbCtrlF8
;
559 event
.key
= kbCtrlF9
;
562 event
.key
= kbCtrlF10
;
565 event
.key
= kbCtrlF11
;
568 event
.key
= kbCtrlF12
;
581 // All OK, return a keypress
586 * Produce mouse events based on "Any event tracking" and UTF-8
588 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
590 * @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event
592 private TInputEvent
parseMouse() {
593 int buttons
= params
.get(0).charAt(0) - 32;
594 int x
= params
.get(0).charAt(1) - 32 - 1;
595 int y
= params
.get(0).charAt(2) - 32 - 1;
597 // Clamp X and Y to the physical screen coordinates.
598 if (x
>= windowResize
.width
) {
599 x
= windowResize
.width
- 1;
601 if (y
>= windowResize
.height
) {
602 y
= windowResize
.height
- 1;
605 TMouseEvent event
= new TMouseEvent(TMouseEvent
.Type
.MOUSE_DOWN
);
611 // System.err.printf("buttons: %04x\r\n", buttons);
628 if (!mouse1
&& !mouse2
&& !mouse3
) {
629 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
631 event
.type
= TMouseEvent
.Type
.MOUSE_UP
;
648 // Dragging with mouse1 down
651 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
655 // Dragging with mouse2 down
658 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
662 // Dragging with mouse3 down
665 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
669 // Dragging with mouse2 down after wheelUp
672 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
676 // Dragging with mouse2 down after wheelDown
679 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
683 event
.mouseWheelUp
= true;
687 event
.mouseWheelDown
= true;
691 // Unknown, just make it motion
692 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
699 * Parses the next character of input to see if an InputEvent is
703 * ch = Unicode code point
704 * noChar = if true, ignore ch. This is currently used to
705 * return a bare ESC and RESIZE events.
708 * list of new events (which may be empty)
710 public List
<TInputEvent
> getEvents(char ch
) {
711 return getEvents(ch
, false);
715 * Parses the next character of input to see if an InputEvent is
719 * ch = Unicode code point
720 * noChar = if true, ignore ch. This is currently used to
721 * return a bare ESC and RESIZE events.
724 * list of new events (which may be empty)
726 public List
<TInputEvent
> getEvents(char ch
, boolean noChar
) {
727 List
<TInputEvent
> events
= new LinkedList
<TInputEvent
>();
729 TKeypressEvent keypress
;
730 Date now
= new Date();
732 // ESCDELAY type timeout
733 if (state
== ParseState
.ESCAPE
) {
734 long escDelay
= now
.getTime() - escapeTime
;
735 if (escDelay
> 250) {
736 // After 0.25 seconds, assume a true escape character
737 events
.add(controlChar((char)0x1B));
742 if (noChar
== true) {
743 int newWidth
= session
.getWindowWidth();
744 int newHeight
= session
.getWindowHeight();
745 if ((newWidth
!= windowResize
.width
) ||
746 (newHeight
!= windowResize
.height
)) {
747 TResizeEvent event
= new TResizeEvent(TResizeEvent
.Type
.Screen
,
748 newWidth
, newHeight
);
749 windowResize
.width
= newWidth
;
750 windowResize
.height
= newHeight
;
754 // Nothing else to do, bail out
758 // System.err.printf("state: %s ch %c\r\n", state, ch);
764 state
= ParseState
.ESCAPE
;
765 escapeTime
= now
.getTime();
771 events
.add(controlChar(ch
));
778 keypress
= new TKeypressEvent();
779 keypress
.key
.isKey
= false;
780 keypress
.key
.ch
= ch
;
781 events
.add(keypress
);
790 // ALT-Control character
791 keypress
= controlChar(ch
);
792 keypress
.key
.alt
= true;
793 events
.add(keypress
);
799 // This will be one of the function keys
800 state
= ParseState
.ESCAPE_INTERMEDIATE
;
804 // '[' goes to CSI_ENTRY
806 state
= ParseState
.CSI_ENTRY
;
810 // Everything else is assumed to be Alt-keystroke
811 keypress
= new TKeypressEvent();
812 keypress
.key
.isKey
= false;
813 keypress
.key
.ch
= ch
;
814 keypress
.key
.alt
= true;
815 if ((ch
>= 'A') && (ch
<= 'Z')) {
816 keypress
.key
.shift
= true;
818 events
.add(keypress
);
822 case ESCAPE_INTERMEDIATE
:
823 if ((ch
>= 'P') && (ch
<= 'S')) {
825 keypress
= new TKeypressEvent();
826 keypress
.key
.isKey
= true;
829 keypress
.key
.fnKey
= TKeypress
.F1
;
832 keypress
.key
.fnKey
= TKeypress
.F2
;
835 keypress
.key
.fnKey
= TKeypress
.F3
;
838 keypress
.key
.fnKey
= TKeypress
.F4
;
843 events
.add(keypress
);
848 // Unknown keystroke, ignore
853 // Numbers - parameter values
854 if ((ch
>= '0') && (ch
<= '9')) {
855 params
.set(paramI
, params
.get(paramI
) + ch
);
856 state
= ParseState
.CSI_PARAM
;
859 // Parameter separator
862 params
.set(paramI
, "");
866 if ((ch
>= 0x30) && (ch
<= 0x7E)) {
870 keypress
= new TKeypressEvent();
871 keypress
.key
.isKey
= true;
872 keypress
.key
.fnKey
= TKeypress
.UP
;
873 if (params
.size() > 1) {
874 if (params
.get(1).equals("2")) {
875 keypress
.key
.shift
= true;
877 if (params
.get(1).equals("5")) {
878 keypress
.key
.ctrl
= true;
880 if (params
.get(1).equals("3")) {
881 keypress
.key
.alt
= true;
884 events
.add(keypress
);
889 keypress
= new TKeypressEvent();
890 keypress
.key
.isKey
= true;
891 keypress
.key
.fnKey
= TKeypress
.DOWN
;
892 if (params
.size() > 1) {
893 if (params
.get(1).equals("2")) {
894 keypress
.key
.shift
= true;
896 if (params
.get(1).equals("5")) {
897 keypress
.key
.ctrl
= true;
899 if (params
.get(1).equals("3")) {
900 keypress
.key
.alt
= true;
903 events
.add(keypress
);
908 keypress
= new TKeypressEvent();
909 keypress
.key
.isKey
= true;
910 keypress
.key
.fnKey
= TKeypress
.RIGHT
;
911 if (params
.size() > 1) {
912 if (params
.get(1).equals("2")) {
913 keypress
.key
.shift
= true;
915 if (params
.get(1).equals("5")) {
916 keypress
.key
.ctrl
= true;
918 if (params
.get(1).equals("3")) {
919 keypress
.key
.alt
= true;
922 events
.add(keypress
);
927 keypress
= new TKeypressEvent();
928 keypress
.key
.isKey
= true;
929 keypress
.key
.fnKey
= TKeypress
.LEFT
;
930 if (params
.size() > 1) {
931 if (params
.get(1).equals("2")) {
932 keypress
.key
.shift
= true;
934 if (params
.get(1).equals("5")) {
935 keypress
.key
.ctrl
= true;
937 if (params
.get(1).equals("3")) {
938 keypress
.key
.alt
= true;
941 events
.add(keypress
);
946 keypress
= new TKeypressEvent();
947 keypress
.key
.isKey
= true;
948 keypress
.key
.fnKey
= TKeypress
.HOME
;
949 events
.add(keypress
);
954 keypress
= new TKeypressEvent();
955 keypress
.key
.isKey
= true;
956 keypress
.key
.fnKey
= TKeypress
.END
;
957 events
.add(keypress
);
961 // CBT - Cursor backward X tab stops (default 1)
962 keypress
= new TKeypressEvent();
963 keypress
.key
.isKey
= true;
964 keypress
.key
.fnKey
= TKeypress
.BTAB
;
965 events
.add(keypress
);
970 state
= ParseState
.MOUSE
;
977 // Unknown keystroke, ignore
982 // Numbers - parameter values
983 if ((ch
>= '0') && (ch
<= '9')) {
984 params
.set(paramI
, params
.get(paramI
) + ch
);
985 state
= ParseState
.CSI_PARAM
;
988 // Parameter separator
991 params
.set(paramI
, "");
996 events
.add(csiFnKey());
1001 if ((ch
>= 0x30) && (ch
<= 0x7E)) {
1005 keypress
= new TKeypressEvent();
1006 keypress
.key
.isKey
= true;
1007 keypress
.key
.fnKey
= TKeypress
.UP
;
1008 if (params
.size() > 1) {
1009 if (params
.get(1).equals("2")) {
1010 keypress
.key
.shift
= true;
1012 if (params
.get(1).equals("5")) {
1013 keypress
.key
.ctrl
= true;
1015 if (params
.get(1).equals("3")) {
1016 keypress
.key
.alt
= true;
1019 events
.add(keypress
);
1024 keypress
= new TKeypressEvent();
1025 keypress
.key
.isKey
= true;
1026 keypress
.key
.fnKey
= TKeypress
.DOWN
;
1027 if (params
.size() > 1) {
1028 if (params
.get(1).equals("2")) {
1029 keypress
.key
.shift
= true;
1031 if (params
.get(1).equals("5")) {
1032 keypress
.key
.ctrl
= true;
1034 if (params
.get(1).equals("3")) {
1035 keypress
.key
.alt
= true;
1038 events
.add(keypress
);
1043 keypress
= new TKeypressEvent();
1044 keypress
.key
.isKey
= true;
1045 keypress
.key
.fnKey
= TKeypress
.RIGHT
;
1046 if (params
.size() > 1) {
1047 if (params
.get(1).equals("2")) {
1048 keypress
.key
.shift
= true;
1050 if (params
.get(1).equals("5")) {
1051 keypress
.key
.ctrl
= true;
1053 if (params
.get(1).equals("3")) {
1054 keypress
.key
.alt
= true;
1057 events
.add(keypress
);
1062 keypress
= new TKeypressEvent();
1063 keypress
.key
.isKey
= true;
1064 keypress
.key
.fnKey
= TKeypress
.LEFT
;
1065 if (params
.size() > 1) {
1066 if (params
.get(1).equals("2")) {
1067 keypress
.key
.shift
= true;
1069 if (params
.get(1).equals("5")) {
1070 keypress
.key
.ctrl
= true;
1072 if (params
.get(1).equals("3")) {
1073 keypress
.key
.alt
= true;
1076 events
.add(keypress
);
1084 // Unknown keystroke, ignore
1089 params
.set(0, params
.get(paramI
) + ch
);
1090 if (params
.get(0).length() == 3) {
1091 // We have enough to generate a mouse event
1092 events
.add(parseMouse());
1101 // This "should" be impossible to reach
1106 * Tell (u)xterm that we want alt- keystrokes to send escape +
1107 * character rather than set the 8th bit. Anyone who wants UTF8
1108 * should want this enabled.
1111 * on = if true, enable metaSendsEscape
1114 * the string to emit to xterm
1116 static public String
xtermMetaSendsEscape(boolean on
) {
1118 return "\033[?1036h\033[?1034l";
1120 return "\033[?1036l";
1124 * Convert a list of SGR parameters into a full escape sequence.
1125 * This also eliminates a trailing ';' which would otherwise reset
1126 * everything to white-on-black not-bold.
1129 * str = string of parameters, e.g. "31;1;"
1132 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[31;1m"
1134 static public String
addHeaderSGR(String str
) {
1135 if (str
.length() > 0) {
1136 // Nix any trailing ';' because that resets all attributes
1137 while (str
.endsWith(":")) {
1138 str
= str
.substring(0, str
.length() - 1);
1141 return "\033[" + str
+ "m";
1145 * Create a SGR parameter sequence for a single color change.
1148 * color = one of the Color.WHITE, Color.BLUE, etc. constants
1149 * foreground = if true, this is a foreground color
1152 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[42m"
1154 static public String
color(Color color
, boolean foreground
) {
1155 return color(color
, foreground
, true);
1159 * Create a SGR parameter sequence for a single color change.
1162 * color = one of the Color.WHITE, Color.BLUE, etc. constants
1163 * foreground = if true, this is a foreground color
1164 * header = if true, make the full header, otherwise just emit
1165 * the color parameter e.g. "42;"
1168 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[42m"
1170 static public String
color(Color color
, boolean foreground
,
1173 int ecmaColor
= color
.value
;
1175 // Convert Color.* values to SGR numerics
1176 if (foreground
== true) {
1183 return String
.format("\033[%dm", ecmaColor
);
1185 return String
.format("%d;", ecmaColor
);
1190 * Create a SGR parameter sequence for both foreground and
1191 * background color change.
1194 * foreColor = one of the Color.WHITE, Color.BLUE, etc. constants
1195 * backColor = one of the Color.WHITE, Color.BLUE, etc. constants
1198 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[31;42m"
1200 static public String
color(Color foreColor
, Color backColor
) {
1201 return color(foreColor
, backColor
, true);
1205 * Create a SGR parameter sequence for both foreground and
1206 * background color change.
1209 * foreColor = one of the Color.WHITE, Color.BLUE, etc. constants
1210 * backColor = one of the Color.WHITE, Color.BLUE, etc. constants
1211 * header = if true, make the full header, otherwise just emit
1212 * the color parameter e.g. "31;42;"
1215 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[31;42m"
1217 static public String
color(Color foreColor
, Color backColor
,
1220 int ecmaForeColor
= foreColor
.value
;
1221 int ecmaBackColor
= backColor
.value
;
1223 // Convert Color.* values to SGR numerics
1224 ecmaBackColor
+= 40;
1225 ecmaForeColor
+= 30;
1228 return String
.format("\033[%d;%dm", ecmaForeColor
, ecmaBackColor
);
1230 return String
.format("%d;%d;", ecmaForeColor
, ecmaBackColor
);
1235 * Create a SGR parameter sequence for foreground, background, and
1236 * several attributes. This sequence first resets all attributes
1237 * to default, then sets attributes as per the parameters.
1240 * foreColor = one of the Color.WHITE, Color.BLUE, etc. constants
1241 * backColor = one of the Color.WHITE, Color.BLUE, etc. constants
1242 * bold = if true, set bold
1243 * reverse = if true, set reverse
1244 * blink = if true, set blink
1245 * underline = if true, set underline
1248 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[0;1;31;42m"
1250 static public String
color(Color foreColor
, Color backColor
, boolean bold
,
1251 boolean reverse
, boolean blink
, boolean underline
) {
1253 int ecmaForeColor
= foreColor
.value
;
1254 int ecmaBackColor
= backColor
.value
;
1256 // Convert Color.* values to SGR numerics
1257 ecmaBackColor
+= 40;
1258 ecmaForeColor
+= 30;
1260 StringBuilder sb
= new StringBuilder();
1261 if ( bold
&& reverse
&& blink
&& !underline
) {
1262 sb
.append("\033[0;1;7;5;");
1263 } else if ( bold
&& reverse
&& !blink
&& !underline
) {
1264 sb
.append("\033[0;1;7;");
1265 } else if ( !bold
&& reverse
&& blink
&& !underline
) {
1266 sb
.append("\033[0;7;5;");
1267 } else if ( bold
&& !reverse
&& blink
&& !underline
) {
1268 sb
.append("\033[0;1;5;");
1269 } else if ( bold
&& !reverse
&& !blink
&& !underline
) {
1270 sb
.append("\033[0;1;");
1271 } else if ( !bold
&& reverse
&& !blink
&& !underline
) {
1272 sb
.append("\033[0;7;");
1273 } else if ( !bold
&& !reverse
&& blink
&& !underline
) {
1274 sb
.append("\033[0;5;");
1275 } else if ( bold
&& reverse
&& blink
&& underline
) {
1276 sb
.append("\033[0;1;7;5;4;");
1277 } else if ( bold
&& reverse
&& !blink
&& underline
) {
1278 sb
.append("\033[0;1;7;4;");
1279 } else if ( !bold
&& reverse
&& blink
&& underline
) {
1280 sb
.append("\033[0;7;5;4;");
1281 } else if ( bold
&& !reverse
&& blink
&& underline
) {
1282 sb
.append("\033[0;1;5;4;");
1283 } else if ( bold
&& !reverse
&& !blink
&& underline
) {
1284 sb
.append("\033[0;1;4;");
1285 } else if ( !bold
&& reverse
&& !blink
&& underline
) {
1286 sb
.append("\033[0;7;4;");
1287 } else if ( !bold
&& !reverse
&& blink
&& underline
) {
1288 sb
.append("\033[0;5;4;");
1289 } else if ( !bold
&& !reverse
&& !blink
&& underline
) {
1290 sb
.append("\033[0;4;");
1292 assert(!bold
&& !reverse
&& !blink
&& !underline
);
1293 sb
.append("\033[0;");
1295 sb
.append(String
.format("%d;%dm", ecmaForeColor
, ecmaBackColor
));
1296 return sb
.toString();
1300 * Create a SGR parameter sequence for enabling reverse color.
1303 * on = if true, turn on reverse
1306 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[7m"
1308 static public String
reverse(boolean on
) {
1316 * Create a SGR parameter sequence to reset to defaults.
1319 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[0m"
1321 static public String
normal() {
1322 return normal(true);
1326 * Create a SGR parameter sequence to reset to defaults.
1329 * header = if true, make the full header, otherwise just emit
1330 * the bare parameter e.g. "0;"
1333 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[0m"
1335 static public String
normal(boolean header
) {
1337 return "\033[0;37;40m";
1343 * Create a SGR parameter sequence for enabling boldface.
1346 * on = if true, turn on bold
1349 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[1m"
1351 static public String
bold(boolean on
) {
1352 return bold(on
, true);
1356 * Create a SGR parameter sequence for enabling boldface.
1359 * on = if true, turn on bold
1360 * header = if true, make the full header, otherwise just emit
1361 * the bare parameter e.g. "1;"
1364 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[1m"
1366 static public String
bold(boolean on
, boolean header
) {
1380 * Create a SGR parameter sequence for enabling blinking text.
1383 * on = if true, turn on blink
1386 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[5m"
1388 static public String
blink(boolean on
) {
1389 return blink(on
, true);
1393 * Create a SGR parameter sequence for enabling blinking text.
1396 * on = if true, turn on blink
1397 * header = if true, make the full header, otherwise just emit
1398 * the bare parameter e.g. "5;"
1401 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[5m"
1403 static public String
blink(boolean on
, boolean header
) {
1417 * Create a SGR parameter sequence for enabling underline /
1421 * on = if true, turn on underline
1424 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[4m"
1426 static public String
underline(boolean on
) {
1434 * Create a SGR parameter sequence for enabling the visible cursor.
1437 * on = if true, turn on cursor
1440 * the string to emit to an ANSI / ECMA-style terminal
1442 public String
cursor(boolean on
) {
1443 if (on
&& (cursorOn
== false)) {
1447 if (!on
&& (cursorOn
== true)) {
1455 * Clear the entire screen. Because some terminals use back-color-erase,
1456 * set the color to white-on-black beforehand.
1459 * the string to emit to an ANSI / ECMA-style terminal
1461 static public String
clearAll() {
1462 return "\033[0;37;40m\033[2J";
1466 * Clear the line from the cursor (inclusive) to the end of the screen.
1467 * Because some terminals use back-color-erase, set the color to
1468 * white-on-black beforehand.
1471 * the string to emit to an ANSI / ECMA-style terminal
1473 static public String
clearRemainingLine() {
1474 return "\033[0;37;40m\033[K";
1478 * Clear the line up the cursor (inclusive). Because some terminals use
1479 * back-color-erase, set the color to white-on-black beforehand.
1482 * the string to emit to an ANSI / ECMA-style terminal
1484 static public String
clearPreceedingLine() {
1485 return "\033[0;37;40m\033[1K";
1489 * Clear the line. Because some terminals use back-color-erase, set the
1490 * color to white-on-black beforehand.
1493 * the string to emit to an ANSI / ECMA-style terminal
1495 static public String
clearLine() {
1496 return "\033[0;37;40m\033[2K";
1500 * Move the cursor to the top-left corner.
1503 * the string to emit to an ANSI / ECMA-style terminal
1505 static public String
home() {
1510 * Move the cursor to (x, y).
1513 * x = column coordinate. 0 is the left-most column.
1514 * y = row coordinate. 0 is the top-most row.
1517 * the string to emit to an ANSI / ECMA-style terminal
1519 static public String
gotoXY(int x
, int y
) {
1520 return String
.format("\033[%d;%dH", y
+ 1, x
+ 1);
1524 * Tell (u)xterm that we want to receive mouse events based on
1525 * "Any event tracking" and UTF-8 coordinates. See
1526 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
1528 * Finally, this sets the alternate screen buffer.
1531 * on = if true, enable mouse report
1534 * the string to emit to xterm
1536 static public String
mouse(boolean on
) {
1538 return "\033[?1003;1005h\033[?1049h";
1540 return "\033[?1003;1005l\033[?1049l";