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 x, int y) {
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    public Ansi cursor(final int x, final int y) {
498        return appendEscapeSequence('H', x, y);
499    }
500
501    public Ansi cursorToColumn(final int x) {
502        return appendEscapeSequence('G', x);
503    }
504
505    public Ansi cursorUp(final int y) {
506        return appendEscapeSequence('A', y);
507    }
508
509    public Ansi cursorDown(final int y) {
510        return appendEscapeSequence('B', y);
511    }
512
513    public Ansi cursorRight(final int x) {
514        return appendEscapeSequence('C', x);
515    }
516
517    public Ansi cursorLeft(final int x) {
518        return appendEscapeSequence('D', x);
519    }
520
521    public Ansi cursorDownLine() {
522        return appendEscapeSequence('E');
523    }
524
525    public Ansi cursorDownLine(final int n) {
526        return appendEscapeSequence('E', n);
527    }
528
529    public Ansi cursorUpLine() {
530        return appendEscapeSequence('F');
531    }
532
533    public Ansi cursorUpLine(final int n) {
534        return appendEscapeSequence('F', n);
535    }
536
537    public Ansi eraseScreen() {
538        return appendEscapeSequence('J', Erase.ALL.value());
539    }
540
541    public Ansi eraseScreen(final Erase kind) {
542        return appendEscapeSequence('J', kind.value());
543    }
544
545    public Ansi eraseLine() {
546        return appendEscapeSequence('K');
547    }
548
549    public Ansi eraseLine(final Erase kind) {
550        return appendEscapeSequence('K', kind.value());
551    }
552
553    public Ansi scrollUp(final int rows) {
554        return appendEscapeSequence('S', rows);
555    }
556
557    public Ansi scrollDown(final int rows) {
558        return appendEscapeSequence('T', rows);
559    }
560
561    public Ansi saveCursorPosition() {
562        return appendEscapeSequence('s');
563    }
564
565    @Deprecated
566    public Ansi restorCursorPosition() {
567        return appendEscapeSequence('u');
568    }
569
570    public Ansi restoreCursorPosition() {
571        return appendEscapeSequence('u');
572    }
573
574    public Ansi reset() {
575        return a(Attribute.RESET);
576    }
577
578    public Ansi bold() {
579        return a(Attribute.INTENSITY_BOLD);
580    }
581
582    public Ansi boldOff() {
583        return a(Attribute.INTENSITY_BOLD_OFF);
584    }
585
586    public Ansi a(String value) {
587        flushAttributes();
588        builder.append(value);
589        return this;
590    }
591
592    public Ansi a(boolean value) {
593        flushAttributes();
594        builder.append(value);
595        return this;
596    }
597
598    public Ansi a(char value) {
599        flushAttributes();
600        builder.append(value);
601        return this;
602    }
603
604    public Ansi a(char[] value, int offset, int len) {
605        flushAttributes();
606        builder.append(value, offset, len);
607        return this;
608    }
609
610    public Ansi a(char[] value) {
611        flushAttributes();
612        builder.append(value);
613        return this;
614    }
615
616    public Ansi a(CharSequence value, int start, int end) {
617        flushAttributes();
618        builder.append(value, start, end);
619        return this;
620    }
621
622    public Ansi a(CharSequence value) {
623        flushAttributes();
624        builder.append(value);
625        return this;
626    }
627
628    public Ansi a(double value) {
629        flushAttributes();
630        builder.append(value);
631        return this;
632    }
633
634    public Ansi a(float value) {
635        flushAttributes();
636        builder.append(value);
637        return this;
638    }
639
640    public Ansi a(int value) {
641        flushAttributes();
642        builder.append(value);
643        return this;
644    }
645
646    public Ansi a(long value) {
647        flushAttributes();
648        builder.append(value);
649        return this;
650    }
651
652    public Ansi a(Object value) {
653        flushAttributes();
654        builder.append(value);
655        return this;
656    }
657
658    public Ansi a(StringBuffer value) {
659        flushAttributes();
660        builder.append(value);
661        return this;
662    }
663
664    public Ansi newline() {
665        flushAttributes();
666        builder.append(System.getProperty("line.separator"));
667        return this;
668    }
669
670    public Ansi format(String pattern, Object... args) {
671        flushAttributes();
672        builder.append(String.format(pattern, args));
673        return this;
674    }
675
676    /**
677     * Uses the {@link AnsiRenderer}
678     * to generate the ANSI escape sequences for the supplied text.
679     *
680     * @param text text
681     * @return this
682     *
683     * @since 1.1
684     */
685    public Ansi render(final String text) {
686        a(AnsiRenderer.render(text));
687        return this;
688    }
689
690    /**
691     * String formats and renders the supplied arguments.  Uses the {@link AnsiRenderer}
692     * to generate the ANSI escape sequences.
693     *
694     * @param text format
695     * @param args arguments
696     * @return this
697     *
698     * @since 1.1
699     */
700    public Ansi render(final String text, Object... args) {
701        a(String.format(AnsiRenderer.render(text), args));
702        return this;
703    }
704
705    @Override
706    public String toString() {
707        flushAttributes();
708        return builder.toString();
709    }
710
711    ///////////////////////////////////////////////////////////////////
712    // Private Helper Methods
713    ///////////////////////////////////////////////////////////////////
714
715    private Ansi appendEscapeSequence(char command) {
716        flushAttributes();
717        builder.append(FIRST_ESC_CHAR);
718        builder.append(SECOND_ESC_CHAR);
719        builder.append(command);
720        return this;
721    }
722
723    private Ansi appendEscapeSequence(char command, int option) {
724        flushAttributes();
725        builder.append(FIRST_ESC_CHAR);
726        builder.append(SECOND_ESC_CHAR);
727        builder.append(option);
728        builder.append(command);
729        return this;
730    }
731
732    private Ansi appendEscapeSequence(char command, Object... options) {
733        flushAttributes();
734        return _appendEscapeSequence(command, options);
735    }
736
737    private void flushAttributes() {
738        if (attributeOptions.isEmpty())
739            return;
740        if (attributeOptions.size() == 1 && attributeOptions.get(0) == 0) {
741            builder.append(FIRST_ESC_CHAR);
742            builder.append(SECOND_ESC_CHAR);
743            builder.append('m');
744        } else {
745            _appendEscapeSequence('m', attributeOptions.toArray());
746        }
747        attributeOptions.clear();
748    }
749
750    private Ansi _appendEscapeSequence(char command, Object... options) {
751        builder.append(FIRST_ESC_CHAR);
752        builder.append(SECOND_ESC_CHAR);
753        int size = options.length;
754        for (int i = 0; i < size; i++) {
755            if (i != 0) {
756                builder.append(';');
757            }
758            if (options[i] != null) {
759                builder.append(options[i]);
760            }
761        }
762        builder.append(command);
763        return this;
764    }
765
766}