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