View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.fusesource.jansi;
20  
21  import java.util.ArrayList;
22  
23  /**
24   * Provides a fluent API for generating
25   * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences">ANSI escape sequences</a>.
26   *
27   * This class comes from Jansi and is provided for backward compatibility
28   * with maven-shared-utils, while Maven has migrated to JLine (into which Jansi has been merged
29   * since JLine 3.25.0).
30   */
31  @SuppressWarnings({"checkstyle:MagicNumber", "unused"})
32  public class Ansi implements Appendable {
33  
34      private static final char FIRST_ESC_CHAR = 27;
35      private static final char SECOND_ESC_CHAR = '[';
36  
37      /**
38       * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors">ANSI 8 colors</a> for fluent API
39       */
40      public enum Color {
41          BLACK(0, "BLACK"),
42          RED(1, "RED"),
43          GREEN(2, "GREEN"),
44          YELLOW(3, "YELLOW"),
45          BLUE(4, "BLUE"),
46          MAGENTA(5, "MAGENTA"),
47          CYAN(6, "CYAN"),
48          WHITE(7, "WHITE"),
49          DEFAULT(9, "DEFAULT");
50  
51          private final int value;
52          private final String name;
53  
54          Color(int index, String name) {
55              this.value = index;
56              this.name = name;
57          }
58  
59          @Override
60          public String toString() {
61              return name;
62          }
63  
64          public int value() {
65              return value;
66          }
67  
68          public int fg() {
69              return value + 30;
70          }
71  
72          public int bg() {
73              return value + 40;
74          }
75  
76          public int fgBright() {
77              return value + 90;
78          }
79  
80          public int bgBright() {
81              return value + 100;
82          }
83      }
84  
85      /**
86       * Display attributes, also know as
87       * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters">SGR
88       * (Select Graphic Rendition) parameters</a>.
89       */
90      public enum Attribute {
91          RESET(0, "RESET"),
92          INTENSITY_BOLD(1, "INTENSITY_BOLD"),
93          INTENSITY_FAINT(2, "INTENSITY_FAINT"),
94          ITALIC(3, "ITALIC_ON"),
95          UNDERLINE(4, "UNDERLINE_ON"),
96          BLINK_SLOW(5, "BLINK_SLOW"),
97          BLINK_FAST(6, "BLINK_FAST"),
98          NEGATIVE_ON(7, "NEGATIVE_ON"),
99          CONCEAL_ON(8, "CONCEAL_ON"),
100         STRIKETHROUGH_ON(9, "STRIKETHROUGH_ON"),
101         UNDERLINE_DOUBLE(21, "UNDERLINE_DOUBLE"),
102         INTENSITY_BOLD_OFF(22, "INTENSITY_BOLD_OFF"),
103         ITALIC_OFF(23, "ITALIC_OFF"),
104         UNDERLINE_OFF(24, "UNDERLINE_OFF"),
105         BLINK_OFF(25, "BLINK_OFF"),
106         NEGATIVE_OFF(27, "NEGATIVE_OFF"),
107         CONCEAL_OFF(28, "CONCEAL_OFF"),
108         STRIKETHROUGH_OFF(29, "STRIKETHROUGH_OFF");
109 
110         private final int value;
111         private final String name;
112 
113         Attribute(int index, String name) {
114             this.value = index;
115             this.name = name;
116         }
117 
118         @Override
119         public String toString() {
120             return name;
121         }
122 
123         public int value() {
124             return value;
125         }
126     }
127 
128     /**
129      * ED (Erase in Display) / EL (Erase in Line) parameter (see
130      * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences">CSI sequence J and K</a>)
131      * @see Ansi#eraseScreen(Erase)
132      * @see Ansi#eraseLine(Erase)
133      */
134     public enum Erase {
135         FORWARD(0, "FORWARD"),
136         BACKWARD(1, "BACKWARD"),
137         ALL(2, "ALL");
138 
139         private final int value;
140         private final String name;
141 
142         Erase(int index, String name) {
143             this.value = index;
144             this.name = name;
145         }
146 
147         @Override
148         public String toString() {
149             return name;
150         }
151 
152         public int value() {
153             return value;
154         }
155     }
156 
157     @FunctionalInterface
158     public interface Consumer {
159         void apply(Ansi ansi);
160     }
161 
162     public static boolean isEnabled() {
163         return org.apache.maven.jline.MessageUtils.isColorEnabled() && org.jline.jansi.Ansi.isEnabled();
164     }
165 
166     public static void setEnabled(final boolean flag) {
167         org.jline.jansi.Ansi.setEnabled(flag);
168     }
169 
170     public static Ansi ansi() {
171         if (isEnabled()) {
172             return new Ansi();
173         } else {
174             return new NoAnsi();
175         }
176     }
177 
178     public static Ansi ansi(StringBuilder builder) {
179         if (isEnabled()) {
180             return new Ansi(builder);
181         } else {
182             return new NoAnsi(builder);
183         }
184     }
185 
186     public static Ansi ansi(int size) {
187         if (isEnabled()) {
188             return new Ansi(size);
189         } else {
190             return new NoAnsi(size);
191         }
192     }
193 
194     private static class NoAnsi extends Ansi {
195         NoAnsi() {
196             super();
197         }
198 
199         NoAnsi(int size) {
200             super(size);
201         }
202 
203         NoAnsi(StringBuilder builder) {
204             super(builder);
205         }
206 
207         @Override
208         public Ansi fg(Color color) {
209             return this;
210         }
211 
212         @Override
213         public Ansi bg(Color color) {
214             return this;
215         }
216 
217         @Override
218         public Ansi fgBright(Color color) {
219             return this;
220         }
221 
222         @Override
223         public Ansi bgBright(Color color) {
224             return this;
225         }
226 
227         @Override
228         public Ansi fg(int color) {
229             return this;
230         }
231 
232         @Override
233         public Ansi fgRgb(int r, int g, int b) {
234             return this;
235         }
236 
237         @Override
238         public Ansi bg(int color) {
239             return this;
240         }
241 
242         @Override
243         public Ansi bgRgb(int r, int g, int b) {
244             return this;
245         }
246 
247         @Override
248         public Ansi a(Attribute attribute) {
249             return this;
250         }
251 
252         @Override
253         public Ansi cursor(int row, int column) {
254             return this;
255         }
256 
257         @Override
258         public Ansi cursorToColumn(int x) {
259             return this;
260         }
261 
262         @Override
263         public Ansi cursorUp(int y) {
264             return this;
265         }
266 
267         @Override
268         public Ansi cursorRight(int x) {
269             return this;
270         }
271 
272         @Override
273         public Ansi cursorDown(int y) {
274             return this;
275         }
276 
277         @Override
278         public Ansi cursorLeft(int x) {
279             return this;
280         }
281 
282         @Override
283         public Ansi cursorDownLine() {
284             return this;
285         }
286 
287         @Override
288         public Ansi cursorDownLine(final int n) {
289             return this;
290         }
291 
292         @Override
293         public Ansi cursorUpLine() {
294             return this;
295         }
296 
297         @Override
298         public Ansi cursorUpLine(final int n) {
299             return this;
300         }
301 
302         @Override
303         public Ansi eraseScreen() {
304             return this;
305         }
306 
307         @Override
308         public Ansi eraseScreen(Erase kind) {
309             return this;
310         }
311 
312         @Override
313         public Ansi eraseLine() {
314             return this;
315         }
316 
317         @Override
318         public Ansi eraseLine(Erase kind) {
319             return this;
320         }
321 
322         @Override
323         public Ansi scrollUp(int rows) {
324             return this;
325         }
326 
327         @Override
328         public Ansi scrollDown(int rows) {
329             return this;
330         }
331 
332         @Override
333         public Ansi saveCursorPosition() {
334             return this;
335         }
336 
337         @Override
338         @Deprecated
339         public Ansi restorCursorPosition() {
340             return this;
341         }
342 
343         @Override
344         public Ansi restoreCursorPosition() {
345             return this;
346         }
347 
348         @Override
349         public Ansi reset() {
350             return this;
351         }
352     }
353 
354     private final StringBuilder builder;
355     private final ArrayList<Integer> attributeOptions = new ArrayList<>(5);
356 
357     public Ansi() {
358         this(new StringBuilder(80));
359     }
360 
361     public Ansi(Ansi parent) {
362         this(new StringBuilder(parent.builder));
363         attributeOptions.addAll(parent.attributeOptions);
364     }
365 
366     public Ansi(int size) {
367         this(new StringBuilder(size));
368     }
369 
370     public Ansi(StringBuilder builder) {
371         this.builder = builder;
372     }
373 
374     public Ansi fg(Color color) {
375         attributeOptions.add(color.fg());
376         return this;
377     }
378 
379     public Ansi fg(int color) {
380         attributeOptions.add(38);
381         attributeOptions.add(5);
382         attributeOptions.add(color & 0xff);
383         return this;
384     }
385 
386     public Ansi fgRgb(int color) {
387         return fgRgb(color >> 16, color >> 8, color);
388     }
389 
390     public Ansi fgRgb(int r, int g, int b) {
391         attributeOptions.add(38);
392         attributeOptions.add(2);
393         attributeOptions.add(r & 0xff);
394         attributeOptions.add(g & 0xff);
395         attributeOptions.add(b & 0xff);
396         return this;
397     }
398 
399     public Ansi fgBlack() {
400         return this.fg(Color.BLACK);
401     }
402 
403     public Ansi fgBlue() {
404         return this.fg(Color.BLUE);
405     }
406 
407     public Ansi fgCyan() {
408         return this.fg(Color.CYAN);
409     }
410 
411     public Ansi fgDefault() {
412         return this.fg(Color.DEFAULT);
413     }
414 
415     public Ansi fgGreen() {
416         return this.fg(Color.GREEN);
417     }
418 
419     public Ansi fgMagenta() {
420         return this.fg(Color.MAGENTA);
421     }
422 
423     public Ansi fgRed() {
424         return this.fg(Color.RED);
425     }
426 
427     public Ansi fgYellow() {
428         return this.fg(Color.YELLOW);
429     }
430 
431     public Ansi bg(Color color) {
432         attributeOptions.add(color.bg());
433         return this;
434     }
435 
436     public Ansi bg(int color) {
437         attributeOptions.add(48);
438         attributeOptions.add(5);
439         attributeOptions.add(color & 0xff);
440         return this;
441     }
442 
443     public Ansi bgRgb(int color) {
444         return bgRgb(color >> 16, color >> 8, color);
445     }
446 
447     public Ansi bgRgb(int r, int g, int b) {
448         attributeOptions.add(48);
449         attributeOptions.add(2);
450         attributeOptions.add(r & 0xff);
451         attributeOptions.add(g & 0xff);
452         attributeOptions.add(b & 0xff);
453         return this;
454     }
455 
456     public Ansi bgCyan() {
457         return this.bg(Color.CYAN);
458     }
459 
460     public Ansi bgDefault() {
461         return this.bg(Color.DEFAULT);
462     }
463 
464     public Ansi bgGreen() {
465         return this.bg(Color.GREEN);
466     }
467 
468     public Ansi bgMagenta() {
469         return this.bg(Color.MAGENTA);
470     }
471 
472     public Ansi bgRed() {
473         return this.bg(Color.RED);
474     }
475 
476     public Ansi bgYellow() {
477         return this.bg(Color.YELLOW);
478     }
479 
480     public Ansi fgBright(Color color) {
481         attributeOptions.add(color.fgBright());
482         return this;
483     }
484 
485     public Ansi fgBrightBlack() {
486         return this.fgBright(Color.BLACK);
487     }
488 
489     public Ansi fgBrightBlue() {
490         return this.fgBright(Color.BLUE);
491     }
492 
493     public Ansi fgBrightCyan() {
494         return this.fgBright(Color.CYAN);
495     }
496 
497     public Ansi fgBrightDefault() {
498         return this.fgBright(Color.DEFAULT);
499     }
500 
501     public Ansi fgBrightGreen() {
502         return this.fgBright(Color.GREEN);
503     }
504 
505     public Ansi fgBrightMagenta() {
506         return this.fgBright(Color.MAGENTA);
507     }
508 
509     public Ansi fgBrightRed() {
510         return this.fgBright(Color.RED);
511     }
512 
513     public Ansi fgBrightYellow() {
514         return this.fgBright(Color.YELLOW);
515     }
516 
517     public Ansi bgBright(Color color) {
518         attributeOptions.add(color.bgBright());
519         return this;
520     }
521 
522     public Ansi bgBrightCyan() {
523         return this.bgBright(Color.CYAN);
524     }
525 
526     public Ansi bgBrightDefault() {
527         return this.bgBright(Color.DEFAULT);
528     }
529 
530     public Ansi bgBrightGreen() {
531         return this.bgBright(Color.GREEN);
532     }
533 
534     public Ansi bgBrightMagenta() {
535         return this.bgBright(Color.MAGENTA);
536     }
537 
538     public Ansi bgBrightRed() {
539         return this.bgBright(Color.RED);
540     }
541 
542     public Ansi bgBrightYellow() {
543         return this.bgBright(Color.YELLOW);
544     }
545 
546     public Ansi a(Attribute attribute) {
547         attributeOptions.add(attribute.value());
548         return this;
549     }
550 
551     /**
552      * Moves the cursor to row n, column m. The values are 1-based.
553      * Any values less than 1 are mapped to 1.
554      *
555      * @param row    row (1-based) from top
556      * @param column column (1 based) from left
557      * @return this Ansi instance
558      */
559     public Ansi cursor(final int row, final int column) {
560         return appendEscapeSequence('H', Math.max(1, row), Math.max(1, column));
561     }
562 
563     /**
564      * Moves the cursor to column n. The parameter n is 1-based.
565      * If n is less than 1 it is moved to the first column.
566      *
567      * @param x the index (1-based) of the column to move to
568      * @return this Ansi instance
569      */
570     public Ansi cursorToColumn(final int x) {
571         return appendEscapeSequence('G', Math.max(1, x));
572     }
573 
574     /**
575      * Moves the cursor up. If the parameter y is negative it moves the cursor down.
576      *
577      * @param y the number of lines to move up
578      * @return this Ansi instance
579      */
580     public Ansi cursorUp(final int y) {
581         return y > 0 ? appendEscapeSequence('A', y) : y < 0 ? cursorDown(-y) : this;
582     }
583 
584     /**
585      * Moves the cursor down. If the parameter y is negative it moves the cursor up.
586      *
587      * @param y the number of lines to move down
588      * @return this Ansi instance
589      */
590     public Ansi cursorDown(final int y) {
591         return y > 0 ? appendEscapeSequence('B', y) : y < 0 ? cursorUp(-y) : this;
592     }
593 
594     /**
595      * Moves the cursor right. If the parameter x is negative it moves the cursor left.
596      *
597      * @param x the number of characters to move right
598      * @return this Ansi instance
599      */
600     public Ansi cursorRight(final int x) {
601         return x > 0 ? appendEscapeSequence('C', x) : x < 0 ? cursorLeft(-x) : this;
602     }
603 
604     /**
605      * Moves the cursor left. If the parameter x is negative it moves the cursor right.
606      *
607      * @param x the number of characters to move left
608      * @return this Ansi instance
609      */
610     public Ansi cursorLeft(final int x) {
611         return x > 0 ? appendEscapeSequence('D', x) : x < 0 ? cursorRight(-x) : this;
612     }
613 
614     /**
615      * Moves the cursor relative to the current position. The cursor is moved right if x is
616      * positive, left if negative and down if y is positive and up if negative.
617      *
618      * @param x the number of characters to move horizontally
619      * @param y the number of lines to move vertically
620      * @return this Ansi instance
621      * @since 2.2
622      */
623     public Ansi cursorMove(final int x, final int y) {
624         return cursorRight(x).cursorDown(y);
625     }
626 
627     /**
628      * Moves the cursor to the beginning of the line below.
629      *
630      * @return this Ansi instance
631      */
632     public Ansi cursorDownLine() {
633         return appendEscapeSequence('E');
634     }
635 
636     /**
637      * Moves the cursor to the beginning of the n-th line below. If the parameter n is negative it
638      * moves the cursor to the beginning of the n-th line above.
639      *
640      * @param n the number of lines to move the cursor
641      * @return this Ansi instance
642      */
643     public Ansi cursorDownLine(final int n) {
644         return n < 0 ? cursorUpLine(-n) : appendEscapeSequence('E', n);
645     }
646 
647     /**
648      * Moves the cursor to the beginning of the line above.
649      *
650      * @return this Ansi instance
651      */
652     public Ansi cursorUpLine() {
653         return appendEscapeSequence('F');
654     }
655 
656     /**
657      * Moves the cursor to the beginning of the n-th line above. If the parameter n is negative it
658      * moves the cursor to the beginning of the n-th line below.
659      *
660      * @param n the number of lines to move the cursor
661      * @return this Ansi instance
662      */
663     public Ansi cursorUpLine(final int n) {
664         return n < 0 ? cursorDownLine(-n) : appendEscapeSequence('F', n);
665     }
666 
667     public Ansi eraseScreen() {
668         return appendEscapeSequence('J', Erase.ALL.value());
669     }
670 
671     public Ansi eraseScreen(final Erase kind) {
672         return appendEscapeSequence('J', kind.value());
673     }
674 
675     public Ansi eraseLine() {
676         return appendEscapeSequence('K');
677     }
678 
679     public Ansi eraseLine(final Erase kind) {
680         return appendEscapeSequence('K', kind.value());
681     }
682 
683     public Ansi scrollUp(final int rows) {
684         if (rows == Integer.MIN_VALUE) {
685             return scrollDown(Integer.MAX_VALUE);
686         }
687         return rows > 0 ? appendEscapeSequence('S', rows) : rows < 0 ? scrollDown(-rows) : this;
688     }
689 
690     public Ansi scrollDown(final int rows) {
691         if (rows == Integer.MIN_VALUE) {
692             return scrollUp(Integer.MAX_VALUE);
693         }
694         return rows > 0 ? appendEscapeSequence('T', rows) : rows < 0 ? scrollUp(-rows) : this;
695     }
696 
697     @Deprecated
698     public Ansi restorCursorPosition() {
699         return restoreCursorPosition();
700     }
701 
702     public Ansi saveCursorPosition() {
703         saveCursorPositionSCO();
704         return saveCursorPositionDEC();
705     }
706 
707     // SCO command
708     public Ansi saveCursorPositionSCO() {
709         return appendEscapeSequence('s');
710     }
711 
712     // DEC command
713     public Ansi saveCursorPositionDEC() {
714         builder.append(FIRST_ESC_CHAR);
715         builder.append('7');
716         return this;
717     }
718 
719     public Ansi restoreCursorPosition() {
720         restoreCursorPositionSCO();
721         return restoreCursorPositionDEC();
722     }
723 
724     // SCO command
725     public Ansi restoreCursorPositionSCO() {
726         return appendEscapeSequence('u');
727     }
728 
729     // DEC command
730     public Ansi restoreCursorPositionDEC() {
731         builder.append(FIRST_ESC_CHAR);
732         builder.append('8');
733         return this;
734     }
735 
736     public Ansi reset() {
737         return a(Attribute.RESET);
738     }
739 
740     public Ansi bold() {
741         return a(Attribute.INTENSITY_BOLD);
742     }
743 
744     public Ansi boldOff() {
745         return a(Attribute.INTENSITY_BOLD_OFF);
746     }
747 
748     public Ansi a(String value) {
749         flushAttributes();
750         builder.append(value);
751         return this;
752     }
753 
754     public Ansi a(boolean value) {
755         flushAttributes();
756         builder.append(value);
757         return this;
758     }
759 
760     public Ansi a(char value) {
761         flushAttributes();
762         builder.append(value);
763         return this;
764     }
765 
766     public Ansi a(char[] value, int offset, int len) {
767         flushAttributes();
768         builder.append(value, offset, len);
769         return this;
770     }
771 
772     public Ansi a(char[] value) {
773         flushAttributes();
774         builder.append(value);
775         return this;
776     }
777 
778     public Ansi a(CharSequence value, int start, int end) {
779         flushAttributes();
780         builder.append(value, start, end);
781         return this;
782     }
783 
784     public Ansi a(CharSequence value) {
785         flushAttributes();
786         builder.append(value);
787         return this;
788     }
789 
790     public Ansi a(double value) {
791         flushAttributes();
792         builder.append(value);
793         return this;
794     }
795 
796     public Ansi a(float value) {
797         flushAttributes();
798         builder.append(value);
799         return this;
800     }
801 
802     public Ansi a(int value) {
803         flushAttributes();
804         builder.append(value);
805         return this;
806     }
807 
808     public Ansi a(long value) {
809         flushAttributes();
810         builder.append(value);
811         return this;
812     }
813 
814     public Ansi a(Object value) {
815         flushAttributes();
816         builder.append(value);
817         return this;
818     }
819 
820     public Ansi a(StringBuffer value) {
821         flushAttributes();
822         builder.append(value);
823         return this;
824     }
825 
826     public Ansi newline() {
827         flushAttributes();
828         builder.append(System.lineSeparator());
829         return this;
830     }
831 
832     public Ansi format(String pattern, Object... args) {
833         flushAttributes();
834         builder.append(String.format(pattern, args));
835         return this;
836     }
837 
838     /**
839      * Applies another function to this Ansi instance.
840      *
841      * @param fun the function to apply
842      * @return this Ansi instance
843      * @since 2.2
844      */
845     public Ansi apply(Consumer fun) {
846         fun.apply(this);
847         return this;
848     }
849 
850     /**
851      * Uses the {@link org.jline.jansi.AnsiRenderer}
852      * to generate the ANSI escape sequences for the supplied text.
853      *
854      * @param text text
855      * @return this
856      * @since 2.2
857      */
858     public Ansi render(final String text) {
859         a(new org.jline.jansi.Ansi().render(text).toString());
860         return this;
861     }
862 
863     /**
864      * String formats and renders the supplied arguments.  Uses the {@link org.jline.jansi.AnsiRenderer}
865      * to generate the ANSI escape sequences.
866      *
867      * @param text format
868      * @param args arguments
869      * @return this
870      * @since 2.2
871      */
872     public Ansi render(final String text, Object... args) {
873         a(String.format(new org.jline.jansi.Ansi().render(text).toString(), args));
874         return this;
875     }
876 
877     @Override
878     public String toString() {
879         flushAttributes();
880         return builder.toString();
881     }
882 
883     ///////////////////////////////////////////////////////////////////
884     // Private Helper Methods
885     ///////////////////////////////////////////////////////////////////
886 
887     private Ansi appendEscapeSequence(char command) {
888         flushAttributes();
889         builder.append(FIRST_ESC_CHAR);
890         builder.append(SECOND_ESC_CHAR);
891         builder.append(command);
892         return this;
893     }
894 
895     private Ansi appendEscapeSequence(char command, int option) {
896         flushAttributes();
897         builder.append(FIRST_ESC_CHAR);
898         builder.append(SECOND_ESC_CHAR);
899         builder.append(option);
900         builder.append(command);
901         return this;
902     }
903 
904     private Ansi appendEscapeSequence(char command, Object... options) {
905         flushAttributes();
906         return doAppendEscapeSequence(command, options);
907     }
908 
909     private void flushAttributes() {
910         if (attributeOptions.isEmpty()) {
911             return;
912         }
913         if (attributeOptions.size() == 1 && attributeOptions.get(0) == 0) {
914             builder.append(FIRST_ESC_CHAR);
915             builder.append(SECOND_ESC_CHAR);
916             builder.append('m');
917         } else {
918             doAppendEscapeSequence('m', attributeOptions.toArray());
919         }
920         attributeOptions.clear();
921     }
922 
923     private Ansi doAppendEscapeSequence(char command, Object... options) {
924         builder.append(FIRST_ESC_CHAR);
925         builder.append(SECOND_ESC_CHAR);
926         int size = options.length;
927         for (int i = 0; i < size; i++) {
928             if (i != 0) {
929                 builder.append(';');
930             }
931             if (options[i] != null) {
932                 builder.append(options[i]);
933             }
934         }
935         builder.append(command);
936         return this;
937     }
938 
939     @Override
940     public Ansi append(CharSequence csq) {
941         builder.append(csq);
942         return this;
943     }
944 
945     @Override
946     public Ansi append(CharSequence csq, int start, int end) {
947         builder.append(csq, start, end);
948         return this;
949     }
950 
951     @Override
952     public Ansi append(char c) {
953         builder.append(c);
954         return this;
955     }
956 }