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 * Call 'stty cooked' to set cooked mode.
165 private void sttyCooked() {
170 * Call 'stty raw' to set raw mode.
172 private void sttyRaw() {
177 * Call 'stty' to set raw or cooked mode.
179 * @param mode if true, set raw mode, otherwise set cooked mode
181 private void doStty(boolean mode
) {
183 "/bin/sh", "-c", "stty raw < /dev/tty"
185 String
[] cmdCooked
= {
186 "/bin/sh", "-c", "stty cooked < /dev/tty"
189 System
.out
.println("spawn stty");
193 process
= Runtime
.getRuntime().exec(cmdRaw
);
195 process
= Runtime
.getRuntime().exec(cmdCooked
);
197 BufferedReader in
= new BufferedReader(new InputStreamReader(process
.getInputStream(), "UTF-8"));
198 String line
= in
.readLine();
199 if ((line
!= null) && (line
.length() > 0)) {
200 System
.err
.println("WEIRD?! Normal output from stty: " + line
);
203 BufferedReader err
= new BufferedReader(new InputStreamReader(process
.getErrorStream(), "UTF-8"));
204 line
= err
.readLine();
205 if ((line
!= null) && (line
.length() > 0)) {
206 System
.err
.println("Error output from stty: " + line
);
211 } catch (InterruptedException e
) {
215 int rc
= process
.exitValue();
217 System
.err
.println("stty returned error code: " + rc
);
219 } catch (IOException e
) {
225 * Constructor sets up state for getEvent()
227 * @param input an InputStream connected to the remote user, or null for
228 * System.in. If System.in is used, then on non-Windows systems it will
229 * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
230 * mode. input is always converted to a Reader with UTF-8 encoding.
231 * @param output an OutputStream connected to the remote user, or null
232 * for System.out. output is always converted to a Writer with UTF-8
235 public ECMA48Terminal(InputStream input
, OutputStream output
) throws UnsupportedEncodingException
{
243 this.input
= new InputStreamReader(System
.in
, "UTF-8");
247 this.input
= new InputStreamReader(input
);
249 // TODO: include TelnetSocket from NIB and have it implement
251 if (input
instanceof SessionInfo
) {
252 session
= (SessionInfo
)input
;
254 if (session
== null) {
256 // Reading right off the tty
257 session
= new TTYSessionInfo();
259 session
= new TSessionInfo();
263 if (output
== null) {
264 this.output
= new PrintWriter(new OutputStreamWriter(System
.out
,
267 this.output
= new PrintWriter(new OutputStreamWriter(output
,
271 // Enable mouse reporting and metaSendsEscape
272 this.output
.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
274 // Hang onto the window size
275 windowResize
= new TResizeEvent(TResizeEvent
.Type
.Screen
,
276 session
.getWindowWidth(), session
.getWindowHeight());
280 * Restore terminal to normal state
282 public void shutdown() {
287 // Disable mouse reporting and show cursor
288 output
.printf("%s%s%s", mouse(false), cursor(true), normal());
294 public void flush() {
299 * Reset keyboard/mouse input parser
301 private void reset() {
302 state
= ParseState
.GROUND
;
309 * Produce a control character or one of the special ones (ENTER, TAB,
312 * @param ch Unicode code point
313 * @return one KEYPRESS event, either a control character (e.g. isKey ==
314 * false, ch == 'A', ctrl == true), or a special key (e.g. isKey == true,
317 private TKeypressEvent
controlChar(char ch
) {
318 TKeypressEvent event
= new TKeypressEvent();
320 // System.err.printf("controlChar: %02x\n", ch);
336 // Make all other control characters come back as the alphabetic
337 // character with the ctrl field set. So SOH would be 'A' +
339 event
.key
= new TKeypress(false, 0, (char)(ch
+ 0x40),
347 * Produce special key from CSI Pn ; Pm ; ... ~
349 * @return one KEYPRESS event representing a special key
351 private TInputEvent
csiFnKey() {
354 if (params
.size() > 0) {
355 key
= Integer
.parseInt(params
.get(0));
357 if (params
.size() > 1) {
358 modifier
= Integer
.parseInt(params
.get(1));
360 TKeypressEvent event
= new TKeypressEvent();
418 event
.key
= kbShiftHome
;
421 event
.key
= kbShiftIns
;
424 event
.key
= kbShiftDel
;
427 event
.key
= kbShiftEnd
;
430 event
.key
= kbShiftPgUp
;
433 event
.key
= kbShiftPgDn
;
436 event
.key
= kbShiftF5
;
439 event
.key
= kbShiftF6
;
442 event
.key
= kbShiftF7
;
445 event
.key
= kbShiftF8
;
448 event
.key
= kbShiftF9
;
451 event
.key
= kbShiftF10
;
454 event
.key
= kbShiftF11
;
457 event
.key
= kbShiftF12
;
469 event
.key
= kbAltHome
;
472 event
.key
= kbAltIns
;
475 event
.key
= kbAltDel
;
478 event
.key
= kbAltEnd
;
481 event
.key
= kbAltPgUp
;
484 event
.key
= kbAltPgDn
;
502 event
.key
= kbAltF10
;
505 event
.key
= kbAltF11
;
508 event
.key
= kbAltF12
;
520 event
.key
= kbCtrlHome
;
523 event
.key
= kbCtrlIns
;
526 event
.key
= kbCtrlDel
;
529 event
.key
= kbCtrlEnd
;
532 event
.key
= kbCtrlPgUp
;
535 event
.key
= kbCtrlPgDn
;
538 event
.key
= kbCtrlF5
;
541 event
.key
= kbCtrlF6
;
544 event
.key
= kbCtrlF7
;
547 event
.key
= kbCtrlF8
;
550 event
.key
= kbCtrlF9
;
553 event
.key
= kbCtrlF10
;
556 event
.key
= kbCtrlF11
;
559 event
.key
= kbCtrlF12
;
572 // All OK, return a keypress
577 * Produce mouse events based on "Any event tracking" and UTF-8
579 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
581 * @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event
583 private TInputEvent
parseMouse() {
584 int buttons
= params
.get(0).charAt(0) - 32;
585 int x
= params
.get(0).charAt(1) - 32 - 1;
586 int y
= params
.get(0).charAt(2) - 32 - 1;
588 // Clamp X and Y to the physical screen coordinates.
589 if (x
>= windowResize
.width
) {
590 x
= windowResize
.width
- 1;
592 if (y
>= windowResize
.height
) {
593 y
= windowResize
.height
- 1;
596 TMouseEvent event
= new TMouseEvent(TMouseEvent
.Type
.MOUSE_DOWN
);
602 // System.err.printf("buttons: %04x\r\n", buttons);
619 if (!mouse1
&& !mouse2
&& !mouse3
) {
620 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
622 event
.type
= TMouseEvent
.Type
.MOUSE_UP
;
639 // Dragging with mouse1 down
642 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
646 // Dragging with mouse2 down
649 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
653 // Dragging with mouse3 down
656 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
660 // Dragging with mouse2 down after wheelUp
663 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
667 // Dragging with mouse2 down after wheelDown
670 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
674 event
.mouseWheelUp
= true;
678 event
.mouseWheelDown
= true;
682 // Unknown, just make it motion
683 event
.type
= TMouseEvent
.Type
.MOUSE_MOTION
;
690 * Parses the next character of input to see if an InputEvent is
694 * ch = Unicode code point
695 * noChar = if true, ignore ch. This is currently used to
696 * return a bare ESC and RESIZE events.
699 * list of new events (which may be empty)
701 public List
<TInputEvent
> getEvents(char ch
) {
702 return getEvents(ch
, false);
706 * Parses the next character of input to see if an InputEvent is
710 * ch = Unicode code point
711 * noChar = if true, ignore ch. This is currently used to
712 * return a bare ESC and RESIZE events.
715 * list of new events (which may be empty)
717 public List
<TInputEvent
> getEvents(char ch
, boolean noChar
) {
718 List
<TInputEvent
> events
= new LinkedList
<TInputEvent
>();
720 TKeypressEvent keypress
;
721 Date now
= new Date();
723 // ESCDELAY type timeout
724 if (state
== ParseState
.ESCAPE
) {
725 long escDelay
= now
.getTime() - escapeTime
;
726 if (escDelay
> 250) {
727 // After 0.25 seconds, assume a true escape character
728 events
.add(controlChar((char)0x1B));
733 if (noChar
== true) {
734 int newWidth
= session
.getWindowWidth();
735 int newHeight
= session
.getWindowHeight();
736 if ((newWidth
!= windowResize
.width
) ||
737 (newHeight
!= windowResize
.height
)) {
738 TResizeEvent event
= new TResizeEvent(TResizeEvent
.Type
.Screen
,
739 newWidth
, newHeight
);
740 windowResize
.width
= newWidth
;
741 windowResize
.height
= newHeight
;
745 // Nothing else to do, bail out
749 // System.err.printf("state: %s ch %c\r\n", state, ch);
755 state
= ParseState
.ESCAPE
;
756 escapeTime
= now
.getTime();
762 events
.add(controlChar(ch
));
769 keypress
= new TKeypressEvent();
770 keypress
.key
.isKey
= false;
771 keypress
.key
.ch
= ch
;
772 events
.add(keypress
);
781 // ALT-Control character
782 keypress
= controlChar(ch
);
783 keypress
.key
.alt
= true;
784 events
.add(keypress
);
790 // This will be one of the function keys
791 state
= ParseState
.ESCAPE_INTERMEDIATE
;
795 // '[' goes to CSI_ENTRY
797 state
= ParseState
.CSI_ENTRY
;
801 // Everything else is assumed to be Alt-keystroke
802 keypress
= new TKeypressEvent();
803 keypress
.key
.isKey
= false;
804 keypress
.key
.ch
= ch
;
805 keypress
.key
.alt
= true;
806 if ((ch
>= 'A') && (ch
<= 'Z')) {
807 keypress
.key
.shift
= true;
809 events
.add(keypress
);
813 case ESCAPE_INTERMEDIATE
:
814 if ((ch
>= 'P') && (ch
<= 'S')) {
816 keypress
= new TKeypressEvent();
817 keypress
.key
.isKey
= true;
820 keypress
.key
.fnKey
= TKeypress
.F1
;
823 keypress
.key
.fnKey
= TKeypress
.F2
;
826 keypress
.key
.fnKey
= TKeypress
.F3
;
829 keypress
.key
.fnKey
= TKeypress
.F4
;
834 events
.add(keypress
);
839 // Unknown keystroke, ignore
844 // Numbers - parameter values
845 if ((ch
>= '0') && (ch
<= '9')) {
846 params
.set(paramI
, params
.get(paramI
) + ch
);
847 state
= ParseState
.CSI_PARAM
;
850 // Parameter separator
853 params
.set(paramI
, "");
857 if ((ch
>= 0x30) && (ch
<= 0x7E)) {
861 keypress
= new TKeypressEvent();
862 keypress
.key
.isKey
= true;
863 keypress
.key
.fnKey
= TKeypress
.UP
;
864 if (params
.size() > 1) {
865 if (params
.get(1).equals("2")) {
866 keypress
.key
.shift
= true;
868 if (params
.get(1).equals("5")) {
869 keypress
.key
.ctrl
= true;
871 if (params
.get(1).equals("3")) {
872 keypress
.key
.alt
= true;
875 events
.add(keypress
);
880 keypress
= new TKeypressEvent();
881 keypress
.key
.isKey
= true;
882 keypress
.key
.fnKey
= TKeypress
.DOWN
;
883 if (params
.size() > 1) {
884 if (params
.get(1).equals("2")) {
885 keypress
.key
.shift
= true;
887 if (params
.get(1).equals("5")) {
888 keypress
.key
.ctrl
= true;
890 if (params
.get(1).equals("3")) {
891 keypress
.key
.alt
= true;
894 events
.add(keypress
);
899 keypress
= new TKeypressEvent();
900 keypress
.key
.isKey
= true;
901 keypress
.key
.fnKey
= TKeypress
.RIGHT
;
902 if (params
.size() > 1) {
903 if (params
.get(1).equals("2")) {
904 keypress
.key
.shift
= true;
906 if (params
.get(1).equals("5")) {
907 keypress
.key
.ctrl
= true;
909 if (params
.get(1).equals("3")) {
910 keypress
.key
.alt
= true;
913 events
.add(keypress
);
918 keypress
= new TKeypressEvent();
919 keypress
.key
.isKey
= true;
920 keypress
.key
.fnKey
= TKeypress
.LEFT
;
921 if (params
.size() > 1) {
922 if (params
.get(1).equals("2")) {
923 keypress
.key
.shift
= true;
925 if (params
.get(1).equals("5")) {
926 keypress
.key
.ctrl
= true;
928 if (params
.get(1).equals("3")) {
929 keypress
.key
.alt
= true;
932 events
.add(keypress
);
937 keypress
= new TKeypressEvent();
938 keypress
.key
.isKey
= true;
939 keypress
.key
.fnKey
= TKeypress
.HOME
;
940 events
.add(keypress
);
945 keypress
= new TKeypressEvent();
946 keypress
.key
.isKey
= true;
947 keypress
.key
.fnKey
= TKeypress
.END
;
948 events
.add(keypress
);
952 // CBT - Cursor backward X tab stops (default 1)
953 keypress
= new TKeypressEvent();
954 keypress
.key
.isKey
= true;
955 keypress
.key
.fnKey
= TKeypress
.BTAB
;
956 events
.add(keypress
);
961 state
= ParseState
.MOUSE
;
968 // Unknown keystroke, ignore
973 // Numbers - parameter values
974 if ((ch
>= '0') && (ch
<= '9')) {
975 params
.set(paramI
, params
.get(paramI
) + ch
);
976 state
= ParseState
.CSI_PARAM
;
979 // Parameter separator
982 params
.set(paramI
, "");
987 events
.add(csiFnKey());
992 if ((ch
>= 0x30) && (ch
<= 0x7E)) {
996 keypress
= new TKeypressEvent();
997 keypress
.key
.isKey
= true;
998 keypress
.key
.fnKey
= TKeypress
.UP
;
999 if (params
.size() > 1) {
1000 if (params
.get(1).equals("2")) {
1001 keypress
.key
.shift
= true;
1003 if (params
.get(1).equals("5")) {
1004 keypress
.key
.ctrl
= true;
1006 if (params
.get(1).equals("3")) {
1007 keypress
.key
.alt
= true;
1010 events
.add(keypress
);
1015 keypress
= new TKeypressEvent();
1016 keypress
.key
.isKey
= true;
1017 keypress
.key
.fnKey
= TKeypress
.DOWN
;
1018 if (params
.size() > 1) {
1019 if (params
.get(1).equals("2")) {
1020 keypress
.key
.shift
= true;
1022 if (params
.get(1).equals("5")) {
1023 keypress
.key
.ctrl
= true;
1025 if (params
.get(1).equals("3")) {
1026 keypress
.key
.alt
= true;
1029 events
.add(keypress
);
1034 keypress
= new TKeypressEvent();
1035 keypress
.key
.isKey
= true;
1036 keypress
.key
.fnKey
= TKeypress
.RIGHT
;
1037 if (params
.size() > 1) {
1038 if (params
.get(1).equals("2")) {
1039 keypress
.key
.shift
= true;
1041 if (params
.get(1).equals("5")) {
1042 keypress
.key
.ctrl
= true;
1044 if (params
.get(1).equals("3")) {
1045 keypress
.key
.alt
= true;
1048 events
.add(keypress
);
1053 keypress
= new TKeypressEvent();
1054 keypress
.key
.isKey
= true;
1055 keypress
.key
.fnKey
= TKeypress
.LEFT
;
1056 if (params
.size() > 1) {
1057 if (params
.get(1).equals("2")) {
1058 keypress
.key
.shift
= true;
1060 if (params
.get(1).equals("5")) {
1061 keypress
.key
.ctrl
= true;
1063 if (params
.get(1).equals("3")) {
1064 keypress
.key
.alt
= true;
1067 events
.add(keypress
);
1075 // Unknown keystroke, ignore
1080 params
.set(0, params
.get(paramI
) + ch
);
1081 if (params
.get(0).length() == 3) {
1082 // We have enough to generate a mouse event
1083 events
.add(parseMouse());
1092 // This "should" be impossible to reach
1097 * Tell (u)xterm that we want alt- keystrokes to send escape +
1098 * character rather than set the 8th bit. Anyone who wants UTF8
1099 * should want this enabled.
1102 * on = if true, enable metaSendsEscape
1105 * the string to emit to xterm
1107 static public String
xtermMetaSendsEscape(boolean on
) {
1109 return "\033[?1036h\033[?1034l";
1111 return "\033[?1036l";
1115 * Convert a list of SGR parameters into a full escape sequence.
1116 * This also eliminates a trailing ';' which would otherwise reset
1117 * everything to white-on-black not-bold.
1120 * str = string of parameters, e.g. "31;1;"
1123 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[31;1m"
1125 static public String
addHeaderSGR(String str
) {
1126 if (str
.length() > 0) {
1127 // Nix any trailing ';' because that resets all attributes
1128 while (str
.endsWith(":")) {
1129 str
= str
.substring(0, str
.length() - 1);
1132 return "\033[" + str
+ "m";
1136 * Create a SGR parameter sequence for a single color change.
1139 * color = one of the Color.WHITE, Color.BLUE, etc. constants
1140 * foreground = if true, this is a foreground color
1143 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[42m"
1145 static public String
color(Color color
, boolean foreground
) {
1146 return color(color
, foreground
, true);
1150 * Create a SGR parameter sequence for a single color change.
1153 * color = one of the Color.WHITE, Color.BLUE, etc. constants
1154 * foreground = if true, this is a foreground color
1155 * header = if true, make the full header, otherwise just emit
1156 * the color parameter e.g. "42;"
1159 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[42m"
1161 static public String
color(Color color
, boolean foreground
,
1164 int ecmaColor
= color
.value
;
1166 // Convert Color.* values to SGR numerics
1167 if (foreground
== true) {
1174 return String
.format("\033[%dm", ecmaColor
);
1176 return String
.format("%d;", ecmaColor
);
1181 * Create a SGR parameter sequence for both foreground and
1182 * background color change.
1185 * foreColor = one of the Color.WHITE, Color.BLUE, etc. constants
1186 * backColor = one of the Color.WHITE, Color.BLUE, etc. constants
1189 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[31;42m"
1191 static public String
color(Color foreColor
, Color backColor
) {
1192 return color(foreColor
, backColor
, true);
1196 * Create a SGR parameter sequence for both foreground and
1197 * background color change.
1200 * foreColor = one of the Color.WHITE, Color.BLUE, etc. constants
1201 * backColor = one of the Color.WHITE, Color.BLUE, etc. constants
1202 * header = if true, make the full header, otherwise just emit
1203 * the color parameter e.g. "31;42;"
1206 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[31;42m"
1208 static public String
color(Color foreColor
, Color backColor
,
1211 int ecmaForeColor
= foreColor
.value
;
1212 int ecmaBackColor
= backColor
.value
;
1214 // Convert Color.* values to SGR numerics
1215 ecmaBackColor
+= 40;
1216 ecmaForeColor
+= 30;
1219 return String
.format("\033[%d;%dm", ecmaForeColor
, ecmaBackColor
);
1221 return String
.format("%d;%d;", ecmaForeColor
, ecmaBackColor
);
1226 * Create a SGR parameter sequence for foreground, background, and
1227 * several attributes. This sequence first resets all attributes
1228 * to default, then sets attributes as per the parameters.
1231 * foreColor = one of the Color.WHITE, Color.BLUE, etc. constants
1232 * backColor = one of the Color.WHITE, Color.BLUE, etc. constants
1233 * bold = if true, set bold
1234 * reverse = if true, set reverse
1235 * blink = if true, set blink
1236 * underline = if true, set underline
1239 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[0;1;31;42m"
1241 static public String
color(Color foreColor
, Color backColor
, boolean bold
,
1242 boolean reverse
, boolean blink
, boolean underline
) {
1244 int ecmaForeColor
= foreColor
.value
;
1245 int ecmaBackColor
= backColor
.value
;
1247 // Convert Color.* values to SGR numerics
1248 ecmaBackColor
+= 40;
1249 ecmaForeColor
+= 30;
1251 StringBuilder sb
= new StringBuilder();
1252 if ( bold
&& reverse
&& blink
&& !underline
) {
1253 sb
.append("\033[0;1;7;5;");
1254 } else if ( bold
&& reverse
&& !blink
&& !underline
) {
1255 sb
.append("\033[0;1;7;");
1256 } else if ( !bold
&& reverse
&& blink
&& !underline
) {
1257 sb
.append("\033[0;7;5;");
1258 } else if ( bold
&& !reverse
&& blink
&& !underline
) {
1259 sb
.append("\033[0;1;5;");
1260 } else if ( bold
&& !reverse
&& !blink
&& !underline
) {
1261 sb
.append("\033[0;1;");
1262 } else if ( !bold
&& reverse
&& !blink
&& !underline
) {
1263 sb
.append("\033[0;7;");
1264 } else if ( !bold
&& !reverse
&& blink
&& !underline
) {
1265 sb
.append("\033[0;5;");
1266 } else if ( bold
&& reverse
&& blink
&& underline
) {
1267 sb
.append("\033[0;1;7;5;4;");
1268 } else if ( bold
&& reverse
&& !blink
&& underline
) {
1269 sb
.append("\033[0;1;7;4;");
1270 } else if ( !bold
&& reverse
&& blink
&& underline
) {
1271 sb
.append("\033[0;7;5;4;");
1272 } else if ( bold
&& !reverse
&& blink
&& underline
) {
1273 sb
.append("\033[0;1;5;4;");
1274 } else if ( bold
&& !reverse
&& !blink
&& underline
) {
1275 sb
.append("\033[0;1;4;");
1276 } else if ( !bold
&& reverse
&& !blink
&& underline
) {
1277 sb
.append("\033[0;7;4;");
1278 } else if ( !bold
&& !reverse
&& blink
&& underline
) {
1279 sb
.append("\033[0;5;4;");
1280 } else if ( !bold
&& !reverse
&& !blink
&& underline
) {
1281 sb
.append("\033[0;4;");
1283 assert(!bold
&& !reverse
&& !blink
&& !underline
);
1284 sb
.append("\033[0;");
1286 sb
.append(String
.format("%d;%dm", ecmaForeColor
, ecmaBackColor
));
1287 return sb
.toString();
1291 * Create a SGR parameter sequence for enabling reverse color.
1294 * on = if true, turn on reverse
1297 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[7m"
1299 static public String
reverse(boolean on
) {
1307 * Create a SGR parameter sequence to reset to defaults.
1310 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[0m"
1312 static public String
normal() {
1313 return normal(true);
1317 * Create a SGR parameter sequence to reset to defaults.
1320 * header = if true, make the full header, otherwise just emit
1321 * the bare parameter e.g. "0;"
1324 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[0m"
1326 static public String
normal(boolean header
) {
1328 return "\033[0;37;40m";
1334 * Create a SGR parameter sequence for enabling boldface.
1337 * on = if true, turn on bold
1340 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[1m"
1342 static public String
bold(boolean on
) {
1343 return bold(on
, true);
1347 * Create a SGR parameter sequence for enabling boldface.
1350 * on = if true, turn on bold
1351 * header = if true, make the full header, otherwise just emit
1352 * the bare parameter e.g. "1;"
1355 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[1m"
1357 static public String
bold(boolean on
, boolean header
) {
1371 * Create a SGR parameter sequence for enabling blinking text.
1374 * on = if true, turn on blink
1377 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[5m"
1379 static public String
blink(boolean on
) {
1380 return blink(on
, true);
1384 * Create a SGR parameter sequence for enabling blinking text.
1387 * on = if true, turn on blink
1388 * header = if true, make the full header, otherwise just emit
1389 * the bare parameter e.g. "5;"
1392 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[5m"
1394 static public String
blink(boolean on
, boolean header
) {
1408 * Create a SGR parameter sequence for enabling underline /
1412 * on = if true, turn on underline
1415 * the string to emit to an ANSI / ECMA-style terminal, e.g. "\033[4m"
1417 static public String
underline(boolean on
) {
1425 * Create a SGR parameter sequence for enabling the visible cursor.
1428 * on = if true, turn on cursor
1431 * the string to emit to an ANSI / ECMA-style terminal
1433 public String
cursor(boolean on
) {
1434 if (on
&& (cursorOn
== false)) {
1438 if (!on
&& (cursorOn
== true)) {
1446 * Clear the entire screen. Because some terminals use back-color-erase,
1447 * set the color to white-on-black beforehand.
1450 * the string to emit to an ANSI / ECMA-style terminal
1452 static public String
clearAll() {
1453 return "\033[0;37;40m\033[2J";
1457 * Clear the line from the cursor (inclusive) to the end of the screen.
1458 * Because some terminals use back-color-erase, set the color to
1459 * white-on-black beforehand.
1462 * the string to emit to an ANSI / ECMA-style terminal
1464 static public String
clearRemainingLine() {
1465 return "\033[0;37;40m\033[K";
1469 * Clear the line up the cursor (inclusive). Because some terminals use
1470 * back-color-erase, set the color to white-on-black beforehand.
1473 * the string to emit to an ANSI / ECMA-style terminal
1475 static public String
clearPreceedingLine() {
1476 return "\033[0;37;40m\033[1K";
1480 * Clear the line. Because some terminals use back-color-erase, set the
1481 * color to white-on-black beforehand.
1484 * the string to emit to an ANSI / ECMA-style terminal
1486 static public String
clearLine() {
1487 return "\033[0;37;40m\033[2K";
1491 * Move the cursor to the top-left corner.
1494 * the string to emit to an ANSI / ECMA-style terminal
1496 static public String
home() {
1501 * Move the cursor to (x, y).
1504 * x = column coordinate. 0 is the left-most column.
1505 * y = row coordinate. 0 is the top-most row.
1508 * the string to emit to an ANSI / ECMA-style terminal
1510 static public String
gotoXY(int x
, int y
) {
1511 return String
.format("\033[%d;%dH", y
+ 1, x
+ 1);
1515 * Tell (u)xterm that we want to receive mouse events based on
1516 * "Any event tracking" and UTF-8 coordinates. See
1517 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
1519 * Finally, this sets the alternate screen buffer.
1522 * on = if true, enable mouse report
1525 * the string to emit to xterm
1527 static public String
mouse(boolean on
) {
1529 return "\033[?1003;1005h\033[?1049h";
1531 return "\033[?1003;1005l\033[?1049l";