001/* 002 * Copyright (C) 2009-2017 the original author(s). 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.fusesource.jansi; 017 018import java.io.IOException; 019import java.io.PrintStream; // expected diff with AnsiOutputStream.java 020import java.nio.charset.Charset; 021import java.util.ArrayList; 022import java.util.Iterator; 023 024/** 025 * A ANSI print stream extracts ANSI escape codes written to 026 * a print stream and calls corresponding <code>process*</code> methods. 027 * 028 * <p>For more information about ANSI escape codes, see 029 * <a href="http://en.wikipedia.org/wiki/ANSI_escape_code">Wikipedia article</a> 030 * 031 * <p>This class just filters out the escape codes so that they are not 032 * sent out to the underlying OutputStream: <code>process*</code> methods 033 * are empty. Subclasses should actually perform the ANSI escape behaviors 034 * by implementing active code in <code>process*</code> methods. 035 * 036 * @author <a href="http://hiramchirino.com">Hiram Chirino</a> 037 * @author Joris Kuipers 038 * @since 1.7 039 * @see AnsiOutputStream 040 */ 041public class AnsiPrintStream extends FilterPrintStream { // expected diff with AnsiOutputStream.java 042 043 public static final String RESET_CODE = "\033[0m"; // expected diff with AnsiOutputStream.java 044 045 public AnsiPrintStream(PrintStream ps) { // expected diff with AnsiOutputStream.java 046 super(ps); // expected diff with AnsiOutputStream.java 047 } 048 049 private final static int MAX_ESCAPE_SEQUENCE_LENGTH = 100; 050 private final byte[] buffer = new byte[MAX_ESCAPE_SEQUENCE_LENGTH]; 051 private int pos = 0; 052 private int startOfValue; 053 private final ArrayList<Object> options = new ArrayList<Object>(); 054 055 private static final int LOOKING_FOR_FIRST_ESC_CHAR = 0; 056 private static final int LOOKING_FOR_SECOND_ESC_CHAR = 1; 057 private static final int LOOKING_FOR_NEXT_ARG = 2; 058 private static final int LOOKING_FOR_STR_ARG_END = 3; 059 private static final int LOOKING_FOR_INT_ARG_END = 4; 060 private static final int LOOKING_FOR_OSC_COMMAND = 5; 061 private static final int LOOKING_FOR_OSC_COMMAND_END = 6; 062 private static final int LOOKING_FOR_OSC_PARAM = 7; 063 private static final int LOOKING_FOR_ST = 8; 064 private static final int LOOKING_FOR_CHARSET = 9; 065 066 int state = LOOKING_FOR_FIRST_ESC_CHAR; 067 068 private static final int FIRST_ESC_CHAR = 27; 069 private static final int SECOND_ESC_CHAR = '['; 070 private static final int SECOND_OSC_CHAR = ']'; 071 private static final int BEL = 7; 072 private static final int SECOND_ST_CHAR = '\\'; 073 private static final int SECOND_CHARSET0_CHAR = '('; 074 private static final int SECOND_CHARSET1_CHAR = ')'; 075 076 @Override 077 protected synchronized boolean filter(int data) { // expected diff with AnsiOutputStream.java 078 switch (state) { 079 case LOOKING_FOR_FIRST_ESC_CHAR: 080 if (data == FIRST_ESC_CHAR) { 081 buffer[pos++] = (byte) data; 082 state = LOOKING_FOR_SECOND_ESC_CHAR; 083 return false; // expected diff with AnsiOutputStream.java 084 } 085 return true; // expected diff with AnsiOutputStream.java 086 087 case LOOKING_FOR_SECOND_ESC_CHAR: 088 buffer[pos++] = (byte) data; 089 if (data == SECOND_ESC_CHAR) { 090 state = LOOKING_FOR_NEXT_ARG; 091 } else if (data == SECOND_OSC_CHAR) { 092 state = LOOKING_FOR_OSC_COMMAND; 093 } else if (data == SECOND_CHARSET0_CHAR) { 094 options.add(Integer.valueOf(0)); 095 state = LOOKING_FOR_CHARSET; 096 } else if (data == SECOND_CHARSET1_CHAR) { 097 options.add(Integer.valueOf(1)); 098 state = LOOKING_FOR_CHARSET; 099 } else { 100 reset(false); 101 } 102 break; 103 104 case LOOKING_FOR_NEXT_ARG: 105 buffer[pos++] = (byte) data; 106 if ('"' == data) { 107 startOfValue = pos - 1; 108 state = LOOKING_FOR_STR_ARG_END; 109 } else if ('0' <= data && data <= '9') { 110 startOfValue = pos - 1; 111 state = LOOKING_FOR_INT_ARG_END; 112 } else if (';' == data) { 113 options.add(null); 114 } else if ('?' == data) { 115 options.add('?'); 116 } else if ('=' == data) { 117 options.add('='); 118 } else { 119 reset(processEscapeCommand(options, data)); 120 } 121 break; 122 default: 123 break; 124 125 case LOOKING_FOR_INT_ARG_END: 126 buffer[pos++] = (byte) data; 127 if (!('0' <= data && data <= '9')) { 128 String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset()); 129 Integer value = new Integer(strValue); 130 options.add(value); 131 if (data == ';') { 132 state = LOOKING_FOR_NEXT_ARG; 133 } else { 134 reset(processEscapeCommand(options, data)); 135 } 136 } 137 break; 138 139 case LOOKING_FOR_STR_ARG_END: 140 buffer[pos++] = (byte) data; 141 if ('"' != data) { 142 String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset()); 143 options.add(value); 144 if (data == ';') { 145 state = LOOKING_FOR_NEXT_ARG; 146 } else { 147 reset(processEscapeCommand(options, data)); 148 } 149 } 150 break; 151 152 case LOOKING_FOR_OSC_COMMAND: 153 buffer[pos++] = (byte) data; 154 if ('0' <= data && data <= '9') { 155 startOfValue = pos - 1; 156 state = LOOKING_FOR_OSC_COMMAND_END; 157 } else { 158 reset(false); 159 } 160 break; 161 162 case LOOKING_FOR_OSC_COMMAND_END: 163 buffer[pos++] = (byte) data; 164 if (';' == data) { 165 String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset()); 166 Integer value = new Integer(strValue); 167 options.add(value); 168 startOfValue = pos; 169 state = LOOKING_FOR_OSC_PARAM; 170 } else if ('0' <= data && data <= '9') { 171 // already pushed digit to buffer, just keep looking 172 } else { 173 // oops, did not expect this 174 reset(false); 175 } 176 break; 177 178 case LOOKING_FOR_OSC_PARAM: 179 buffer[pos++] = (byte) data; 180 if (BEL == data) { 181 String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset()); 182 options.add(value); 183 reset(processOperatingSystemCommand(options)); 184 } else if (FIRST_ESC_CHAR == data) { 185 state = LOOKING_FOR_ST; 186 } else { 187 // just keep looking while adding text 188 } 189 break; 190 191 case LOOKING_FOR_ST: 192 buffer[pos++] = (byte) data; 193 if (SECOND_ST_CHAR == data) { 194 String value = new String(buffer, startOfValue, (pos - 2) - startOfValue, Charset.defaultCharset()); 195 options.add(value); 196 reset(processOperatingSystemCommand(options)); 197 } else { 198 state = LOOKING_FOR_OSC_PARAM; 199 } 200 break; 201 202 case LOOKING_FOR_CHARSET: 203 options.add(Character.valueOf((char) data)); 204 reset(processCharsetSelect(options)); 205 break; 206 } 207 208 // Is it just too long? 209 if (pos >= buffer.length) { 210 reset(false); 211 } 212 return false; // expected diff with AnsiOutputStream.java 213 } 214 215 /** 216 * Resets all state to continue with regular parsing 217 * @param skipBuffer if current buffer should be skipped or written to out 218 */ 219 private void reset(boolean skipBuffer) { // expected diff with AnsiOutputStream.java 220 if (!skipBuffer) { 221 ps.write(buffer, 0, pos); // expected diff with AnsiOutputStream.java 222 } 223 pos = 0; 224 startOfValue = 0; 225 options.clear(); 226 state = LOOKING_FOR_FIRST_ESC_CHAR; 227 } 228 229 /** 230 * Helper for processEscapeCommand() to iterate over integer options 231 * @param optionsIterator the underlying iterator 232 * @throws IOException if no more non-null values left 233 */ 234 private int getNextOptionInt(Iterator<Object> optionsIterator) throws IOException { 235 for (;;) { 236 if (!optionsIterator.hasNext()) 237 throw new IllegalArgumentException(); 238 Object arg = optionsIterator.next(); 239 if (arg != null) 240 return (Integer) arg; 241 } 242 } 243 244 /** 245 * 246 * @param options 247 * @param command 248 * @return true if the escape command was processed. 249 */ 250 private boolean processEscapeCommand(ArrayList<Object> options, int command) { // expected diff with AnsiOutputStream.java 251 try { 252 switch (command) { 253 case 'A': 254 processCursorUp(optionInt(options, 0, 1)); 255 return true; 256 case 'B': 257 processCursorDown(optionInt(options, 0, 1)); 258 return true; 259 case 'C': 260 processCursorRight(optionInt(options, 0, 1)); 261 return true; 262 case 'D': 263 processCursorLeft(optionInt(options, 0, 1)); 264 return true; 265 case 'E': 266 processCursorDownLine(optionInt(options, 0, 1)); 267 return true; 268 case 'F': 269 processCursorUpLine(optionInt(options, 0, 1)); 270 return true; 271 case 'G': 272 processCursorToColumn(optionInt(options, 0)); 273 return true; 274 case 'H': 275 case 'f': 276 processCursorTo(optionInt(options, 0, 1), optionInt(options, 1, 1)); 277 return true; 278 case 'J': 279 processEraseScreen(optionInt(options, 0, 0)); 280 return true; 281 case 'K': 282 processEraseLine(optionInt(options, 0, 0)); 283 return true; 284 case 'L': 285 processInsertLine(optionInt(options, 0, 1)); 286 return true; 287 case 'M': 288 processDeleteLine(optionInt(options, 0, 1)); 289 return true; 290 case 'S': 291 processScrollUp(optionInt(options, 0, 1)); 292 return true; 293 case 'T': 294 processScrollDown(optionInt(options, 0, 1)); 295 return true; 296 case 'm': 297 // Validate all options are ints... 298 for (Object next : options) { 299 if (next != null && next.getClass() != Integer.class) { 300 throw new IllegalArgumentException(); 301 } 302 } 303 304 int count = 0; 305 Iterator<Object> optionsIterator = options.iterator(); 306 while (optionsIterator.hasNext()) { 307 Object next = optionsIterator.next(); 308 if (next != null) { 309 count++; 310 int value = (Integer) next; 311 if (30 <= value && value <= 37) { 312 processSetForegroundColor(value - 30); 313 } else if (40 <= value && value <= 47) { 314 processSetBackgroundColor(value - 40); 315 } else if (90 <= value && value <= 97) { 316 processSetForegroundColor(value - 90, true); 317 } else if (100 <= value && value <= 107) { 318 processSetBackgroundColor(value - 100, true); 319 } else if (value == 38 || value == 48) { 320 // extended color like `esc[38;5;<index>m` or `esc[38;2;<r>;<g>;<b>m` 321 int arg2or5 = getNextOptionInt(optionsIterator); 322 if (arg2or5 == 2) { 323 // 24 bit color style like `esc[38;2;<r>;<g>;<b>m` 324 int r = getNextOptionInt(optionsIterator); 325 int g = getNextOptionInt(optionsIterator); 326 int b = getNextOptionInt(optionsIterator); 327 if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) { 328 if (value == 38) 329 processSetForegroundColorExt(r, g, b); 330 else 331 processSetBackgroundColorExt(r, g, b); 332 } else { 333 throw new IllegalArgumentException(); 334 } 335 } 336 else if (arg2or5 == 5) { 337 // 256 color style like `esc[38;5;<index>m` 338 int paletteIndex = getNextOptionInt(optionsIterator); 339 if (paletteIndex >= 0 && paletteIndex <= 255) { 340 if (value == 38) 341 processSetForegroundColorExt(paletteIndex); 342 else 343 processSetBackgroundColorExt(paletteIndex); 344 } else { 345 throw new IllegalArgumentException(); 346 } 347 } 348 else { 349 throw new IllegalArgumentException(); 350 } 351 } else { 352 switch (value) { 353 case 39: 354 processDefaultTextColor(); 355 break; 356 case 49: 357 processDefaultBackgroundColor(); 358 break; 359 case 0: 360 processAttributeRest(); 361 break; 362 default: 363 processSetAttribute(value); 364 } 365 } 366 } 367 } 368 if (count == 0) { 369 processAttributeRest(); 370 } 371 return true; 372 case 's': 373 processSaveCursorPosition(); 374 return true; 375 case 'u': 376 processRestoreCursorPosition(); 377 return true; 378 379 default: 380 if ('a' <= command && 'z' <= command) { 381 processUnknownExtension(options, command); 382 return true; 383 } 384 if ('A' <= command && 'Z' <= command) { 385 processUnknownExtension(options, command); 386 return true; 387 } 388 return false; 389 } 390 } catch (IllegalArgumentException ignore) { 391 } catch (IOException ioe) { // expected diff with AnsiOutputStream.java 392 setError(); // expected diff with AnsiOutputStream.java 393 } 394 return false; 395 } 396 397 /** 398 * 399 * @param options 400 * @return true if the operating system command was processed. 401 */ 402 private boolean processOperatingSystemCommand(ArrayList<Object> options) { // expected diff with AnsiOutputStream.java 403 int command = optionInt(options, 0); 404 String label = (String) options.get(1); 405 // for command > 2 label could be composed (i.e. contain ';'), but we'll leave 406 // it to processUnknownOperatingSystemCommand implementations to handle that 407 try { 408 switch (command) { 409 case 0: 410 processChangeIconNameAndWindowTitle(label); 411 return true; 412 case 1: 413 processChangeIconName(label); 414 return true; 415 case 2: 416 processChangeWindowTitle(label); 417 return true; 418 419 default: 420 // not exactly unknown, but not supported through dedicated process methods: 421 processUnknownOperatingSystemCommand(command, label); 422 return true; 423 } 424 } catch (IllegalArgumentException ignore) { 425 } 426 return false; 427 } 428 429 /** 430 * Process <code>CSI u</code> ANSI code, corresponding to <code>RCP – Restore Cursor Position</code> 431 * @throws IOException IOException 432 */ 433 protected void processRestoreCursorPosition() throws IOException { 434 } 435 436 /** 437 * Process <code>CSI s</code> ANSI code, corresponding to <code>SCP – Save Cursor Position</code> 438 * @throws IOException IOException 439 */ 440 protected void processSaveCursorPosition() throws IOException { 441 } 442 443 /** 444 * Process <code>CSI L</code> ANSI code, corresponding to <code>IL – Insert Line</code> 445 * @param optionInt option 446 * @throws IOException IOException 447 * @since 1.16 448 */ 449 protected void processInsertLine(int optionInt) throws IOException { 450 } 451 452 /** 453 * Process <code>CSI M</code> ANSI code, corresponding to <code>DL – Delete Line</code> 454 * @param optionInt option 455 * @throws IOException IOException 456 * @since 1.16 457 */ 458 protected void processDeleteLine(int optionInt) throws IOException { 459 } 460 461 /** 462 * Process <code>CSI n T</code> ANSI code, corresponding to <code>SD – Scroll Down</code> 463 * @param optionInt option 464 * @throws IOException IOException 465 */ 466 protected void processScrollDown(int optionInt) throws IOException { 467 } 468 469 /** 470 * Process <code>CSI n U</code> ANSI code, corresponding to <code>SU – Scroll Up</code> 471 * @param optionInt option 472 * @throws IOException IOException 473 */ 474 protected void processScrollUp(int optionInt) throws IOException { 475 } 476 477 protected static final int ERASE_SCREEN_TO_END = 0; 478 protected static final int ERASE_SCREEN_TO_BEGINING = 1; 479 protected static final int ERASE_SCREEN = 2; 480 481 /** 482 * Process <code>CSI n J</code> ANSI code, corresponding to <code>ED – Erase in Display</code> 483 * @param eraseOption eraseOption 484 * @throws IOException IOException 485 */ 486 protected void processEraseScreen(int eraseOption) throws IOException { 487 } 488 489 protected static final int ERASE_LINE_TO_END = 0; 490 protected static final int ERASE_LINE_TO_BEGINING = 1; 491 protected static final int ERASE_LINE = 2; 492 493 /** 494 * Process <code>CSI n K</code> ANSI code, corresponding to <code>ED – Erase in Line</code> 495 * @param eraseOption eraseOption 496 * @throws IOException IOException 497 */ 498 protected void processEraseLine(int eraseOption) throws IOException { 499 } 500 501 protected static final int ATTRIBUTE_INTENSITY_BOLD = 1; // Intensity: Bold 502 protected static final int ATTRIBUTE_INTENSITY_FAINT = 2; // Intensity; Faint not widely supported 503 protected static final int ATTRIBUTE_ITALIC = 3; // Italic; on not widely supported. Sometimes treated as inverse. 504 protected static final int ATTRIBUTE_UNDERLINE = 4; // Underline; Single 505 protected static final int ATTRIBUTE_BLINK_SLOW = 5; // Blink; Slow less than 150 per minute 506 protected static final int ATTRIBUTE_BLINK_FAST = 6; // Blink; Rapid MS-DOS ANSI.SYS; 150 per minute or more 507 protected static final int ATTRIBUTE_NEGATIVE_ON = 7; // Image; Negative inverse or reverse; swap foreground and background 508 protected static final int ATTRIBUTE_CONCEAL_ON = 8; // Conceal on 509 protected static final int ATTRIBUTE_UNDERLINE_DOUBLE = 21; // Underline; Double not widely supported 510 protected static final int ATTRIBUTE_INTENSITY_NORMAL = 22; // Intensity; Normal not bold and not faint 511 protected static final int ATTRIBUTE_UNDERLINE_OFF = 24; // Underline; None 512 protected static final int ATTRIBUTE_BLINK_OFF = 25; // Blink; off 513 @Deprecated 514 protected static final int ATTRIBUTE_NEGATIVE_Off = 27; // Image; Positive 515 protected static final int ATTRIBUTE_NEGATIVE_OFF = 27; // Image; Positive 516 protected static final int ATTRIBUTE_CONCEAL_OFF = 28; // Reveal conceal off 517 518 /** 519 * process <code>SGR</code> other than <code>0</code> (reset), <code>30-39</code> (foreground), 520 * <code>40-49</code> (background), <code>90-97</code> (foreground high intensity) or 521 * <code>100-107</code> (background high intensity) 522 * @param attribute attribute 523 * @throws IOException IOException 524 * @see #processAttributeRest() 525 * @see #processSetForegroundColor(int) 526 * @see #processSetForegroundColor(int, boolean) 527 * @see #processSetForegroundColorExt(int) 528 * @see #processSetForegroundColorExt(int, int, int) 529 * @see #processDefaultTextColor() 530 * @see #processDefaultBackgroundColor() 531 */ 532 protected void processSetAttribute(int attribute) throws IOException { 533 } 534 535 protected static final int BLACK = 0; 536 protected static final int RED = 1; 537 protected static final int GREEN = 2; 538 protected static final int YELLOW = 3; 539 protected static final int BLUE = 4; 540 protected static final int MAGENTA = 5; 541 protected static final int CYAN = 6; 542 protected static final int WHITE = 7; 543 544 /** 545 * process <code>SGR 30-37</code> corresponding to <code>Set text color (foreground)</code>. 546 * @param color the text color 547 * @throws IOException IOException 548 */ 549 protected void processSetForegroundColor(int color) throws IOException { 550 processSetForegroundColor(color, false); 551 } 552 553 /** 554 * process <code>SGR 30-37</code> or <code>SGR 90-97</code> corresponding to 555 * <code>Set text color (foreground)</code> either in normal mode or high intensity. 556 * @param color the text color 557 * @param bright is high intensity? 558 * @throws IOException IOException 559 */ 560 protected void processSetForegroundColor(int color, boolean bright) throws IOException { 561 } 562 563 /** 564 * process <code>SGR 38</code> corresponding to <code>extended set text color (foreground)</code> 565 * with a palette of 255 colors. 566 * @param paletteIndex the text color in the palette 567 * @throws IOException IOException 568 */ 569 protected void processSetForegroundColorExt(int paletteIndex) throws IOException { 570 } 571 572 /** 573 * process <code>SGR 38</code> corresponding to <code>extended set text color (foreground)</code> 574 * with a 24 bits RGB definition of the color. 575 * @param r red 576 * @param g green 577 * @param b blue 578 * @throws IOException IOException 579 */ 580 protected void processSetForegroundColorExt(int r, int g, int b) throws IOException { 581 } 582 583 /** 584 * process <code>SGR 40-47</code> corresponding to <code>Set background color</code>. 585 * @param color the background color 586 * @throws IOException IOException 587 */ 588 protected void processSetBackgroundColor(int color) throws IOException { 589 processSetBackgroundColor(color, false); 590 } 591 592 /** 593 * process <code>SGR 40-47</code> or <code>SGR 100-107</code> corresponding to 594 * <code>Set background color</code> either in normal mode or high intensity. 595 * @param color the background color 596 * @param bright is high intensity? 597 * @throws IOException IOException 598 */ 599 protected void processSetBackgroundColor(int color, boolean bright) throws IOException { 600 } 601 602 /** 603 * process <code>SGR 48</code> corresponding to <code>extended set background color</code> 604 * with a palette of 255 colors. 605 * @param paletteIndex the background color in the palette 606 * @throws IOException IOException 607 */ 608 protected void processSetBackgroundColorExt(int paletteIndex) throws IOException { 609 } 610 611 /** 612 * process <code>SGR 48</code> corresponding to <code>extended set background color</code> 613 * with a 24 bits RGB definition of the color. 614 * @param r red 615 * @param g green 616 * @param b blue 617 * @throws IOException IOException 618 */ 619 protected void processSetBackgroundColorExt(int r, int g, int b) throws IOException { 620 } 621 622 /** 623 * process <code>SGR 39</code> corresponding to <code>Default text color (foreground)</code> 624 * @throws IOException IOException 625 */ 626 protected void processDefaultTextColor() throws IOException { 627 } 628 629 /** 630 * process <code>SGR 49</code> corresponding to <code>Default background color</code> 631 * @throws IOException IOException 632 */ 633 protected void processDefaultBackgroundColor() throws IOException { 634 } 635 636 /** 637 * process <code>SGR 0</code> corresponding to <code>Reset / Normal</code> 638 * @throws IOException IOException 639 */ 640 protected void processAttributeRest() throws IOException { 641 } 642 643 /** 644 * process <code>CSI n ; m H</code> corresponding to <code>CUP – Cursor Position</code> or 645 * <code>CSI n ; m f</code> corresponding to <code>HVP – Horizontal and Vertical Position</code> 646 * @param row row 647 * @param col col 648 * @throws IOException IOException 649 */ 650 protected void processCursorTo(int row, int col) throws IOException { 651 } 652 653 /** 654 * process <code>CSI n G</code> corresponding to <code>CHA – Cursor Horizontal Absolute</code> 655 * @param x the column 656 * @throws IOException IOException 657 */ 658 protected void processCursorToColumn(int x) throws IOException { 659 } 660 661 /** 662 * process <code>CSI n F</code> corresponding to <code>CPL – Cursor Previous Line</code> 663 * @param count line count 664 * @throws IOException IOException 665 */ 666 protected void processCursorUpLine(int count) throws IOException { 667 } 668 669 /** 670 * process <code>CSI n E</code> corresponding to <code>CNL – Cursor Next Line</code> 671 * @param count line count 672 * @throws IOException IOException 673 */ 674 protected void processCursorDownLine(int count) throws IOException { 675 // Poor mans impl.. 676 for (int i = 0; i < count; i++) { 677 ps.write('\n'); // expected diff with AnsiOutputStream.java 678 } 679 } 680 681 /** 682 * process <code>CSI n D</code> corresponding to <code>CUB – Cursor Back</code> 683 * @param count count 684 * @throws IOException IOException 685 */ 686 protected void processCursorLeft(int count) throws IOException { 687 } 688 689 /** 690 * process <code>CSI n C</code> corresponding to <code>CUF – Cursor Forward</code> 691 * @param count count 692 * @throws IOException IOException 693 */ 694 protected void processCursorRight(int count) throws IOException { 695 // Poor mans impl.. 696 for (int i = 0; i < count; i++) { 697 ps.write(' '); // expected diff with AnsiOutputStream.java 698 } 699 } 700 701 /** 702 * process <code>CSI n B</code> corresponding to <code>CUD – Cursor Down</code> 703 * @param count count 704 * @throws IOException IOException 705 */ 706 protected void processCursorDown(int count) throws IOException { 707 } 708 709 /** 710 * process <code>CSI n A</code> corresponding to <code>CUU – Cursor Up</code> 711 * @param count count 712 * @throws IOException IOException 713 */ 714 protected void processCursorUp(int count) throws IOException { 715 } 716 717 /** 718 * Process Unknown Extension 719 * @param options options 720 * @param command command 721 */ 722 protected void processUnknownExtension(ArrayList<Object> options, int command) { 723 } 724 725 /** 726 * process <code>OSC 0;text BEL</code> corresponding to <code>Change Window and Icon label</code> 727 * @param label window title name 728 */ 729 protected void processChangeIconNameAndWindowTitle(String label) { 730 processChangeIconName(label); 731 processChangeWindowTitle(label); 732 } 733 734 /** 735 * process <code>OSC 1;text BEL</code> corresponding to <code>Change Icon label</code> 736 * @param label icon label 737 */ 738 protected void processChangeIconName(String label) { 739 } 740 741 /** 742 * process <code>OSC 2;text BEL</code> corresponding to <code>Change Window title</code> 743 * @param label window title text 744 */ 745 protected void processChangeWindowTitle(String label) { 746 } 747 748 /** 749 * Process unknown <code>OSC</code> command. 750 * @param command command 751 * @param param param 752 */ 753 protected void processUnknownOperatingSystemCommand(int command, String param) { 754 } 755 756 /** 757 * Process character set sequence. 758 * @param options options 759 * @return true if the charcter set select command was processed. 760 */ 761 private boolean processCharsetSelect(ArrayList<Object> options) { 762 int set = optionInt(options, 0); 763 char seq = ((Character) options.get(1)).charValue(); 764 processCharsetSelect(set, seq); 765 return true; 766 } 767 768 protected void processCharsetSelect(int set, char seq) { 769 } 770 771 private int optionInt(ArrayList<Object> options, int index) { 772 if (options.size() <= index) 773 throw new IllegalArgumentException(); 774 Object value = options.get(index); 775 if (value == null) 776 throw new IllegalArgumentException(); 777 if (!value.getClass().equals(Integer.class)) 778 throw new IllegalArgumentException(); 779 return (Integer) value; 780 } 781 782 private int optionInt(ArrayList<Object> options, int index, int defaultValue) { 783 if (options.size() > index) { 784 Object value = options.get(index); 785 if (value == null) { 786 return defaultValue; 787 } 788 return (Integer) value; 789 } 790 return defaultValue; 791 } 792 793 @Override 794 public void close() { // expected diff with AnsiOutputStream.java 795 print(RESET_CODE); // expected diff with AnsiOutputStream.java 796 flush(); 797 super.close(); 798 } 799 800}