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.io; 017 018import java.io.IOException; 019import java.io.OutputStream; 020import java.util.ArrayList; 021import java.util.Iterator; 022 023/** 024 * ANSI processor providing <code>process*</code> corresponding to ANSI escape codes. 025 * This class methods implementations are empty: subclasses should actually perform the 026 * ANSI escape behaviors by implementing active code in <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 * @since 1.19 032 */ 033@SuppressWarnings("unused") 034public class AnsiProcessor { 035 protected final OutputStream os; 036 037 public AnsiProcessor(OutputStream os) { 038 this.os = os; 039 } 040 041 /** 042 * Helper for processEscapeCommand() to iterate over integer options 043 * @param optionsIterator the underlying iterator 044 * @throws IOException if no more non-null values left 045 */ 046 protected int getNextOptionInt(Iterator<Object> optionsIterator) throws IOException { 047 for (;;) { 048 if (!optionsIterator.hasNext()) 049 throw new IllegalArgumentException(); 050 Object arg = optionsIterator.next(); 051 if (arg != null) 052 return (Integer) arg; 053 } 054 } 055 056 /** 057 * @return true if the escape command was processed. 058 */ 059 protected boolean processEscapeCommand(ArrayList<Object> options, int command) throws IOException { 060 try { 061 switch (command) { 062 case 'A': 063 processCursorUp(optionInt(options, 0, 1)); 064 return true; 065 case 'B': 066 processCursorDown(optionInt(options, 0, 1)); 067 return true; 068 case 'C': 069 processCursorRight(optionInt(options, 0, 1)); 070 return true; 071 case 'D': 072 processCursorLeft(optionInt(options, 0, 1)); 073 return true; 074 case 'E': 075 processCursorDownLine(optionInt(options, 0, 1)); 076 return true; 077 case 'F': 078 processCursorUpLine(optionInt(options, 0, 1)); 079 return true; 080 case 'G': 081 processCursorToColumn(optionInt(options, 0)); 082 return true; 083 case 'H': 084 case 'f': 085 processCursorTo(optionInt(options, 0, 1), optionInt(options, 1, 1)); 086 return true; 087 case 'J': 088 processEraseScreen(optionInt(options, 0, 0)); 089 return true; 090 case 'K': 091 processEraseLine(optionInt(options, 0, 0)); 092 return true; 093 case 'L': 094 processInsertLine(optionInt(options, 0, 1)); 095 return true; 096 case 'M': 097 processDeleteLine(optionInt(options, 0, 1)); 098 return true; 099 case 'S': 100 processScrollUp(optionInt(options, 0, 1)); 101 return true; 102 case 'T': 103 processScrollDown(optionInt(options, 0, 1)); 104 return true; 105 case 'm': 106 // Validate all options are ints... 107 for (Object next : options) { 108 if (next != null && next.getClass() != Integer.class) { 109 throw new IllegalArgumentException(); 110 } 111 } 112 113 int count = 0; 114 Iterator<Object> optionsIterator = options.iterator(); 115 while (optionsIterator.hasNext()) { 116 Object next = optionsIterator.next(); 117 if (next != null) { 118 count++; 119 int value = (Integer) next; 120 if (30 <= value && value <= 37) { 121 processSetForegroundColor(value - 30); 122 } else if (40 <= value && value <= 47) { 123 processSetBackgroundColor(value - 40); 124 } else if (90 <= value && value <= 97) { 125 processSetForegroundColor(value - 90, true); 126 } else if (100 <= value && value <= 107) { 127 processSetBackgroundColor(value - 100, true); 128 } else if (value == 38 || value == 48) { 129 if (!optionsIterator.hasNext()) { 130 continue; 131 } 132 // extended color like `esc[38;5;<index>m` or `esc[38;2;<r>;<g>;<b>m` 133 int arg2or5 = getNextOptionInt(optionsIterator); 134 if (arg2or5 == 2) { 135 // 24 bit color style like `esc[38;2;<r>;<g>;<b>m` 136 int r = getNextOptionInt(optionsIterator); 137 int g = getNextOptionInt(optionsIterator); 138 int b = getNextOptionInt(optionsIterator); 139 if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) { 140 if (value == 38) 141 processSetForegroundColorExt(r, g, b); 142 else 143 processSetBackgroundColorExt(r, g, b); 144 } else { 145 throw new IllegalArgumentException(); 146 } 147 } 148 else if (arg2or5 == 5) { 149 // 256 color style like `esc[38;5;<index>m` 150 int paletteIndex = getNextOptionInt(optionsIterator); 151 if (paletteIndex >= 0 && paletteIndex <= 255) { 152 if (value == 38) 153 processSetForegroundColorExt(paletteIndex); 154 else 155 processSetBackgroundColorExt(paletteIndex); 156 } else { 157 throw new IllegalArgumentException(); 158 } 159 } 160 else { 161 throw new IllegalArgumentException(); 162 } 163 } else { 164 switch (value) { 165 case 39: 166 processDefaultTextColor(); 167 break; 168 case 49: 169 processDefaultBackgroundColor(); 170 break; 171 case 0: 172 processAttributeReset(); 173 break; 174 default: 175 processSetAttribute(value); 176 } 177 } 178 } 179 } 180 if (count == 0) { 181 processAttributeReset(); 182 } 183 return true; 184 case 's': 185 processSaveCursorPosition(); 186 return true; 187 case 'u': 188 processRestoreCursorPosition(); 189 return true; 190 191 default: 192 if ('a' <= command && command <= 'z') { 193 processUnknownExtension(options, command); 194 return true; 195 } 196 if ('A' <= command && command <= 'Z') { 197 processUnknownExtension(options, command); 198 return true; 199 } 200 return false; 201 } 202 } catch (IllegalArgumentException ignore) { 203 } 204 return false; 205 } 206 207 /** 208 * @return true if the operating system command was processed. 209 */ 210 protected boolean processOperatingSystemCommand(ArrayList<Object> options) { 211 int command = optionInt(options, 0); 212 String label = (String) options.get(1); 213 // for command > 2 label could be composed (i.e. contain ';'), but we'll leave 214 // it to processUnknownOperatingSystemCommand implementations to handle that 215 try { 216 switch (command) { 217 case 0: 218 processChangeIconNameAndWindowTitle(label); 219 return true; 220 case 1: 221 processChangeIconName(label); 222 return true; 223 case 2: 224 processChangeWindowTitle(label); 225 return true; 226 227 default: 228 // not exactly unknown, but not supported through dedicated process methods: 229 processUnknownOperatingSystemCommand(command, label); 230 return true; 231 } 232 } catch (IllegalArgumentException ignore) { 233 } 234 return false; 235 } 236 237 /** 238 * Process character set sequence. 239 * @param options options 240 * @return true if the charcter set select command was processed. 241 */ 242 protected boolean processCharsetSelect(ArrayList<Object> options) { 243 int set = optionInt(options, 0); 244 char seq = (Character) options.get(1); 245 processCharsetSelect(set, seq); 246 return true; 247 } 248 249 private int optionInt(ArrayList<Object> options, int index) { 250 if (options.size() <= index) 251 throw new IllegalArgumentException(); 252 Object value = options.get(index); 253 if (value == null) 254 throw new IllegalArgumentException(); 255 if (!value.getClass().equals(Integer.class)) 256 throw new IllegalArgumentException(); 257 return (Integer) value; 258 } 259 260 private int optionInt(ArrayList<Object> options, int index, int defaultValue) { 261 if (options.size() > index) { 262 Object value = options.get(index); 263 if (value == null) { 264 return defaultValue; 265 } 266 return (Integer) value; 267 } 268 return defaultValue; 269 } 270 271 /** 272 * Process <code>CSI u</code> ANSI code, corresponding to <code>RCP – Restore Cursor Position</code> 273 * @throws IOException IOException 274 */ 275 protected void processRestoreCursorPosition() throws IOException { 276 } 277 278 /** 279 * Process <code>CSI s</code> ANSI code, corresponding to <code>SCP – Save Cursor Position</code> 280 * @throws IOException IOException 281 */ 282 protected void processSaveCursorPosition() throws IOException { 283 } 284 285 /** 286 * Process <code>CSI L</code> ANSI code, corresponding to <code>IL – Insert Line</code> 287 * @param optionInt option 288 * @throws IOException IOException 289 * @since 1.16 290 */ 291 protected void processInsertLine(int optionInt) throws IOException { 292 } 293 294 /** 295 * Process <code>CSI M</code> ANSI code, corresponding to <code>DL – Delete Line</code> 296 * @param optionInt option 297 * @throws IOException IOException 298 * @since 1.16 299 */ 300 protected void processDeleteLine(int optionInt) throws IOException { 301 } 302 303 /** 304 * Process <code>CSI n T</code> ANSI code, corresponding to <code>SD – Scroll Down</code> 305 * @param optionInt option 306 * @throws IOException IOException 307 */ 308 protected void processScrollDown(int optionInt) throws IOException { 309 } 310 311 /** 312 * Process <code>CSI n U</code> ANSI code, corresponding to <code>SU – Scroll Up</code> 313 * @param optionInt option 314 * @throws IOException IOException 315 */ 316 protected void processScrollUp(int optionInt) throws IOException { 317 } 318 319 protected static final int ERASE_SCREEN_TO_END = 0; 320 protected static final int ERASE_SCREEN_TO_BEGINING = 1; 321 protected static final int ERASE_SCREEN = 2; 322 323 /** 324 * Process <code>CSI n J</code> ANSI code, corresponding to <code>ED – Erase in Display</code> 325 * @param eraseOption eraseOption 326 * @throws IOException IOException 327 */ 328 protected void processEraseScreen(int eraseOption) throws IOException { 329 } 330 331 protected static final int ERASE_LINE_TO_END = 0; 332 protected static final int ERASE_LINE_TO_BEGINING = 1; 333 protected static final int ERASE_LINE = 2; 334 335 /** 336 * Process <code>CSI n K</code> ANSI code, corresponding to <code>ED – Erase in Line</code> 337 * @param eraseOption eraseOption 338 * @throws IOException IOException 339 */ 340 protected void processEraseLine(int eraseOption) throws IOException { 341 } 342 343 protected static final int ATTRIBUTE_INTENSITY_BOLD = 1; // Intensity: Bold 344 protected static final int ATTRIBUTE_INTENSITY_FAINT = 2; // Intensity; Faint not widely supported 345 protected static final int ATTRIBUTE_ITALIC = 3; // Italic; on not widely supported. Sometimes treated as inverse. 346 protected static final int ATTRIBUTE_UNDERLINE = 4; // Underline; Single 347 protected static final int ATTRIBUTE_BLINK_SLOW = 5; // Blink; Slow less than 150 per minute 348 protected static final int ATTRIBUTE_BLINK_FAST = 6; // Blink; Rapid MS-DOS ANSI.SYS; 150 per minute or more 349 protected static final int ATTRIBUTE_NEGATIVE_ON = 7; // Image; Negative inverse or reverse; swap foreground and background 350 protected static final int ATTRIBUTE_CONCEAL_ON = 8; // Conceal on 351 protected static final int ATTRIBUTE_UNDERLINE_DOUBLE = 21; // Underline; Double not widely supported 352 protected static final int ATTRIBUTE_INTENSITY_NORMAL = 22; // Intensity; Normal not bold and not faint 353 protected static final int ATTRIBUTE_UNDERLINE_OFF = 24; // Underline; None 354 protected static final int ATTRIBUTE_BLINK_OFF = 25; // Blink; off 355 protected static final int ATTRIBUTE_NEGATIVE_OFF = 27; // Image; Positive 356 protected static final int ATTRIBUTE_CONCEAL_OFF = 28; // Reveal conceal off 357 358 /** 359 * process <code>SGR</code> other than <code>0</code> (reset), <code>30-39</code> (foreground), 360 * <code>40-49</code> (background), <code>90-97</code> (foreground high intensity) or 361 * <code>100-107</code> (background high intensity) 362 * @param attribute attribute 363 * @throws IOException IOException 364 * @see #processAttributeReset() 365 * @see #processSetForegroundColor(int) 366 * @see #processSetForegroundColor(int, boolean) 367 * @see #processSetForegroundColorExt(int) 368 * @see #processSetForegroundColorExt(int, int, int) 369 * @see #processDefaultTextColor() 370 * @see #processDefaultBackgroundColor() 371 */ 372 protected void processSetAttribute(int attribute) throws IOException { 373 } 374 375 protected static final int BLACK = 0; 376 protected static final int RED = 1; 377 protected static final int GREEN = 2; 378 protected static final int YELLOW = 3; 379 protected static final int BLUE = 4; 380 protected static final int MAGENTA = 5; 381 protected static final int CYAN = 6; 382 protected static final int WHITE = 7; 383 384 /** 385 * process <code>SGR 30-37</code> corresponding to <code>Set text color (foreground)</code>. 386 * @param color the text color 387 * @throws IOException IOException 388 */ 389 protected void processSetForegroundColor(int color) throws IOException { 390 processSetForegroundColor(color, false); 391 } 392 393 /** 394 * process <code>SGR 30-37</code> or <code>SGR 90-97</code> corresponding to 395 * <code>Set text color (foreground)</code> either in normal mode or high intensity. 396 * @param color the text color 397 * @param bright is high intensity? 398 * @throws IOException IOException 399 */ 400 protected void processSetForegroundColor(int color, boolean bright) throws IOException { 401 } 402 403 /** 404 * process <code>SGR 38</code> corresponding to <code>extended set text color (foreground)</code> 405 * with a palette of 255 colors. 406 * @param paletteIndex the text color in the palette 407 * @throws IOException IOException 408 */ 409 protected void processSetForegroundColorExt(int paletteIndex) throws IOException { 410 } 411 412 /** 413 * process <code>SGR 38</code> corresponding to <code>extended set text color (foreground)</code> 414 * with a 24 bits RGB definition of the color. 415 * @param r red 416 * @param g green 417 * @param b blue 418 * @throws IOException IOException 419 */ 420 protected void processSetForegroundColorExt(int r, int g, int b) throws IOException { 421 } 422 423 /** 424 * process <code>SGR 40-47</code> corresponding to <code>Set background color</code>. 425 * @param color the background color 426 * @throws IOException IOException 427 */ 428 protected void processSetBackgroundColor(int color) throws IOException { 429 processSetBackgroundColor(color, false); 430 } 431 432 /** 433 * process <code>SGR 40-47</code> or <code>SGR 100-107</code> corresponding to 434 * <code>Set background color</code> either in normal mode or high intensity. 435 * @param color the background color 436 * @param bright is high intensity? 437 * @throws IOException IOException 438 */ 439 protected void processSetBackgroundColor(int color, boolean bright) throws IOException { 440 } 441 442 /** 443 * process <code>SGR 48</code> corresponding to <code>extended set background color</code> 444 * with a palette of 255 colors. 445 * @param paletteIndex the background color in the palette 446 * @throws IOException IOException 447 */ 448 protected void processSetBackgroundColorExt(int paletteIndex) throws IOException { 449 } 450 451 /** 452 * process <code>SGR 48</code> corresponding to <code>extended set background color</code> 453 * with a 24 bits RGB definition of the color. 454 * @param r red 455 * @param g green 456 * @param b blue 457 * @throws IOException IOException 458 */ 459 protected void processSetBackgroundColorExt(int r, int g, int b) throws IOException { 460 } 461 462 /** 463 * process <code>SGR 39</code> corresponding to <code>Default text color (foreground)</code> 464 * @throws IOException IOException 465 */ 466 protected void processDefaultTextColor() throws IOException { 467 } 468 469 /** 470 * process <code>SGR 49</code> corresponding to <code>Default background color</code> 471 * @throws IOException IOException 472 */ 473 protected void processDefaultBackgroundColor() throws IOException { 474 } 475 476 /** 477 * process <code>SGR 0</code> corresponding to <code>Reset / Normal</code> 478 * @throws IOException IOException 479 */ 480 protected void processAttributeReset() throws IOException { 481 } 482 483 /** 484 * process <code>CSI n ; m H</code> corresponding to <code>CUP – Cursor Position</code> or 485 * <code>CSI n ; m f</code> corresponding to <code>HVP – Horizontal and Vertical Position</code> 486 * @param row row 487 * @param col col 488 * @throws IOException IOException 489 */ 490 protected void processCursorTo(int row, int col) throws IOException { 491 } 492 493 /** 494 * process <code>CSI n G</code> corresponding to <code>CHA – Cursor Horizontal Absolute</code> 495 * @param x the column 496 * @throws IOException IOException 497 */ 498 protected void processCursorToColumn(int x) throws IOException { 499 } 500 501 /** 502 * process <code>CSI n F</code> corresponding to <code>CPL – Cursor Previous Line</code> 503 * @param count line count 504 * @throws IOException IOException 505 */ 506 protected void processCursorUpLine(int count) throws IOException { 507 } 508 509 /** 510 * process <code>CSI n E</code> corresponding to <code>CNL – Cursor Next Line</code> 511 * @param count line count 512 * @throws IOException IOException 513 */ 514 protected void processCursorDownLine(int count) throws IOException { 515 // Poor mans impl.. 516 for (int i = 0; i < count; i++) { 517 os.write('\n'); 518 } 519 } 520 521 /** 522 * process <code>CSI n D</code> corresponding to <code>CUB – Cursor Back</code> 523 * @param count count 524 * @throws IOException IOException 525 */ 526 protected void processCursorLeft(int count) throws IOException { 527 } 528 529 /** 530 * process <code>CSI n C</code> corresponding to <code>CUF – Cursor Forward</code> 531 * @param count count 532 * @throws IOException IOException 533 */ 534 protected void processCursorRight(int count) throws IOException { 535 // Poor mans impl.. 536 for (int i = 0; i < count; i++) { 537 os.write(' '); 538 } 539 } 540 541 /** 542 * process <code>CSI n B</code> corresponding to <code>CUD – Cursor Down</code> 543 * @param count count 544 * @throws IOException IOException 545 */ 546 protected void processCursorDown(int count) throws IOException { 547 } 548 549 /** 550 * process <code>CSI n A</code> corresponding to <code>CUU – Cursor Up</code> 551 * @param count count 552 * @throws IOException IOException 553 */ 554 protected void processCursorUp(int count) throws IOException { 555 } 556 557 /** 558 * Process Unknown Extension 559 * @param options options 560 * @param command command 561 */ 562 protected void processUnknownExtension(ArrayList<Object> options, int command) { 563 } 564 565 /** 566 * process <code>OSC 0;text BEL</code> corresponding to <code>Change Window and Icon label</code> 567 * @param label window title name 568 */ 569 protected void processChangeIconNameAndWindowTitle(String label) { 570 processChangeIconName(label); 571 processChangeWindowTitle(label); 572 } 573 574 /** 575 * process <code>OSC 1;text BEL</code> corresponding to <code>Change Icon label</code> 576 * @param label icon label 577 */ 578 protected void processChangeIconName(String label) { 579 } 580 581 /** 582 * process <code>OSC 2;text BEL</code> corresponding to <code>Change Window title</code> 583 * @param label window title text 584 */ 585 protected void processChangeWindowTitle(String label) { 586 } 587 588 /** 589 * Process unknown <code>OSC</code> command. 590 * @param command command 591 * @param param param 592 */ 593 protected void processUnknownOperatingSystemCommand(int command, String param) { 594 } 595 596 protected void processCharsetSelect(int set, char seq) { 597 } 598}