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