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 implements Appendable { 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 interface Consumer { 155 void apply(Ansi ansi); 156 } 157 158 public static final String DISABLE = Ansi.class.getName() + ".disable"; 159 160 private static Callable<Boolean> detector = new Callable<Boolean>() { 161 public Boolean call() throws Exception { 162 return !Boolean.getBoolean(DISABLE); 163 } 164 }; 165 166 public static void setDetector(final Callable<Boolean> detector) { 167 if (detector == null) throw new IllegalArgumentException(); 168 Ansi.detector = detector; 169 } 170 171 public static boolean isDetected() { 172 try { 173 return detector.call(); 174 } catch (Exception e) { 175 return true; 176 } 177 } 178 179 private static final InheritableThreadLocal<Boolean> holder = new InheritableThreadLocal<Boolean>() { 180 @Override 181 protected Boolean initialValue() { 182 return isDetected(); 183 } 184 }; 185 186 public static void setEnabled(final boolean flag) { 187 holder.set(flag); 188 } 189 190 public static boolean isEnabled() { 191 return holder.get(); 192 } 193 194 public static Ansi ansi() { 195 if (isEnabled()) { 196 return new Ansi(); 197 } else { 198 return new NoAnsi(); 199 } 200 } 201 202 public static Ansi ansi(StringBuilder builder) { 203 if (isEnabled()) { 204 return new Ansi(builder); 205 } else { 206 return new NoAnsi(builder); 207 } 208 } 209 210 public static Ansi ansi(int size) { 211 if (isEnabled()) { 212 return new Ansi(size); 213 } else { 214 return new NoAnsi(size); 215 } 216 } 217 218 private static class NoAnsi 219 extends Ansi { 220 public NoAnsi() { 221 super(); 222 } 223 224 public NoAnsi(int size) { 225 super(size); 226 } 227 228 public NoAnsi(StringBuilder builder) { 229 super(builder); 230 } 231 232 @Override 233 public Ansi fg(Color color) { 234 return this; 235 } 236 237 @Override 238 public Ansi bg(Color color) { 239 return this; 240 } 241 242 @Override 243 public Ansi fgBright(Color color) { 244 return this; 245 } 246 247 @Override 248 public Ansi bgBright(Color color) { 249 return this; 250 } 251 252 @Override 253 public Ansi fg(int color) { 254 return this; 255 } 256 257 @Override 258 public Ansi fgRgb(int r, int g, int b) { 259 return this; 260 } 261 262 @Override 263 public Ansi bg(int color) { 264 return this; 265 } 266 267 @Override 268 public Ansi bgRgb(int r, int g, int b) { 269 return this; 270 } 271 272 @Override 273 public Ansi a(Attribute attribute) { 274 return this; 275 } 276 277 @Override 278 public Ansi cursor(int row, int column) { 279 return this; 280 } 281 282 @Override 283 public Ansi cursorToColumn(int x) { 284 return this; 285 } 286 287 @Override 288 public Ansi cursorUp(int y) { 289 return this; 290 } 291 292 @Override 293 public Ansi cursorRight(int x) { 294 return this; 295 } 296 297 @Override 298 public Ansi cursorDown(int y) { 299 return this; 300 } 301 302 @Override 303 public Ansi cursorLeft(int x) { 304 return this; 305 } 306 307 @Override 308 public Ansi cursorDownLine() { 309 return this; 310 } 311 312 @Override 313 public Ansi cursorDownLine(final int n) { 314 return this; 315 } 316 317 @Override 318 public Ansi cursorUpLine() { 319 return this; 320 } 321 322 @Override 323 public Ansi cursorUpLine(final int n) { 324 return this; 325 } 326 327 @Override 328 public Ansi eraseScreen() { 329 return this; 330 } 331 332 @Override 333 public Ansi eraseScreen(Erase kind) { 334 return this; 335 } 336 337 @Override 338 public Ansi eraseLine() { 339 return this; 340 } 341 342 @Override 343 public Ansi eraseLine(Erase kind) { 344 return this; 345 } 346 347 @Override 348 public Ansi scrollUp(int rows) { 349 return this; 350 } 351 352 @Override 353 public Ansi scrollDown(int rows) { 354 return this; 355 } 356 357 @Override 358 public Ansi saveCursorPosition() { 359 return this; 360 } 361 362 @Override 363 @Deprecated 364 public Ansi restorCursorPosition() { 365 return this; 366 } 367 368 @Override 369 public Ansi restoreCursorPosition() { 370 return this; 371 } 372 373 @Override 374 public Ansi reset() { 375 return this; 376 } 377 } 378 379 private final StringBuilder builder; 380 private final ArrayList<Integer> attributeOptions = new ArrayList<Integer>(5); 381 382 public Ansi() { 383 this(new StringBuilder(80)); 384 } 385 386 public Ansi(Ansi parent) { 387 this(new StringBuilder(parent.builder)); 388 attributeOptions.addAll(parent.attributeOptions); 389 } 390 391 public Ansi(int size) { 392 this(new StringBuilder(size)); 393 } 394 395 public Ansi(StringBuilder builder) { 396 this.builder = builder; 397 } 398 399 public Ansi fg(Color color) { 400 attributeOptions.add(color.fg()); 401 return this; 402 } 403 404 public Ansi fg(int color) { 405 attributeOptions.add(38); 406 attributeOptions.add(5); 407 attributeOptions.add(color & 0xff); 408 return this; 409 } 410 411 public Ansi fgRgb(int color) { 412 return fgRgb(color >> 16, color >> 8, color); 413 } 414 415 public Ansi fgRgb(int r, int g, int b) { 416 attributeOptions.add(38); 417 attributeOptions.add(2); 418 attributeOptions.add(r & 0xff); 419 attributeOptions.add(g & 0xff); 420 attributeOptions.add(b & 0xff); 421 return this; 422 } 423 424 public Ansi fgBlack() { 425 return this.fg(Color.BLACK); 426 } 427 428 public Ansi fgBlue() { 429 return this.fg(Color.BLUE); 430 } 431 432 public Ansi fgCyan() { 433 return this.fg(Color.CYAN); 434 } 435 436 public Ansi fgDefault() { 437 return this.fg(Color.DEFAULT); 438 } 439 440 public Ansi fgGreen() { 441 return this.fg(Color.GREEN); 442 } 443 444 public Ansi fgMagenta() { 445 return this.fg(Color.MAGENTA); 446 } 447 448 public Ansi fgRed() { 449 return this.fg(Color.RED); 450 } 451 452 public Ansi fgYellow() { 453 return this.fg(Color.YELLOW); 454 } 455 456 public Ansi bg(Color color) { 457 attributeOptions.add(color.bg()); 458 return this; 459 } 460 461 public Ansi bg(int color) { 462 attributeOptions.add(48); 463 attributeOptions.add(5); 464 attributeOptions.add(color & 0xff); 465 return this; 466 } 467 468 public Ansi bgRgb(int color) { 469 return bgRgb(color >> 16, color >> 8, color); 470 } 471 472 public Ansi bgRgb(int r, int g, int b) { 473 attributeOptions.add(48); 474 attributeOptions.add(2); 475 attributeOptions.add(r & 0xff); 476 attributeOptions.add(g & 0xff); 477 attributeOptions.add(b & 0xff); 478 return this; 479 } 480 481 public Ansi bgCyan() { 482 return this.bg(Color.CYAN); 483 } 484 485 public Ansi bgDefault() { 486 return this.bg(Color.DEFAULT); 487 } 488 489 public Ansi bgGreen() { 490 return this.bg(Color.GREEN); 491 } 492 493 public Ansi bgMagenta() { 494 return this.bg(Color.MAGENTA); 495 } 496 497 public Ansi bgRed() { 498 return this.bg(Color.RED); 499 } 500 501 public Ansi bgYellow() { 502 return this.bg(Color.YELLOW); 503 } 504 505 public Ansi fgBright(Color color) { 506 attributeOptions.add(color.fgBright()); 507 return this; 508 } 509 510 public Ansi fgBrightBlack() { 511 return this.fgBright(Color.BLACK); 512 } 513 514 public Ansi fgBrightBlue() { 515 return this.fgBright(Color.BLUE); 516 } 517 518 public Ansi fgBrightCyan() { 519 return this.fgBright(Color.CYAN); 520 } 521 522 public Ansi fgBrightDefault() { 523 return this.fgBright(Color.DEFAULT); 524 } 525 526 public Ansi fgBrightGreen() { 527 return this.fgBright(Color.GREEN); 528 } 529 530 public Ansi fgBrightMagenta() { 531 return this.fgBright(Color.MAGENTA); 532 } 533 534 public Ansi fgBrightRed() { 535 return this.fgBright(Color.RED); 536 } 537 538 public Ansi fgBrightYellow() { 539 return this.fgBright(Color.YELLOW); 540 } 541 542 public Ansi bgBright(Color color) { 543 attributeOptions.add(color.bgBright()); 544 return this; 545 } 546 547 public Ansi bgBrightCyan() { 548 return this.bgBright(Color.CYAN); 549 } 550 551 public Ansi bgBrightDefault() { 552 return this.bgBright(Color.DEFAULT); 553 } 554 555 public Ansi bgBrightGreen() { 556 return this.bgBright(Color.GREEN); 557 } 558 559 public Ansi bgBrightMagenta() { 560 return this.bgBright(Color.MAGENTA); 561 } 562 563 public Ansi bgBrightRed() { 564 return this.bgBright(Color.RED); 565 } 566 567 public Ansi bgBrightYellow() { 568 return this.bgBright(Color.YELLOW); 569 } 570 571 public Ansi a(Attribute attribute) { 572 attributeOptions.add(attribute.value()); 573 return this; 574 } 575 576 /** 577 * Moves the cursor to row n, column m. The values are 1-based. 578 * Any values less than 1 are mapped to 1. 579 * 580 * @param row row (1-based) from top 581 * @param column column (1 based) from left 582 * @return this Ansi instance 583 */ 584 public Ansi cursor(final int row, final int column) { 585 return appendEscapeSequence('H', Math.max(1, row), Math.max(1, column)); 586 } 587 588 /** 589 * Moves the cursor to column n. The parameter n is 1-based. 590 * If n is less than 1 it is moved to the first column. 591 * 592 * @param x the index (1-based) of the column to move to 593 * @return this Ansi instance 594 */ 595 public Ansi cursorToColumn(final int x) { 596 return appendEscapeSequence('G', Math.max(1, x)); 597 } 598 599 /** 600 * Moves the cursor up. If the parameter y is negative it moves the cursor down. 601 * 602 * @param y the number of lines to move up 603 * @return this Ansi instance 604 */ 605 public Ansi cursorUp(final int y) { 606 return y > 0 ? appendEscapeSequence('A', y) : y < 0 ? cursorDown(-y) : this; 607 } 608 609 /** 610 * Moves the cursor down. If the parameter y is negative it moves the cursor up. 611 * 612 * @param y the number of lines to move down 613 * @return this Ansi instance 614 */ 615 public Ansi cursorDown(final int y) { 616 return y > 0 ? appendEscapeSequence('B', y) : y < 0 ? cursorUp(-y) : this; 617 } 618 619 /** 620 * Moves the cursor right. If the parameter x is negative it moves the cursor left. 621 * 622 * @param x the number of characters to move right 623 * @return this Ansi instance 624 */ 625 public Ansi cursorRight(final int x) { 626 return x > 0 ? appendEscapeSequence('C', x) : x < 0 ? cursorLeft(-x) : this; 627 } 628 629 /** 630 * Moves the cursor left. If the parameter x is negative it moves the cursor right. 631 * 632 * @param x the number of characters to move left 633 * @return this Ansi instance 634 */ 635 public Ansi cursorLeft(final int x) { 636 return x > 0 ? appendEscapeSequence('D', x) : x < 0 ? cursorRight(-x) : this; 637 } 638 639 /** 640 * Moves the cursor relative to the current position. The cursor is moved right if x is 641 * positive, left if negative and down if y is positive and up if negative. 642 * 643 * @param x the number of characters to move horizontally 644 * @param y the number of lines to move vertically 645 * @return this Ansi instance 646 * @since 2.2 647 */ 648 public Ansi cursorMove(final int x, final int y) { 649 return cursorRight(x).cursorDown(y); 650 } 651 652 /** 653 * Moves the cursor to the beginning of the line below. 654 * 655 * @return this Ansi instance 656 */ 657 public Ansi cursorDownLine() { 658 return appendEscapeSequence('E'); 659 } 660 661 /** 662 * Moves the cursor to the beginning of the n-th line below. If the parameter n is negative it 663 * moves the cursor to the beginning of the n-th line above. 664 * 665 * @param n the number of lines to move the cursor 666 * @return this Ansi instance 667 */ 668 public Ansi cursorDownLine(final int n) { 669 return n < 0 ? cursorUpLine(-n) : appendEscapeSequence('E', n); 670 } 671 672 /** 673 * Moves the cursor to the beginning of the line above. 674 * 675 * @return this Ansi instance 676 */ 677 public Ansi cursorUpLine() { 678 return appendEscapeSequence('F'); 679 } 680 681 /** 682 * Moves the cursor to the beginning of the n-th line above. If the parameter n is negative it 683 * moves the cursor to the beginning of the n-th line below. 684 * 685 * @param n the number of lines to move the cursor 686 * @return this Ansi instance 687 */ 688 public Ansi cursorUpLine(final int n) { 689 return n < 0 ? cursorDownLine(-n) : appendEscapeSequence('F', n); 690 } 691 692 public Ansi eraseScreen() { 693 return appendEscapeSequence('J', Erase.ALL.value()); 694 } 695 696 public Ansi eraseScreen(final Erase kind) { 697 return appendEscapeSequence('J', kind.value()); 698 } 699 700 public Ansi eraseLine() { 701 return appendEscapeSequence('K'); 702 } 703 704 public Ansi eraseLine(final Erase kind) { 705 return appendEscapeSequence('K', kind.value()); 706 } 707 708 public Ansi scrollUp(final int rows) { 709 return rows > 0 ? appendEscapeSequence('S', rows) : rows < 0 ? scrollDown(-rows) : this; 710 } 711 712 public Ansi scrollDown(final int rows) { 713 return rows > 0 ? appendEscapeSequence('T', rows) : rows < 0 ? scrollUp(-rows) : this; 714 } 715 716 public Ansi saveCursorPosition() { 717 return appendEscapeSequence('s'); 718 } 719 720 @Deprecated 721 public Ansi restorCursorPosition() { 722 return appendEscapeSequence('u'); 723 } 724 725 public Ansi restoreCursorPosition() { 726 return appendEscapeSequence('u'); 727 } 728 729 public Ansi reset() { 730 return a(Attribute.RESET); 731 } 732 733 public Ansi bold() { 734 return a(Attribute.INTENSITY_BOLD); 735 } 736 737 public Ansi boldOff() { 738 return a(Attribute.INTENSITY_BOLD_OFF); 739 } 740 741 public Ansi a(String value) { 742 flushAttributes(); 743 builder.append(value); 744 return this; 745 } 746 747 public Ansi a(boolean value) { 748 flushAttributes(); 749 builder.append(value); 750 return this; 751 } 752 753 public Ansi a(char value) { 754 flushAttributes(); 755 builder.append(value); 756 return this; 757 } 758 759 public Ansi a(char[] value, int offset, int len) { 760 flushAttributes(); 761 builder.append(value, offset, len); 762 return this; 763 } 764 765 public Ansi a(char[] value) { 766 flushAttributes(); 767 builder.append(value); 768 return this; 769 } 770 771 public Ansi a(CharSequence value, int start, int end) { 772 flushAttributes(); 773 builder.append(value, start, end); 774 return this; 775 } 776 777 public Ansi a(CharSequence value) { 778 flushAttributes(); 779 builder.append(value); 780 return this; 781 } 782 783 public Ansi a(double value) { 784 flushAttributes(); 785 builder.append(value); 786 return this; 787 } 788 789 public Ansi a(float value) { 790 flushAttributes(); 791 builder.append(value); 792 return this; 793 } 794 795 public Ansi a(int value) { 796 flushAttributes(); 797 builder.append(value); 798 return this; 799 } 800 801 public Ansi a(long value) { 802 flushAttributes(); 803 builder.append(value); 804 return this; 805 } 806 807 public Ansi a(Object value) { 808 flushAttributes(); 809 builder.append(value); 810 return this; 811 } 812 813 public Ansi a(StringBuffer value) { 814 flushAttributes(); 815 builder.append(value); 816 return this; 817 } 818 819 public Ansi newline() { 820 flushAttributes(); 821 builder.append(System.getProperty("line.separator")); 822 return this; 823 } 824 825 public Ansi format(String pattern, Object... args) { 826 flushAttributes(); 827 builder.append(String.format(pattern, args)); 828 return this; 829 } 830 831 /** 832 * Applies another function to this Ansi instance. 833 * 834 * @param fun the function to apply 835 * @return this Ansi instance 836 * @since 2.2 837 */ 838 public Ansi apply(Consumer fun) { 839 fun.apply(this); 840 return this; 841 } 842 843 /** 844 * Uses the {@link AnsiRenderer} 845 * to generate the ANSI escape sequences for the supplied text. 846 * 847 * @param text text 848 * @return this 849 * @since 2.2 850 */ 851 public Ansi render(final String text) { 852 a(AnsiRenderer.render(text)); 853 return this; 854 } 855 856 /** 857 * String formats and renders the supplied arguments. Uses the {@link AnsiRenderer} 858 * to generate the ANSI escape sequences. 859 * 860 * @param text format 861 * @param args arguments 862 * @return this 863 * @since 2.2 864 */ 865 public Ansi render(final String text, Object... args) { 866 a(String.format(AnsiRenderer.render(text), args)); 867 return this; 868 } 869 870 @Override 871 public String toString() { 872 flushAttributes(); 873 return builder.toString(); 874 } 875 876 /////////////////////////////////////////////////////////////////// 877 // Private Helper Methods 878 /////////////////////////////////////////////////////////////////// 879 880 private Ansi appendEscapeSequence(char command) { 881 flushAttributes(); 882 builder.append(FIRST_ESC_CHAR); 883 builder.append(SECOND_ESC_CHAR); 884 builder.append(command); 885 return this; 886 } 887 888 private Ansi appendEscapeSequence(char command, int option) { 889 flushAttributes(); 890 builder.append(FIRST_ESC_CHAR); 891 builder.append(SECOND_ESC_CHAR); 892 builder.append(option); 893 builder.append(command); 894 return this; 895 } 896 897 private Ansi appendEscapeSequence(char command, Object... options) { 898 flushAttributes(); 899 return _appendEscapeSequence(command, options); 900 } 901 902 private void flushAttributes() { 903 if (attributeOptions.isEmpty()) 904 return; 905 if (attributeOptions.size() == 1 && attributeOptions.get(0) == 0) { 906 builder.append(FIRST_ESC_CHAR); 907 builder.append(SECOND_ESC_CHAR); 908 builder.append('m'); 909 } else { 910 _appendEscapeSequence('m', attributeOptions.toArray()); 911 } 912 attributeOptions.clear(); 913 } 914 915 private Ansi _appendEscapeSequence(char command, Object... options) { 916 builder.append(FIRST_ESC_CHAR); 917 builder.append(SECOND_ESC_CHAR); 918 int size = options.length; 919 for (int i = 0; i < size; i++) { 920 if (i != 0) { 921 builder.append(';'); 922 } 923 if (options[i] != null) { 924 builder.append(options[i]); 925 } 926 } 927 builder.append(command); 928 return this; 929 } 930 931 @Override 932 public Ansi append(CharSequence csq) { 933 builder.append(csq); 934 return this; 935 } 936 937 @Override 938 public Ansi append(CharSequence csq, int start, int end) { 939 builder.append(csq, start, end); 940 return this; 941 } 942 943 @Override 944 public Ansi append(char c) { 945 builder.append(c); 946 return this; 947 } 948}