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 }