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.util.ArrayList; 019import java.util.concurrent.Callable; 020 021/** 022 * Provides a fluent API for generating ANSI escape sequences. 023 * 024 * @author <a href="http://hiramchirino.com">Hiram Chirino</a> 025 * @since 1.0 026 */ 027public class Ansi { 028 029 private static final char FIRST_ESC_CHAR = 27; 030 private static final char SECOND_ESC_CHAR = '['; 031 032 public enum Color { 033 BLACK(0, "BLACK"), 034 RED(1, "RED"), 035 GREEN(2, "GREEN"), 036 YELLOW(3, "YELLOW"), 037 BLUE(4, "BLUE"), 038 MAGENTA(5, "MAGENTA"), 039 CYAN(6, "CYAN"), 040 WHITE(7, "WHITE"), 041 DEFAULT(9, "DEFAULT"); 042 043 private final int value; 044 private final String name; 045 046 Color(int index, String name) { 047 this.value = index; 048 this.name = name; 049 } 050 051 @Override 052 public String toString() { 053 return name; 054 } 055 056 public int value() { 057 return value; 058 } 059 060 public int fg() { 061 return value + 30; 062 } 063 064 public int bg() { 065 return value + 40; 066 } 067 068 public int fgBright() { 069 return value + 90; 070 } 071 072 public int bgBright() { 073 return value + 100; 074 } 075 } 076 077 public enum Attribute { 078 RESET(0, "RESET"), 079 INTENSITY_BOLD(1, "INTENSITY_BOLD"), 080 INTENSITY_FAINT(2, "INTENSITY_FAINT"), 081 ITALIC(3, "ITALIC_ON"), 082 UNDERLINE(4, "UNDERLINE_ON"), 083 BLINK_SLOW(5, "BLINK_SLOW"), 084 BLINK_FAST(6, "BLINK_FAST"), 085 NEGATIVE_ON(7, "NEGATIVE_ON"), 086 CONCEAL_ON(8, "CONCEAL_ON"), 087 STRIKETHROUGH_ON(9, "STRIKETHROUGH_ON"), 088 UNDERLINE_DOUBLE(21, "UNDERLINE_DOUBLE"), 089 INTENSITY_BOLD_OFF(22, "INTENSITY_BOLD_OFF"), 090 ITALIC_OFF(23, "ITALIC_OFF"), 091 UNDERLINE_OFF(24, "UNDERLINE_OFF"), 092 BLINK_OFF(25, "BLINK_OFF"), 093 NEGATIVE_OFF(27, "NEGATIVE_OFF"), 094 CONCEAL_OFF(28, "CONCEAL_OFF"), 095 STRIKETHROUGH_OFF(29, "STRIKETHROUGH_OFF"); 096 097 private final int value; 098 private final String name; 099 100 Attribute(int index, String name) { 101 this.value = index; 102 this.name = name; 103 } 104 105 @Override 106 public String toString() { 107 return name; 108 } 109 110 public int value() { 111 return value; 112 } 113 114 } 115 116 public enum Erase { 117 FORWARD(0, "FORWARD"), 118 BACKWARD(1, "BACKWARD"), 119 ALL(2, "ALL"); 120 121 private final int value; 122 private final String name; 123 124 Erase(int index, String name) { 125 this.value = index; 126 this.name = name; 127 } 128 129 @Override 130 public String toString() { 131 return name; 132 } 133 134 public int value() { 135 return value; 136 } 137 } 138 139 public static final String DISABLE = Ansi.class.getName() + ".disable"; 140 141 private static Callable<Boolean> detector = new Callable<Boolean>() { 142 public Boolean call() throws Exception { 143 return !Boolean.getBoolean(DISABLE); 144 } 145 }; 146 147 public static void setDetector(final Callable<Boolean> detector) { 148 if (detector == null) throw new IllegalArgumentException(); 149 Ansi.detector = detector; 150 } 151 152 public static boolean isDetected() { 153 try { 154 return detector.call(); 155 } catch (Exception e) { 156 return true; 157 } 158 } 159 160 private static final InheritableThreadLocal<Boolean> holder = new InheritableThreadLocal<Boolean>() { 161 @Override 162 protected Boolean initialValue() { 163 return isDetected(); 164 } 165 }; 166 167 public static void setEnabled(final boolean flag) { 168 holder.set(flag); 169 } 170 171 public static boolean isEnabled() { 172 return holder.get(); 173 } 174 175 public static Ansi ansi() { 176 if (isEnabled()) { 177 return new Ansi(); 178 } else { 179 return new NoAnsi(); 180 } 181 } 182 183 public static Ansi ansi(StringBuilder builder) { 184 if (isEnabled()) { 185 return new Ansi(builder); 186 } else { 187 return new NoAnsi(builder); 188 } 189 } 190 191 public static Ansi ansi(int size) { 192 if (isEnabled()) { 193 return new Ansi(size); 194 } else { 195 return new NoAnsi(size); 196 } 197 } 198 199 private static class NoAnsi 200 extends Ansi { 201 public NoAnsi() { 202 super(); 203 } 204 205 public NoAnsi(int size) { 206 super(size); 207 } 208 209 public NoAnsi(StringBuilder builder) { 210 super(builder); 211 } 212 213 @Override 214 public Ansi fg(Color color) { 215 return this; 216 } 217 218 @Override 219 public Ansi bg(Color color) { 220 return this; 221 } 222 223 @Override 224 public Ansi fgBright(Color color) { 225 return this; 226 } 227 228 @Override 229 public Ansi bgBright(Color color) { 230 return this; 231 } 232 233 @Override 234 public Ansi a(Attribute attribute) { 235 return this; 236 } 237 238 @Override 239 public Ansi cursor(int row, int column) { 240 return this; 241 } 242 243 @Override 244 public Ansi cursorToColumn(int x) { 245 return this; 246 } 247 248 @Override 249 public Ansi cursorUp(int y) { 250 return this; 251 } 252 253 @Override 254 public Ansi cursorRight(int x) { 255 return this; 256 } 257 258 @Override 259 public Ansi cursorDown(int y) { 260 return this; 261 } 262 263 @Override 264 public Ansi cursorLeft(int x) { 265 return this; 266 } 267 268 @Override 269 public Ansi cursorDownLine() { 270 return this; 271 } 272 273 @Override 274 public Ansi cursorDownLine(final int n) { 275 return this; 276 } 277 278 @Override 279 public Ansi cursorUpLine() { 280 return this; 281 } 282 283 @Override 284 public Ansi cursorUpLine(final int n) { 285 return this; 286 } 287 288 @Override 289 public Ansi eraseScreen() { 290 return this; 291 } 292 293 @Override 294 public Ansi eraseScreen(Erase kind) { 295 return this; 296 } 297 298 @Override 299 public Ansi eraseLine() { 300 return this; 301 } 302 303 @Override 304 public Ansi eraseLine(Erase kind) { 305 return this; 306 } 307 308 @Override 309 public Ansi scrollUp(int rows) { 310 return this; 311 } 312 313 @Override 314 public Ansi scrollDown(int rows) { 315 return this; 316 } 317 318 @Override 319 public Ansi saveCursorPosition() { 320 return this; 321 } 322 323 @Override 324 @Deprecated 325 public Ansi restorCursorPosition() { 326 return this; 327 } 328 329 @Override 330 public Ansi restoreCursorPosition() { 331 return this; 332 } 333 334 @Override 335 public Ansi reset() { 336 return this; 337 } 338 } 339 340 private final StringBuilder builder; 341 private final ArrayList<Integer> attributeOptions = new ArrayList<Integer>(5); 342 343 public Ansi() { 344 this(new StringBuilder()); 345 } 346 347 public Ansi(Ansi parent) { 348 this(new StringBuilder(parent.builder)); 349 attributeOptions.addAll(parent.attributeOptions); 350 } 351 352 public Ansi(int size) { 353 this(new StringBuilder(size)); 354 } 355 356 public Ansi(StringBuilder builder) { 357 this.builder = builder; 358 } 359 360 public Ansi fg(Color color) { 361 attributeOptions.add(color.fg()); 362 return this; 363 } 364 365 public Ansi fgBlack() { 366 return this.fg(Color.BLACK); 367 } 368 369 public Ansi fgBlue() { 370 return this.fg(Color.BLUE); 371 } 372 373 public Ansi fgCyan() { 374 return this.fg(Color.CYAN); 375 } 376 377 public Ansi fgDefault() { 378 return this.fg(Color.DEFAULT); 379 } 380 381 public Ansi fgGreen() { 382 return this.fg(Color.GREEN); 383 } 384 385 public Ansi fgMagenta() { 386 return this.fg(Color.MAGENTA); 387 } 388 389 public Ansi fgRed() { 390 return this.fg(Color.RED); 391 } 392 393 public Ansi fgYellow() { 394 return this.fg(Color.YELLOW); 395 } 396 397 public Ansi bg(Color color) { 398 attributeOptions.add(color.bg()); 399 return this; 400 } 401 402 public Ansi bgCyan() { 403 return this.fg(Color.CYAN); 404 } 405 406 public Ansi bgDefault() { 407 return this.bg(Color.DEFAULT); 408 } 409 410 public Ansi bgGreen() { 411 return this.bg(Color.GREEN); 412 } 413 414 public Ansi bgMagenta() { 415 return this.bg(Color.MAGENTA); 416 } 417 418 public Ansi bgRed() { 419 return this.bg(Color.RED); 420 } 421 422 public Ansi bgYellow() { 423 return this.bg(Color.YELLOW); 424 } 425 426 public Ansi fgBright(Color color) { 427 attributeOptions.add(color.fgBright()); 428 return this; 429 } 430 431 public Ansi fgBrightBlack() { 432 return this.fgBright(Color.BLACK); 433 } 434 435 public Ansi fgBrightBlue() { 436 return this.fgBright(Color.BLUE); 437 } 438 439 public Ansi fgBrightCyan() { 440 return this.fgBright(Color.CYAN); 441 } 442 443 public Ansi fgBrightDefault() { 444 return this.fgBright(Color.DEFAULT); 445 } 446 447 public Ansi fgBrightGreen() { 448 return this.fgBright(Color.GREEN); 449 } 450 451 public Ansi fgBrightMagenta() { 452 return this.fgBright(Color.MAGENTA); 453 } 454 455 public Ansi fgBrightRed() { 456 return this.fgBright(Color.RED); 457 } 458 459 public Ansi fgBrightYellow() { 460 return this.fgBright(Color.YELLOW); 461 } 462 463 public Ansi bgBright(Color color) { 464 attributeOptions.add(color.bgBright()); 465 return this; 466 } 467 468 public Ansi bgBrightCyan() { 469 return this.fgBright(Color.CYAN); 470 } 471 472 public Ansi bgBrightDefault() { 473 return this.bgBright(Color.DEFAULT); 474 } 475 476 public Ansi bgBrightGreen() { 477 return this.bgBright(Color.GREEN); 478 } 479 480 public Ansi bgBrightMagenta() { 481 return this.bg(Color.MAGENTA); 482 } 483 484 public Ansi bgBrightRed() { 485 return this.bgBright(Color.RED); 486 } 487 488 public Ansi bgBrightYellow() { 489 return this.bgBright(Color.YELLOW); 490 } 491 492 public Ansi a(Attribute attribute) { 493 attributeOptions.add(attribute.value()); 494 return this; 495 } 496 497 /** 498 * Moves the cursor to row n, column m. 499 * The values are 1-based, and default to 1 (top left corner) if omitted. 500 * A sequence such as CSI ;5H is a synonym for CSI 1;5H as well as CSI 17;H is the same as CSI 17H and CSI 17;1H 501 * 502 * @param row row (1-based) from top 503 * @param column column (1 based) from left 504 * @return Ansi 505 */ 506 public Ansi cursor(final int row, final int column) { 507 return appendEscapeSequence('H', row, column); 508 } 509 510 public Ansi cursorToColumn(final int x) { 511 return appendEscapeSequence('G', x); 512 } 513 514 public Ansi cursorUp(final int y) { 515 return appendEscapeSequence('A', y); 516 } 517 518 public Ansi cursorDown(final int y) { 519 return appendEscapeSequence('B', y); 520 } 521 522 public Ansi cursorRight(final int x) { 523 return appendEscapeSequence('C', x); 524 } 525 526 public Ansi cursorLeft(final int x) { 527 return appendEscapeSequence('D', x); 528 } 529 530 public Ansi cursorDownLine() { 531 return appendEscapeSequence('E'); 532 } 533 534 public Ansi cursorDownLine(final int n) { 535 return appendEscapeSequence('E', n); 536 } 537 538 public Ansi cursorUpLine() { 539 return appendEscapeSequence('F'); 540 } 541 542 public Ansi cursorUpLine(final int n) { 543 return appendEscapeSequence('F', n); 544 } 545 546 public Ansi eraseScreen() { 547 return appendEscapeSequence('J', Erase.ALL.value()); 548 } 549 550 public Ansi eraseScreen(final Erase kind) { 551 return appendEscapeSequence('J', kind.value()); 552 } 553 554 public Ansi eraseLine() { 555 return appendEscapeSequence('K'); 556 } 557 558 public Ansi eraseLine(final Erase kind) { 559 return appendEscapeSequence('K', kind.value()); 560 } 561 562 public Ansi scrollUp(final int rows) { 563 return appendEscapeSequence('S', rows); 564 } 565 566 public Ansi scrollDown(final int rows) { 567 return appendEscapeSequence('T', rows); 568 } 569 570 public Ansi saveCursorPosition() { 571 return appendEscapeSequence('s'); 572 } 573 574 @Deprecated 575 public Ansi restorCursorPosition() { 576 return appendEscapeSequence('u'); 577 } 578 579 public Ansi restoreCursorPosition() { 580 return appendEscapeSequence('u'); 581 } 582 583 public Ansi reset() { 584 return a(Attribute.RESET); 585 } 586 587 public Ansi bold() { 588 return a(Attribute.INTENSITY_BOLD); 589 } 590 591 public Ansi boldOff() { 592 return a(Attribute.INTENSITY_BOLD_OFF); 593 } 594 595 public Ansi a(String value) { 596 flushAttributes(); 597 builder.append(value); 598 return this; 599 } 600 601 public Ansi a(boolean value) { 602 flushAttributes(); 603 builder.append(value); 604 return this; 605 } 606 607 public Ansi a(char value) { 608 flushAttributes(); 609 builder.append(value); 610 return this; 611 } 612 613 public Ansi a(char[] value, int offset, int len) { 614 flushAttributes(); 615 builder.append(value, offset, len); 616 return this; 617 } 618 619 public Ansi a(char[] value) { 620 flushAttributes(); 621 builder.append(value); 622 return this; 623 } 624 625 public Ansi a(CharSequence value, int start, int end) { 626 flushAttributes(); 627 builder.append(value, start, end); 628 return this; 629 } 630 631 public Ansi a(CharSequence value) { 632 flushAttributes(); 633 builder.append(value); 634 return this; 635 } 636 637 public Ansi a(double value) { 638 flushAttributes(); 639 builder.append(value); 640 return this; 641 } 642 643 public Ansi a(float value) { 644 flushAttributes(); 645 builder.append(value); 646 return this; 647 } 648 649 public Ansi a(int value) { 650 flushAttributes(); 651 builder.append(value); 652 return this; 653 } 654 655 public Ansi a(long value) { 656 flushAttributes(); 657 builder.append(value); 658 return this; 659 } 660 661 public Ansi a(Object value) { 662 flushAttributes(); 663 builder.append(value); 664 return this; 665 } 666 667 public Ansi a(StringBuffer value) { 668 flushAttributes(); 669 builder.append(value); 670 return this; 671 } 672 673 public Ansi newline() { 674 flushAttributes(); 675 builder.append(System.getProperty("line.separator")); 676 return this; 677 } 678 679 public Ansi format(String pattern, Object... args) { 680 flushAttributes(); 681 builder.append(String.format(pattern, args)); 682 return this; 683 } 684 685 /** 686 * Uses the {@link AnsiRenderer} 687 * to generate the ANSI escape sequences for the supplied text. 688 * 689 * @param text text 690 * @return this 691 * 692 * @since 1.1 693 */ 694 public Ansi render(final String text) { 695 a(AnsiRenderer.render(text)); 696 return this; 697 } 698 699 /** 700 * String formats and renders the supplied arguments. Uses the {@link AnsiRenderer} 701 * to generate the ANSI escape sequences. 702 * 703 * @param text format 704 * @param args arguments 705 * @return this 706 * 707 * @since 1.1 708 */ 709 public Ansi render(final String text, Object... args) { 710 a(String.format(AnsiRenderer.render(text), args)); 711 return this; 712 } 713 714 @Override 715 public String toString() { 716 flushAttributes(); 717 return builder.toString(); 718 } 719 720 /////////////////////////////////////////////////////////////////// 721 // Private Helper Methods 722 /////////////////////////////////////////////////////////////////// 723 724 private Ansi appendEscapeSequence(char command) { 725 flushAttributes(); 726 builder.append(FIRST_ESC_CHAR); 727 builder.append(SECOND_ESC_CHAR); 728 builder.append(command); 729 return this; 730 } 731 732 private Ansi appendEscapeSequence(char command, int option) { 733 flushAttributes(); 734 builder.append(FIRST_ESC_CHAR); 735 builder.append(SECOND_ESC_CHAR); 736 builder.append(option); 737 builder.append(command); 738 return this; 739 } 740 741 private Ansi appendEscapeSequence(char command, Object... options) { 742 flushAttributes(); 743 return _appendEscapeSequence(command, options); 744 } 745 746 private void flushAttributes() { 747 if (attributeOptions.isEmpty()) 748 return; 749 if (attributeOptions.size() == 1 && attributeOptions.get(0) == 0) { 750 builder.append(FIRST_ESC_CHAR); 751 builder.append(SECOND_ESC_CHAR); 752 builder.append('m'); 753 } else { 754 _appendEscapeSequence('m', attributeOptions.toArray()); 755 } 756 attributeOptions.clear(); 757 } 758 759 private Ansi _appendEscapeSequence(char command, Object... options) { 760 builder.append(FIRST_ESC_CHAR); 761 builder.append(SECOND_ESC_CHAR); 762 int size = options.length; 763 for (int i = 0; i < size; i++) { 764 if (i != 0) { 765 builder.append(';'); 766 } 767 if (options[i] != null) { 768 builder.append(options[i]); 769 } 770 } 771 builder.append(command); 772 return this; 773 } 774 775}