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}