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}