001 /**
002 * Copyright (C) 2009, Progress Software Corporation and/or its
003 * subsidiaries or affiliates. All rights reserved.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.fusesource.jansi;
018
019 import java.util.ArrayList;
020 import java.util.concurrent.Callable;
021
022 /**
023 * Provides a fluent API for generating ANSI escape sequences.
024 *
025 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
026 * @since 1.0
027 */
028 public class Ansi {
029
030 private static final char FIRST_ESC_CHAR = 27;
031 private static final char SECOND_ESC_CHAR = '[';
032
033 public static enum Color {
034 BLACK(0, "BLACK"),
035 RED(1, "RED"),
036 GREEN(2, "GREEN"),
037 YELLOW(3, "YELLOW"),
038 BLUE(4, "BLUE"),
039 MAGENTA(5, "MAGENTA"),
040 CYAN(6, "CYAN"),
041 WHITE(7,"WHITE"),
042 DEFAULT(9,"DEFAULT");
043
044 private final int value;
045 private final String name;
046
047 Color(int index, String name) {
048 this.value = index;
049 this.name = name;
050 }
051
052 @Override
053 public String toString() {
054 return name;
055 }
056
057 public int value() {
058 return value;
059 }
060
061 public int fg() {
062 return value + 30;
063 }
064
065 public int bg() {
066 return value + 40;
067 }
068
069 public int fgBright() {
070 return value + 90;
071 }
072
073 public int bgBright() {
074 return value + 100;
075 }
076 };
077
078 public static enum Attribute {
079 RESET ( 0, "RESET"),
080 INTENSITY_BOLD ( 1, "INTENSITY_BOLD"),
081 INTENSITY_FAINT ( 2, "INTENSITY_FAINT"),
082 ITALIC ( 3, "ITALIC_ON"),
083 UNDERLINE ( 4, "UNDERLINE_ON"),
084 BLINK_SLOW ( 5, "BLINK_SLOW"),
085 BLINK_FAST ( 6, "BLINK_FAST"),
086 NEGATIVE_ON ( 7, "NEGATIVE_ON"),
087 CONCEAL_ON ( 8, "CONCEAL_ON"),
088 STRIKETHROUGH_ON ( 9, "STRIKETHROUGH_ON"),
089 UNDERLINE_DOUBLE ( 21, "UNDERLINE_DOUBLE"),
090 INTENSITY_BOLD_OFF ( 22, "INTENSITY_BOLD_OFF"),
091 ITALIC_OFF ( 23, "ITALIC_OFF"),
092 UNDERLINE_OFF ( 24, "UNDERLINE_OFF"),
093 BLINK_OFF ( 25, "BLINK_OFF"),
094 NEGATIVE_OFF ( 27, "NEGATIVE_OFF"),
095 CONCEAL_OFF ( 28, "CONCEAL_OFF"),
096 STRIKETHROUGH_OFF ( 29, "STRIKETHROUGH_OFF");
097
098 private final int value;
099 private final String name;
100
101 Attribute(int index, String name) {
102 this.value = index;
103 this.name = name;
104 }
105
106 @Override
107 public String toString() {
108 return name;
109 }
110
111 public int value() {
112 return value;
113 }
114
115 };
116
117 public static enum Erase {
118 FORWARD(0, "FORWARD"),
119 BACKWARD(1, "BACKWARD"),
120 ALL(2, "ALL");
121
122 private final int value;
123 private final String name;
124
125 Erase(int index, String name) {
126 this.value = index;
127 this.name = name;
128 }
129
130 @Override
131 public String toString() {
132 return name;
133 }
134
135 public int value() {
136 return value;
137 }
138 };
139
140 public static final String DISABLE = Ansi.class.getName() + ".disable";
141
142 private static Callable<Boolean> detector = new Callable<Boolean>() {
143 public Boolean call() throws Exception {
144 return !Boolean.getBoolean(DISABLE);
145 }
146 };
147
148 public static void setDetector(final Callable<Boolean> detector) {
149 if (detector == null) throw new IllegalArgumentException();
150 Ansi.detector = detector;
151 }
152
153 public static boolean isDetected() {
154 try {
155 return detector.call();
156 }
157 catch (Exception e) {
158 return true;
159 }
160 }
161
162 private static final InheritableThreadLocal<Boolean> holder = new InheritableThreadLocal<Boolean>()
163 {
164 @Override
165 protected Boolean initialValue() {
166 return isDetected();
167 }
168 };
169
170 public static void setEnabled(final boolean flag) {
171 holder.set(flag);
172 }
173
174 public static boolean isEnabled() {
175 return holder.get();
176 }
177
178 public static Ansi ansi() {
179 if (isEnabled()) {
180 return new Ansi();
181 }
182 else {
183 return new NoAnsi();
184 }
185 }
186
187 private static class NoAnsi
188 extends Ansi
189 {
190 @Override
191 public Ansi fg(Color color) {
192 return this;
193 }
194
195 @Override
196 public Ansi bg(Color color) {
197 return this;
198 }
199
200 @Override
201 public Ansi a(Attribute attribute) {
202 return this;
203 }
204
205 @Override
206 public Ansi cursor(int x, int y) {
207 return this;
208 }
209
210 @Override
211 public Ansi cursorUp(int y) {
212 return this;
213 }
214
215 @Override
216 public Ansi cursorRight(int x) {
217 return this;
218 }
219
220 @Override
221 public Ansi cursorDown(int y) {
222 return this;
223 }
224
225 @Override
226 public Ansi cursorLeft(int x) {
227 return this;
228 }
229
230 @Override
231 public Ansi eraseScreen() {
232 return this;
233 }
234
235 @Override
236 public Ansi eraseScreen(Erase kind) {
237 return this;
238 }
239
240 @Override
241 public Ansi eraseLine() {
242 return this;
243 }
244
245 @Override
246 public Ansi eraseLine(Erase kind) {
247 return this;
248 }
249
250 @Override
251 public Ansi scrollUp(int rows) {
252 return this;
253 }
254
255 @Override
256 public Ansi scrollDown(int rows) {
257 return this;
258 }
259
260 @Override
261 public Ansi saveCursorPosition() {
262 return this;
263 }
264
265 @Override
266 public Ansi restorCursorPosition() {
267 return this;
268 }
269
270 @Override
271 public Ansi reset() {
272 return this;
273 }
274 }
275
276 private final StringBuilder builder;
277 private final ArrayList<Integer> attributeOptions = new ArrayList<Integer>(5);
278
279 public Ansi() {
280 this(new StringBuilder());
281 }
282
283 public Ansi(int size) {
284 this(new StringBuilder(size));
285 }
286
287 public Ansi(StringBuilder builder) {
288 this.builder = builder;
289 }
290
291 public static Ansi ansi(StringBuilder builder) {
292 return new Ansi(builder);
293 }
294 public static Ansi ansi(int size) {
295 return new Ansi(size);
296 }
297
298 public Ansi fg(Color color) {
299 attributeOptions.add(color.fg());
300 return this;
301 }
302
303 public Ansi bg(Color color) {
304 attributeOptions.add(color.bg());
305 return this;
306 }
307
308 public Ansi a(Attribute attribute) {
309 attributeOptions.add(attribute.value());
310 return this;
311 }
312
313 public Ansi cursor(final int x, final int y) {
314 return appendEscapeSequence('H', x, y);
315 }
316
317 public Ansi cursorUp(final int y) {
318 return appendEscapeSequence('A', y);
319 }
320
321 public Ansi cursorDown(final int y) {
322 return appendEscapeSequence('B', y);
323 }
324
325 public Ansi cursorRight(final int x) {
326 return appendEscapeSequence('C', x);
327 }
328
329 public Ansi cursorLeft(final int x) {
330 return appendEscapeSequence('D', x);
331 }
332
333 public Ansi eraseScreen() {
334 return appendEscapeSequence('J',Erase.ALL.value());
335 }
336
337 public Ansi eraseScreen(final Erase kind) {
338 return appendEscapeSequence('J', kind.value());
339 }
340
341 public Ansi eraseLine() {
342 return appendEscapeSequence('K');
343 }
344
345 public Ansi eraseLine(final Erase kind) {
346 return appendEscapeSequence('K', kind.value());
347 }
348
349 public Ansi scrollUp(final int rows) {
350 return appendEscapeSequence('S', rows);
351 }
352
353 public Ansi scrollDown(final int rows) {
354 return appendEscapeSequence('T', rows);
355 }
356
357 public Ansi saveCursorPosition() {
358 return appendEscapeSequence('s');
359 }
360
361 public Ansi restorCursorPosition() {
362 return appendEscapeSequence('u');
363 }
364
365 public Ansi reset() {
366 return a(Attribute.RESET);
367 }
368
369 public Ansi a(String value) {
370 flushAtttributes();
371 builder.append(value);
372 return this;
373 }
374
375 public Ansi a(boolean value) {
376 flushAtttributes();
377 builder.append(value);
378 return this;
379 }
380
381 public Ansi a(char value) {
382 flushAtttributes();
383 builder.append(value);
384 return this;
385 }
386
387 public Ansi a(char[] value, int offset, int len) {
388 flushAtttributes();
389 builder.append(value, offset, len);
390 return this;
391 }
392
393 public Ansi a(char[] value) {
394 flushAtttributes();
395 builder.append(value);
396 return this;
397 }
398
399 public Ansi a(CharSequence value, int start, int end) {
400 flushAtttributes();
401 builder.append(value, start, end);
402 return this;
403 }
404
405 public Ansi a(CharSequence value) {
406 flushAtttributes();
407 builder.append(value);
408 return this;
409 }
410
411 public Ansi a(double value) {
412 flushAtttributes();
413 builder.append(value);
414 return this;
415 }
416
417 public Ansi a(float value) {
418 flushAtttributes();
419 builder.append(value);
420 return this;
421 }
422
423 public Ansi a(int value) {
424 flushAtttributes();
425 builder.append(value);
426 return this;
427 }
428
429 public Ansi a(long value) {
430 flushAtttributes();
431 builder.append(value);
432 return this;
433 }
434
435 public Ansi a(Object value) {
436 flushAtttributes();
437 builder.append(value);
438 return this;
439 }
440
441 public Ansi a(StringBuffer value) {
442 flushAtttributes();
443 builder.append(value);
444 return this;
445 }
446
447 public Ansi newline() {
448 flushAtttributes();
449 builder.append(System.getProperty("line.separator"));
450 return this;
451 }
452
453 public Ansi format(String pattern, Object... args) {
454 flushAtttributes();
455 builder.append(String.format(pattern, args));
456 return this;
457 }
458
459 /**
460 * Uses the {@link AnsiRenderer}
461 * to generate the ANSI escape sequences for the supplied text.
462 *
463 * @since 1.1
464 * @param text
465 */
466 public Ansi render(final String text) {
467 a(AnsiRenderer.render(text));
468 return this;
469 }
470
471 /**
472 * String formats and renders the supplied arguments. Uses the {@link AnsiRenderer}
473 * to generate the ANSI escape sequences.
474 *
475 * @since 1.1
476 * @param text
477 * @param args
478 */
479 public Ansi render(final String text, Object... args) {
480 a(String.format(AnsiRenderer.render(text), args));
481 return this;
482 }
483
484 @Override
485 public String toString() {
486 flushAtttributes();
487 return builder.toString();
488 }
489
490 ///////////////////////////////////////////////////////////////////
491 // Private Helper Methods
492 ///////////////////////////////////////////////////////////////////
493
494 private Ansi appendEscapeSequence(char command) {
495 flushAtttributes();
496 builder.append(FIRST_ESC_CHAR);
497 builder.append(SECOND_ESC_CHAR);
498 builder.append(command);
499 return this;
500 }
501
502 private Ansi appendEscapeSequence(char command, int option) {
503 flushAtttributes();
504 builder.append(FIRST_ESC_CHAR);
505 builder.append(SECOND_ESC_CHAR);
506 builder.append(option);
507 builder.append(command);
508 return this;
509 }
510
511 private Ansi appendEscapeSequence(char command, Object... options) {
512 flushAtttributes();
513 return _appendEscapeSequence(command, options);
514 }
515
516 private void flushAtttributes() {
517 if( attributeOptions.isEmpty() )
518 return;
519 if (attributeOptions.size() == 1 && attributeOptions.get(0) == 0) {
520 builder.append(FIRST_ESC_CHAR);
521 builder.append(SECOND_ESC_CHAR);
522 builder.append('m');
523 } else {
524 _appendEscapeSequence('m', attributeOptions.toArray());
525 }
526 attributeOptions.clear();
527 }
528
529 private Ansi _appendEscapeSequence(char command, Object... options) {
530 builder.append(FIRST_ESC_CHAR);
531 builder.append(SECOND_ESC_CHAR);
532 int size = options.length;
533 for (int i = 0; i < size; i++) {
534 if (i != 0) {
535 builder.append(';');
536 }
537 if (options[i] != null) {
538 builder.append(options[i]);
539 }
540 }
541 builder.append(command);
542 return this;
543 }
544
545 }