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 * Return any events in the IO queue.
701 * @return list of new events (which may be empty)
703 public List
<TInputEvent
> getEvents() {
704 List
<TInputEvent
> events
= new LinkedList
<TInputEvent
>();
709 * Parses the next character of input to see if an InputEvent is fully
712 * @param ch Unicode code point
713 * @return list of new events (which may be empty)
715 public List
<TInputEvent
> getEvents(char ch
) {
716 return getEvents(ch
, false);
720 * Parses the next character of input to see if an InputEvent is
723 * @param ch Unicode code point
724 * @param noChar if true, ignore ch. This is currently used to return a
725 * bare ESC and RESIZE events.
726 * @return list of new events (which may be empty)
728 public List
<TInputEvent
> getEvents(char ch
, boolean noChar
) {
729 List
<TInputEvent
> events
= new LinkedList
<TInputEvent
>();
731 TKeypressEvent keypress
;
732 Date now
= new Date();
734 // ESCDELAY type timeout
735 if (state
== ParseState
.ESCAPE
) {
736 long escDelay
= now
.getTime() - escapeTime
;
737 if (escDelay
> 250) {
738 // After 0.25 seconds, assume a true escape character
739 events
.add(controlChar((char)0x1B));
744 if (noChar
== true) {
745 int newWidth
= session
.getWindowWidth();
746 int newHeight
= session
.getWindowHeight();
747 if ((newWidth
!= windowResize
.width
) ||
748 (newHeight
!= windowResize
.height
)) {
749 TResizeEvent event
= new TResizeEvent(TResizeEvent
.Type
.Screen
,
750 newWidth
, newHeight
);
751 windowResize
.width
= newWidth
;
752 windowResize
.height
= newHeight
;
756 // Nothing else to do, bail out
760 // System.err.printf("state: %s ch %c\r\n", state, ch);
766 state
= ParseState
.ESCAPE
;
767 escapeTime
= now
.getTime();
773 events
.add(controlChar(ch
));
780 keypress
= new TKeypressEvent();
781 keypress
.key
.isKey
= false;
782 keypress
.key
.ch
= ch
;
783 events
.add(keypress
);
792 // ALT-Control character
793 keypress
= controlChar(ch
);
794 keypress
.key
.alt
= true;
795 events
.add(keypress
);
801 // This will be one of the function keys
802 state
= ParseState
.ESCAPE_INTERMEDIATE
;
806 // '[' goes to CSI_ENTRY
808 state
= ParseState
.CSI_ENTRY
;
812 // Everything else is assumed to be Alt-keystroke
813 keypress
= new TKeypressEvent();
814 keypress
.key
.isKey
= false;
815 keypress
.key
.ch
= ch
;
816 keypress
.key
.alt
= true;
817 if ((ch
>= 'A') && (ch
<= 'Z')) {
818 keypress
.key
.shift
= true;
820 events
.add(keypress
);
824 case ESCAPE_INTERMEDIATE
:
825 if ((ch
>= 'P') && (ch
<= 'S')) {
827 keypress
= new TKeypressEvent();
828 keypress
.key
.isKey
= true;
831 keypress
.key
.fnKey
= TKeypress
.F1
;
834 keypress
.key
.fnKey
= TKeypress
.F2
;
837 keypress
.key
.fnKey
= TKeypress
.F3
;
840 keypress
.key
.fnKey
= TKeypress
.F4
;
845 events
.add(keypress
);
850 // Unknown keystroke, ignore
855 // Numbers - parameter values
856 if ((ch
>= '0') && (ch
<= '9')) {
857 params
.set(paramI
, params
.get(paramI
) + ch
);
858 state
= ParseState
.CSI_PARAM
;
861 // Parameter separator
864 params
.set(paramI
, "");
868 if ((ch
>= 0x30) && (ch
<= 0x7E)) {
872 keypress
= new TKeypressEvent();
873 keypress
.key
.isKey
= true;
874 keypress
.key
.fnKey
= TKeypress
.UP
;
875 if (params
.size() > 1) {
876 if (params
.get(1).equals("2")) {
877 keypress
.key
.shift
= true;
879 if (params
.get(1).equals("5")) {
880 keypress
.key
.ctrl
= true;
882 if (params
.get(1).equals("3")) {
883 keypress
.key
.alt
= true;
886 events
.add(keypress
);
891 keypress
= new TKeypressEvent();
892 keypress
.key
.isKey
= true;
893 keypress
.key
.fnKey
= TKeypress
.DOWN
;
894 if (params
.size() > 1) {
895 if (params
.get(1).equals("2")) {
896 keypress
.key
.shift
= true;
898 if (params
.get(1).equals("5")) {
899 keypress
.key
.ctrl
= true;
901 if (params
.get(1).equals("3")) {
902 keypress
.key
.alt
= true;
905 events
.add(keypress
);
910 keypress
= new TKeypressEvent();
911 keypress
.key
.isKey
= true;
912 keypress
.key
.fnKey
= TKeypress
.RIGHT
;
913 if (params
.size() > 1) {
914 if (params
.get(1).equals("2")) {
915 keypress
.key
.shift
= true;
917 if (params
.get(1).equals("5")) {
918 keypress
.key
.ctrl
= true;
920 if (params
.get(1).equals("3")) {
921 keypress
.key
.alt
= true;
924 events
.add(keypress
);
929 keypress
= new TKeypressEvent();
930 keypress
.key
.isKey
= true;
931 keypress
.key
.fnKey
= TKeypress
.LEFT
;
932 if (params
.size() > 1) {
933 if (params
.get(1).equals("2")) {
934 keypress
.key
.shift
= true;
936 if (params
.get(1).equals("5")) {
937 keypress
.key
.ctrl
= true;
939 if (params
.get(1).equals("3")) {
940 keypress
.key
.alt
= true;
943 events
.add(keypress
);
948 keypress
= new TKeypressEvent();
949 keypress
.key
.isKey
= true;
950 keypress
.key
.fnKey
= TKeypress
.HOME
;
951 events
.add(keypress
);
956 keypress
= new TKeypressEvent();
957 keypress
.key
.isKey
= true;
958 keypress
.key
.fnKey
= TKeypress
.END
;
959 events
.add(keypress
);
963 // CBT - Cursor backward X tab stops (default 1)
964 keypress
= new TKeypressEvent();
965 keypress
.key
.isKey
= true;
966 keypress
.key
.fnKey
= TKeypress
.BTAB
;
967 events
.add(keypress
);
972 state
= ParseState
.MOUSE
;
979 // Unknown keystroke, ignore
984 // Numbers - parameter values
985 if ((ch
>= '0') && (ch
<= '9')) {
986 params
.set(paramI
, params
.get(paramI
) + ch
);
987 state
= ParseState
.CSI_PARAM
;
990 // Parameter separator
993 params
.set(paramI
, "");
998 events
.add(csiFnKey());
1003 if ((ch
>= 0x30) && (ch
<= 0x7E)) {
1007 keypress
= new TKeypressEvent();
1008 keypress
.key
.isKey
= true;
1009 keypress
.key
.fnKey
= TKeypress
.UP
;
1010 if (params
.size() > 1) {
1011 if (params
.get(1).equals("2")) {
1012 keypress
.key
.shift
= true;
1014 if (params
.get(1).equals("5")) {
1015 keypress
.key
.ctrl
= true;
1017 if (params
.get(1).equals("3")) {
1018 keypress
.key
.alt
= true;
1021 events
.add(keypress
);
1026 keypress
= new TKeypressEvent();
1027 keypress
.key
.isKey
= true;
1028 keypress
.key
.fnKey
= TKeypress
.DOWN
;
1029 if (params
.size() > 1) {
1030 if (params
.get(1).equals("2")) {
1031 keypress
.key
.shift
= true;
1033 if (params
.get(1).equals("5")) {
1034 keypress
.key
.ctrl
= true;
1036 if (params
.get(1).equals("3")) {
1037 keypress
.key
.alt
= true;
1040 events
.add(keypress
);
1045 keypress
= new TKeypressEvent();
1046 keypress
.key
.isKey
= true;
1047 keypress
.key
.fnKey
= TKeypress
.RIGHT
;
1048 if (params
.size() > 1) {
1049 if (params
.get(1).equals("2")) {
1050 keypress
.key
.shift
= true;
1052 if (params
.get(1).equals("5")) {
1053 keypress
.key
.ctrl
= true;
1055 if (params
.get(1).equals("3")) {
1056 keypress
.key
.alt
= true;
1059 events
.add(keypress
);
1064 keypress
= new TKeypressEvent();
1065 keypress
.key
.isKey
= true;
1066 keypress
.key
.fnKey
= TKeypress
.LEFT
;
1067 if (params
.size() > 1) {
1068 if (params
.get(1).equals("2")) {
1069 keypress
.key
.shift
= true;
1071 if (params
.get(1).equals("5")) {
1072 keypress
.key
.ctrl
= true;
1074 if (params
.get(1).equals("3")) {
1075 keypress
.key
.alt
= true;
1078 events
.add(keypress
);
1086 // Unknown keystroke, ignore
1091 params
.set(0, params
.get(paramI
) + ch
);
1092 if (params
.get(0).length() == 3) {
1093 // We have enough to generate a mouse event
1094 events
.add(parseMouse());
1103 // This "should" be impossible to reach
1108 * Tell (u)xterm that we want alt- keystrokes to send escape + character
1109 * rather than set the 8th bit. Anyone who wants UTF8 should want this
1112 * @param on if true, enable metaSendsEscape
1113 * @return the string to emit to xterm
1115 static public String
xtermMetaSendsEscape(boolean on
) {
1117 return "\033[?1036h\033[?1034l";
1119 return "\033[?1036l";
1123 * Convert a list of SGR parameters into a full escape sequence. This
1124 * also eliminates a trailing ';' which would otherwise reset everything
1125 * to white-on-black not-bold.
1127 * @param str string of parameters, e.g. "31;1;"
1128 * @return the string to emit to an ANSI / ECMA-style terminal,
1131 static public String
addHeaderSGR(String str
) {
1132 if (str
.length() > 0) {
1133 // Nix any trailing ';' because that resets all attributes
1134 while (str
.endsWith(":")) {
1135 str
= str
.substring(0, str
.length() - 1);
1138 return "\033[" + str
+ "m";
1142 * Create a SGR parameter sequence for a single color change.
1144 * @param color one of the Color.WHITE, Color.BLUE, etc. constants
1145 * @param foreground if true, this is a foreground color
1146 * @return the string to emit to an ANSI / ECMA-style terminal,
1149 static public String
color(Color color
, boolean foreground
) {
1150 return color(color
, foreground
, true);
1154 * Create a SGR parameter sequence for a single color change.
1156 * @param color one of the Color.WHITE, Color.BLUE, etc. constants
1157 * @param foreground if true, this is a foreground color
1158 * @param header if true, make the full header, otherwise just emit the
1159 * color parameter e.g. "42;"
1160 * @return the string to emit to an ANSI / ECMA-style terminal,
1163 static public String
color(Color color
, boolean foreground
,
1166 int ecmaColor
= color
.value
;
1168 // Convert Color.* values to SGR numerics
1169 if (foreground
== true) {
1176 return String
.format("\033[%dm", ecmaColor
);
1178 return String
.format("%d;", ecmaColor
);
1183 * Create a SGR parameter sequence for both foreground and
1184 * background color change.
1186 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1187 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1188 * @return the string to emit to an ANSI / ECMA-style terminal,
1189 * 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.
1199 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1200 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1201 * @param header if true, make the full header, otherwise just emit the
1202 * color parameter e.g. "31;42;"
1203 * @return the string to emit to an ANSI / ECMA-style terminal,
1204 * e.g. "\033[31;42m"
1206 static public String
color(Color foreColor
, Color backColor
,
1209 int ecmaForeColor
= foreColor
.value
;
1210 int ecmaBackColor
= backColor
.value
;
1212 // Convert Color.* values to SGR numerics
1213 ecmaBackColor
+= 40;
1214 ecmaForeColor
+= 30;
1217 return String
.format("\033[%d;%dm", ecmaForeColor
, ecmaBackColor
);
1219 return String
.format("%d;%d;", ecmaForeColor
, ecmaBackColor
);
1224 * Create a SGR parameter sequence for foreground, background, and
1225 * several attributes. This sequence first resets all attributes to
1226 * default, then sets attributes as per the parameters.
1228 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1229 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1230 * @param bold if true, set bold
1231 * @param reverse if true, set reverse
1232 * @param blink if true, set blink
1233 * @param underline if true, set underline
1234 * @return the string to emit to an ANSI / ECMA-style terminal,
1235 * e.g. "\033[0;1;31;42m"
1237 static public String
color(Color foreColor
, Color backColor
, boolean bold
,
1238 boolean reverse
, boolean blink
, boolean underline
) {
1240 int ecmaForeColor
= foreColor
.value
;
1241 int ecmaBackColor
= backColor
.value
;
1243 // Convert Color.* values to SGR numerics
1244 ecmaBackColor
+= 40;
1245 ecmaForeColor
+= 30;
1247 StringBuilder sb
= new StringBuilder();
1248 if ( bold
&& reverse
&& blink
&& !underline
) {
1249 sb
.append("\033[0;1;7;5;");
1250 } else if ( bold
&& reverse
&& !blink
&& !underline
) {
1251 sb
.append("\033[0;1;7;");
1252 } else if ( !bold
&& reverse
&& blink
&& !underline
) {
1253 sb
.append("\033[0;7;5;");
1254 } else if ( bold
&& !reverse
&& blink
&& !underline
) {
1255 sb
.append("\033[0;1;5;");
1256 } else if ( bold
&& !reverse
&& !blink
&& !underline
) {
1257 sb
.append("\033[0;1;");
1258 } else if ( !bold
&& reverse
&& !blink
&& !underline
) {
1259 sb
.append("\033[0;7;");
1260 } else if ( !bold
&& !reverse
&& blink
&& !underline
) {
1261 sb
.append("\033[0;5;");
1262 } else if ( bold
&& reverse
&& blink
&& underline
) {
1263 sb
.append("\033[0;1;7;5;4;");
1264 } else if ( bold
&& reverse
&& !blink
&& underline
) {
1265 sb
.append("\033[0;1;7;4;");
1266 } else if ( !bold
&& reverse
&& blink
&& underline
) {
1267 sb
.append("\033[0;7;5;4;");
1268 } else if ( bold
&& !reverse
&& blink
&& underline
) {
1269 sb
.append("\033[0;1;5;4;");
1270 } else if ( bold
&& !reverse
&& !blink
&& underline
) {
1271 sb
.append("\033[0;1;4;");
1272 } else if ( !bold
&& reverse
&& !blink
&& underline
) {
1273 sb
.append("\033[0;7;4;");
1274 } else if ( !bold
&& !reverse
&& blink
&& underline
) {
1275 sb
.append("\033[0;5;4;");
1276 } else if ( !bold
&& !reverse
&& !blink
&& underline
) {
1277 sb
.append("\033[0;4;");
1279 assert(!bold
&& !reverse
&& !blink
&& !underline
);
1280 sb
.append("\033[0;");
1282 sb
.append(String
.format("%d;%dm", ecmaForeColor
, ecmaBackColor
));
1283 return sb
.toString();
1287 * Create a SGR parameter sequence for enabling reverse color.
1289 * @param on if true, turn on reverse
1290 * @return the string to emit to an ANSI / ECMA-style terminal,
1293 static public String
reverse(boolean on
) {
1301 * Create a SGR parameter sequence to reset to defaults.
1303 * @return the string to emit to an ANSI / ECMA-style terminal,
1306 static public String
normal() {
1307 return normal(true);
1311 * Create a SGR parameter sequence to reset to defaults.
1313 * @param header if true, make the full header, otherwise just emit the
1314 * bare parameter e.g. "0;"
1315 * @return the string to emit to an ANSI / ECMA-style terminal,
1318 static public String
normal(boolean header
) {
1320 return "\033[0;37;40m";
1326 * Create a SGR parameter sequence for enabling boldface.
1328 * @param on if true, turn on bold
1329 * @return the string to emit to an ANSI / ECMA-style terminal,
1332 static public String
bold(boolean on
) {
1333 return bold(on
, true);
1337 * Create a SGR parameter sequence for enabling boldface.
1339 * @param on if true, turn on bold
1340 * @param header if true, make the full header, otherwise just emit the
1341 * bare parameter e.g. "1;"
1342 * @return the string to emit to an ANSI / ECMA-style terminal,
1345 static public String
bold(boolean on
, boolean header
) {
1359 * Create a SGR parameter sequence for enabling blinking text.
1361 * @param on if true, turn on blink
1362 * @return the string to emit to an ANSI / ECMA-style terminal,
1365 static public String
blink(boolean on
) {
1366 return blink(on
, true);
1370 * Create a SGR parameter sequence for enabling blinking text.
1372 * @param on if true, turn on blink
1373 * @param header if true, make the full header, otherwise just emit the
1374 * bare parameter e.g. "5;"
1375 * @return the string to emit to an ANSI / ECMA-style terminal,
1378 static public String
blink(boolean on
, boolean header
) {
1392 * Create a SGR parameter sequence for enabling underline / underscored
1395 * @param on if true, turn on underline
1396 * @return the string to emit to an ANSI / ECMA-style terminal,
1399 static public String
underline(boolean on
) {
1407 * Create a SGR parameter sequence for enabling the visible cursor.
1409 * @param on if true, turn on cursor
1410 * @return the string to emit to an ANSI / ECMA-style terminal
1412 public String
cursor(boolean on
) {
1413 if (on
&& (cursorOn
== false)) {
1417 if (!on
&& (cursorOn
== true)) {
1425 * Clear the entire screen. Because some terminals use back-color-erase,
1426 * set the color to white-on-black beforehand.
1428 * @return the string to emit to an ANSI / ECMA-style terminal
1430 static public String
clearAll() {
1431 return "\033[0;37;40m\033[2J";
1435 * Clear the line from the cursor (inclusive) to the end of the screen.
1436 * Because some terminals use back-color-erase, set the color to
1437 * white-on-black beforehand.
1439 * @return the string to emit to an ANSI / ECMA-style terminal
1441 static public String
clearRemainingLine() {
1442 return "\033[0;37;40m\033[K";
1446 * Clear the line up the cursor (inclusive). Because some terminals use
1447 * back-color-erase, set the color to white-on-black beforehand.
1449 * @return the string to emit to an ANSI / ECMA-style terminal
1451 static public String
clearPreceedingLine() {
1452 return "\033[0;37;40m\033[1K";
1456 * Clear the line. Because some terminals use back-color-erase, set the
1457 * color to white-on-black beforehand.
1459 * @return the string to emit to an ANSI / ECMA-style terminal
1461 static public String
clearLine() {
1462 return "\033[0;37;40m\033[2K";
1466 * Move the cursor to the top-left corner.
1468 * @return the string to emit to an ANSI / ECMA-style terminal
1470 static public String
home() {
1475 * Move the cursor to (x, y).
1477 * @param x column coordinate. 0 is the left-most column.
1478 * @param y row coordinate. 0 is the top-most row.
1479 * @return the string to emit to an ANSI / ECMA-style terminal
1481 static public String
gotoXY(int x
, int y
) {
1482 return String
.format("\033[%d;%dH", y
+ 1, x
+ 1);
1486 * Tell (u)xterm that we want to receive mouse events based on "Any event
1487 * tracking" and UTF-8 coordinates. See
1488 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
1490 * Note that this also sets the alternate/primary screen buffer.
1492 * @param on If true, enable mouse report and use the alternate screen
1493 * buffer. If false disable mouse reporting and use the primary screen
1495 * @return the string to emit to xterm
1497 static public String
mouse(boolean on
) {
1499 return "\033[?1003;1005h\033[?1049h";
1501 return "\033[?1003;1005l\033[?1049l";