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}