diff --git a/build.gradle b/build.gradle index 7114e195..dc4e2799 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,8 @@ dependencies { compile "org.springframework:spring-core:$springVersion" compile "org.springframework:spring-context-support:$springVersion" compile "commons-io:commons-io:$commonsioVersion" - compile "net.sourceforge.jline:jline:$jlineVersion" + //compile "net.sourceforge.jline:jline:$jlineVersion" + compile "jline:jline:2.11" compile "org.fusesource.jansi:jansi:$jansiVersion" compile "cglib:cglib:$cglibVersion" diff --git a/src/main/java/org/springframework/shell/commands/ConsoleCommands.java b/src/main/java/org/springframework/shell/commands/ConsoleCommands.java index 1effa045..2c132c17 100644 --- a/src/main/java/org/springframework/shell/commands/ConsoleCommands.java +++ b/src/main/java/org/springframework/shell/commands/ConsoleCommands.java @@ -15,8 +15,9 @@ */ package org.springframework.shell.commands; -import jline.ANSIBuffer; +import static org.fusesource.jansi.Ansi.ansi; +import org.fusesource.jansi.AnsiConsole; import org.springframework.shell.core.CommandMarker; import org.springframework.shell.core.annotation.CliCommand; import org.springframework.stereotype.Component; @@ -25,15 +26,14 @@ import org.springframework.stereotype.Component; * Commands related to the manipulation of the jline console * * @author Mark Pollack - * + * */ @Component -public class ConsoleCommands implements CommandMarker { - - @CliCommand(value = {"cls", "clear"}, help = "Clears the console") +public class ConsoleCommands implements CommandMarker { + + @CliCommand(value = { "cls", "clear" }, help = "Clears the console") public void clear() { - System.out.print(ANSIBuffer.ANSICodes.clrscr()); - System.out.print(ANSIBuffer.ANSICodes.gotoxy(0, 0)); + AnsiConsole.out().println(ansi().eraseScreen().cursor(0, 0)); } } diff --git a/src/main/java/org/springframework/shell/core/JLineCompletorAdapter.java b/src/main/java/org/springframework/shell/core/JLineCompletorAdapter.java index 2cc40ee8..c01c217e 100644 --- a/src/main/java/org/springframework/shell/core/JLineCompletorAdapter.java +++ b/src/main/java/org/springframework/shell/core/JLineCompletorAdapter.java @@ -18,17 +18,17 @@ package org.springframework.shell.core; import java.util.ArrayList; import java.util.List; -import jline.Completor; +import jline.console.completer.Completer; import org.springframework.util.Assert; /** * An implementation of JLine's {@link Completor} interface that delegates to a {@link Parser}. - * + * * @author Ben Alex * @since 1.0 */ -public class JLineCompletorAdapter implements Completor { +public class JLineCompletorAdapter implements Completer { // Fields private final Parser parser; @@ -38,7 +38,7 @@ public class JLineCompletorAdapter implements Completor { this.parser = parser; } - @SuppressWarnings({"rawtypes","unchecked"}) + @SuppressWarnings({ "rawtypes", "unchecked" }) public int complete(final String buffer, final int cursor, final List candidates) { int result; try { @@ -46,9 +46,12 @@ public class JLineCompletorAdapter implements Completor { List completions = new ArrayList(); result = parser.completeAdvanced(buffer, cursor, completions); for (Completion completion : completions) { - candidates.add(new jline.Completion(completion.getValue(), completion.getFormattedValue(), completion.getHeading())); + // candidates.add(new jline.Completion(completion.getValue(), completion.getFormattedValue(), completion + // .getHeading())); + candidates.add(completion.getValue()); } - } finally { + } + finally { JLineLogHandler.prohibitRedraw(); } return result; diff --git a/src/main/java/org/springframework/shell/core/JLineLogHandler.java b/src/main/java/org/springframework/shell/core/JLineLogHandler.java index 25e84586..653fd550 100644 --- a/src/main/java/org/springframework/shell/core/JLineLogHandler.java +++ b/src/main/java/org/springframework/shell/core/JLineLogHandler.java @@ -15,6 +15,10 @@ */ package org.springframework.shell.core; +import static org.fusesource.jansi.Ansi.ansi; +import static org.fusesource.jansi.Ansi.Color.GREEN; +import static org.fusesource.jansi.Ansi.Color.MAGENTA; +import static org.fusesource.jansi.Ansi.Color.RED; import static org.springframework.shell.support.util.OsUtils.LINE_SEPARATOR; import java.io.PrintWriter; @@ -24,16 +28,16 @@ import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; -import jline.ANSIBuffer; -import jline.ConsoleReader; +import jline.console.ConsoleReader; +import org.fusesource.jansi.Ansi; +import org.fusesource.jansi.Ansi.Attribute; import org.springframework.shell.support.util.IOUtils; -import org.springframework.shell.support.util.OsUtils; import org.springframework.util.Assert; /** * JDK logging {@link Handler} that emits log messages to a JLine {@link ConsoleReader}. - * + * * @author Ben Alex * @since 1.0 */ @@ -41,18 +45,26 @@ public class JLineLogHandler extends Handler { // Constants private static final boolean ROO_BRIGHT_COLORS = Boolean.getBoolean("roo.bright"); - private static final boolean SHELL_BRIGHT_COLORS = Boolean.getBoolean("spring.shell.bright"); - private static final boolean BRIGHT_COLORS = ROO_BRIGHT_COLORS || SHELL_BRIGHT_COLORS; + private static final boolean SHELL_BRIGHT_COLORS = Boolean.getBoolean("spring.shell.bright"); + + private static final boolean BRIGHT_COLORS = ROO_BRIGHT_COLORS || SHELL_BRIGHT_COLORS; // Fields private ConsoleReader reader; + private ShellPromptAccessor shellPromptAccessor; + private static ThreadLocal redrawProhibit = new ThreadLocal(); + private static String lastMessage; + private static boolean includeThreadName = false; + private boolean ansiSupported; + private String userInterfaceThreadName; + private static boolean suppressDuplicateMessages = true; public JLineLogHandler(final ConsoleReader reader, final ShellPromptAccessor shellPromptAccessor) { @@ -61,7 +73,7 @@ public class JLineLogHandler extends Handler { this.reader = reader; this.shellPromptAccessor = shellPromptAccessor; this.userInterfaceThreadName = Thread.currentThread().getName(); - this.ansiSupported = reader.getTerminal().isANSISupported(); + this.ansiSupported = reader.getTerminal().isAnsiSupported(); setFormatter(new Formatter() { @Override @@ -77,8 +89,10 @@ public class JLineLogHandler extends Handler { pw = new PrintWriter(sw); record.getThrown().printStackTrace(pw); sb.append(sw.toString()); - } catch (Exception ex) { - } finally { + } + catch (Exception ex) { + } + finally { IOUtils.closeQuietly(pw); } } @@ -88,10 +102,12 @@ public class JLineLogHandler extends Handler { } @Override - public void flush() {} + public void flush() { + } @Override - public void close() throws SecurityException {} + public void close() throws SecurityException { + } public static void prohibitRedraw() { redrawProhibit.set(true); @@ -127,35 +143,35 @@ public class JLineLogHandler extends Handler { } lastMessage = toDisplay; - StringBuffer buffer = reader.getCursorBuffer().getBuffer(); + StringBuilder buffer = reader.getCursorBuffer().copy().buffer; int cursor = reader.getCursorBuffer().cursor; if (reader.getCursorBuffer().length() > 0) { // The user has semi-typed something, so put a new line in so the debug message is separated - reader.printNewline(); + reader.println(); // We need to cancel whatever they typed (it's reset later on), so the line appears empty - reader.getCursorBuffer().setBuffer(new StringBuffer()); - reader.getCursorBuffer().cursor = 0; + reader.getCursorBuffer().clear(); } // This ensures nothing is ever displayed when redrawing the line - reader.setDefaultPrompt(""); + reader.setPrompt(""); reader.redrawLine(); // Now restore the line formatting settings back to their original - reader.setDefaultPrompt(shellPromptAccessor.getShellPrompt()); + reader.setPrompt(shellPromptAccessor.getShellPrompt()); - reader.getCursorBuffer().setBuffer(buffer); + reader.getCursorBuffer().write(buffer.toString()); reader.getCursorBuffer().cursor = cursor; - reader.printString(toDisplay); + reader.print(toDisplay); Boolean prohibitingRedraw = redrawProhibit.get(); if (prohibitingRedraw == null) { reader.redrawLine(); } - reader.flushConsole(); - } catch (Exception e) { + reader.flush(); + } + catch (Exception e) { reportError("Could not publish log message", e, Level.SEVERE.intValue()); } } @@ -165,7 +181,8 @@ public class JLineLogHandler extends Handler { String threadName; String eventString; - if (includeThreadName && !userInterfaceThreadName.equals(Thread.currentThread().getName()) && !"".equals(Thread.currentThread().getName())) { + if (includeThreadName && !userInterfaceThreadName.equals(Thread.currentThread().getName()) + && !"".equals(Thread.currentThread().getName())) { threadName = "[" + Thread.currentThread().getName() + "]"; // Build an event string that will indent nicely given the left hand side now contains a thread name @@ -174,26 +191,36 @@ public class JLineLogHandler extends Handler { lineSeparatorAndIndentingString.append(" "); } - eventString = " " + getFormatter().format(event).replace(LINE_SEPARATOR, LINE_SEPARATOR + lineSeparatorAndIndentingString.toString()); + eventString = " " + + getFormatter().format(event).replace(LINE_SEPARATOR, + LINE_SEPARATOR + lineSeparatorAndIndentingString.toString()); if (eventString.endsWith(lineSeparatorAndIndentingString.toString())) { eventString = eventString.substring(0, eventString.length() - lineSeparatorAndIndentingString.length()); } - } else { + } + else { threadName = ""; eventString = getFormatter().format(event); } if (ansiSupported) { + Ansi ansi = ansi(sb); if (event.getLevel().intValue() >= Level.SEVERE.intValue()) { - sb.append(getANSIBuffer().reverse(threadName).red(eventString)); - } else if (event.getLevel().intValue() >= Level.WARNING.intValue()) { - sb.append(getANSIBuffer().reverse(threadName).magenta(eventString)); - } else if (event.getLevel().intValue() >= Level.INFO.intValue()) { - sb.append(getANSIBuffer().reverse(threadName).green(eventString)); - } else { - sb.append(getANSIBuffer().reverse(threadName).append(eventString)); + ansi.a(Attribute.NEGATIVE_ON).a(threadName).a(Attribute.NEGATIVE_OFF).fg(RED).a(eventString).reset(); } - } else { + else if (event.getLevel().intValue() >= Level.WARNING.intValue()) { + ansi.a(Attribute.NEGATIVE_ON).a(threadName).a(Attribute.NEGATIVE_OFF).fg(MAGENTA).a(eventString) + .reset(); + } + else if (event.getLevel().intValue() >= Level.INFO.intValue()) { + ansi.a(Attribute.NEGATIVE_ON).a(threadName).a(Attribute.NEGATIVE_OFF).fg(GREEN).a(eventString).reset(); + } + else { + ansi.a(Attribute.NEGATIVE_ON).a(threadName).a(Attribute.NEGATIVE_OFF).a(eventString); + } + + } + else { sb.append(threadName).append(eventString); } @@ -201,30 +228,30 @@ public class JLineLogHandler extends Handler { } /** - * Makes text brighter if requested through system property 'roo.bright' and - * works around issue on Windows in using reverse() in combination with the - * Jansi lib, which leaves its 'negative' flag set unless reset explicitly. - * + * Makes text brighter if requested through system property 'roo.bright' and works around issue on Windows in using + * reverse() in combination with the Jansi lib, which leaves its 'negative' flag set unless reset explicitly. + * * @return new patched ANSIBuffer */ - public static ANSIBuffer getANSIBuffer() { - final char esc = (char) 27; - return new ANSIBuffer() { - @Override - public ANSIBuffer reverse(final String str) { - if (OsUtils.isWindows()) { - return super.reverse(str).append(ANSICodes.attrib(esc)); - } - return super.reverse(str); - }; - @Override - public ANSIBuffer attrib(final String str, final int code) { - if (BRIGHT_COLORS && 30 <= code && code <= 37) { - // This is a color code: add a 'bright' code - return append(esc + "[" + code + ";1m").append(str).append(ANSICodes.attrib(0)); - } - return super.attrib(str, code); - } - }; - } + // public static ANSIBuffer getANSIBuffer() { + // final char esc = (char) 27; + // return new ANSIBuffer() { + // @Override + // public ANSIBuffer reverse(final String str) { + // if (OsUtils.isWindows()) { + // return super.reverse(str).append(ANSICodes.attrib(esc)); + // } + // return super.reverse(str); + // }; + // + // @Override + // public ANSIBuffer attrib(final String str, final int code) { + // if (BRIGHT_COLORS && 30 <= code && code <= 37) { + // // This is a color code: add a 'bright' code + // return append(esc + "[" + code + ";1m").append(str).append(ANSICodes.attrib(0)); + // } + // return super.attrib(str, code); + // } + // }; + // } } diff --git a/src/main/java/org/springframework/shell/core/JLineShell.java b/src/main/java/org/springframework/shell/core/JLineShell.java index 709f914d..790699dd 100644 --- a/src/main/java/org/springframework/shell/core/JLineShell.java +++ b/src/main/java/org/springframework/shell/core/JLineShell.java @@ -15,14 +15,15 @@ */ package org.springframework.shell.core; +import static org.fusesource.jansi.Ansi.ansi; + import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; -import java.io.OutputStreamWriter; +import java.io.OutputStream; import java.io.PrintStream; -import java.io.PrintWriter; import java.nio.charset.Charset; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -37,12 +38,16 @@ import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; -import jline.ANSIBuffer; -import jline.ANSIBuffer.ANSICodes; -import jline.ConsoleReader; import jline.WindowsTerminal; +import jline.console.ConsoleReader; +import jline.console.history.MemoryHistory; import org.apache.commons.io.input.ReversedLinesFileReader; +import org.fusesource.jansi.Ansi; +import org.fusesource.jansi.Ansi.Attribute; +import org.fusesource.jansi.Ansi.Color; +import org.fusesource.jansi.Ansi.Erase; +import org.fusesource.jansi.AnsiConsole; import org.springframework.shell.event.ShellStatus; import org.springframework.shell.event.ShellStatus.Status; import org.springframework.shell.event.ShellStatusListener; @@ -54,16 +59,16 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; - /** - * Uses the feature-rich JLine library to provide an interactive shell. - * + * Uses the feature-rich JLine library to provide an interactive + * shell. + * *

* Due to Windows' lack of color ANSI services out-of-the-box, this implementation automatically detects the classpath * presence of Jansi and uses it if present. This library is not necessary - * for *nix machines, which support colour ANSI without any special effort. This implementation has been written to - * use reflection in order to avoid hard dependencies on Jansi. - * + * for *nix machines, which support colour ANSI without any special effort. This implementation has been written to use + * reflection in order to avoid hard dependencies on Jansi. + * * @author Ben Alex * @author Jarred Li * @since 1.0 @@ -72,20 +77,31 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl // Constants private static final String ANSI_CONSOLE_CLASSNAME = "org.fusesource.jansi.AnsiConsole"; + private static final boolean JANSI_AVAILABLE = ClassUtils.isPresent(ANSI_CONSOLE_CLASSNAME, JLineShell.class.getClassLoader()); + private static final char ESCAPE = 27; + private static final String BEL = "\007"; + // Fields protected ConsoleReader reader; + private boolean developmentMode = false; + private FileWriter fileLog; + private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + protected ShellStatusListener statusListener; // ROO-836 + /** key: slot name, value: flashInfo instance */ private final Map flashInfoMap = new HashMap(); + /** key: row number, value: eraseLineFromPosition */ private final Map rowErasureMap = new HashMap(); + private boolean shutdownHookFired = false; // ROO-1599 private int historySize; @@ -101,7 +117,7 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl removeHandlers(mainLogger); mainLogger.addHandler(handler); - reader.addCompletor(new JLineCompletorAdapter(getParser())); + reader.addCompleter(new JLineCompletorAdapter(getParser())); reader.setBellEnabled(true); if (Boolean.getBoolean("jline.nobell")) { @@ -111,11 +127,11 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl // reader.setDebug(new PrintWriter(new FileWriter("writer.debug", true))); openFileLogIfPossible(); - this.reader.getHistory().setMaxSize(getHistorySize()); + ((MemoryHistory) this.reader.getHistory()).setMaxSize(getHistorySize()); // Try to build previous command history from the project's log String[] filteredLogEntries = filterLogEntry(); for (String logEntry : filteredLogEntries) { - reader.getHistory().addToHistory(logEntry); + reader.getHistory().add(logEntry); } flashMessageRenderer(); @@ -136,7 +152,8 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl shutdownHookFired = true; } }, getProductName() + " JLine Shutdown Hook")); - } catch (Throwable t) { + } + catch (Throwable t) { } // Handle any "execute-then-quit" operation @@ -161,14 +178,14 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl /** * read history commands from history log. the history size if determined by --histsize options. - * + * * @return history commands */ private String[] filterLogEntry() { ArrayList entries = new ArrayList(); try { - ReversedLinesFileReader reader = new ReversedLinesFileReader( - new File(getHistoryFileName()),4096,Charset.forName("UTF-8")); + ReversedLinesFileReader reader = new ReversedLinesFileReader(new File(getHistoryFileName()), 4096, + Charset.forName("UTF-8")); int size = 0; String line = null; while ((line = reader.readLine()) != null) { @@ -182,19 +199,19 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl } } } - } catch (IOException e) { - logger.warning("read history file failed. Reason:"+ e.getMessage()); + } + catch (IOException e) { + logger.warning("read history file failed. Reason:" + e.getMessage()); } Collections.reverse(entries); return entries.toArray(new String[0]); } /** - * Creates new jline ConsoleReader. On Windows if jansi is available, uses - * createAnsiWindowsReader(). Otherwise, always creates a default ConsoleReader. - * Sub-classes of this class can plug in their version of ConsoleReader - * by overriding this method, if required. - * + * Creates new jline ConsoleReader. On Windows if jansi is available, uses createAnsiWindowsReader(). Otherwise, + * always creates a default ConsoleReader. Sub-classes of this class can plug in their version of ConsoleReader by + * overriding this method, if required. + * * @return a jline ConsoleReader instance */ protected ConsoleReader createConsoleReader() { @@ -202,16 +219,18 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl try { if (isJansiAvailable()) { try { - consoleReader = createAnsiWindowsReader(); - } catch (Exception e) { + consoleReader = createAnsiWindowsReader(); + } + catch (Exception e) { // Try again using default ConsoleReader constructor logger.warning("Can't initialize jansi AnsiConsole, falling back to default: " + e); } } if (consoleReader == null) { - consoleReader = new ConsoleReader(); + consoleReader = new ConsoleReader(); } - } catch (IOException ioe) { + } + catch (IOException ioe) { throw new IllegalStateException("Cannot start console class", ioe); } return consoleReader; @@ -244,19 +263,20 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl @Override public void setPromptPath(final String path, final boolean overrideStyle) { - if (reader.getTerminal().isANSISupported()) { - ANSIBuffer ansi = JLineLogHandler.getANSIBuffer(); + if (reader.getTerminal().isAnsiSupported()) { + // ANSIBuffer ansi = JLineLogHandler.getANSIBuffer(); + Ansi ansi = ansi(); if (path == null || "".equals(path)) { - shellPrompt = ansi.yellow(getPromptText()).toString(); + shellPrompt = ansi.fg(Color.YELLOW).a(getPromptText()).reset().toString(); } else { if (overrideStyle) { - ansi.append(path); + ansi.a(path); } else { - ansi.cyan(path); + ansi.fg(Color.CYAN).a(path).reset(); } - shellPrompt = ansi.yellow(" " + getPromptText()).toString(); + shellPrompt = ansi.fg(Color.YELLOW).a(" " + getPromptText()).toString(); } } else { @@ -265,20 +285,20 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl } // The shellPrompt is now correct; let's ensure it now gets used - reader.setDefaultPrompt(JLineShell.shellPrompt); + reader.setPrompt(AbstractShell.shellPrompt); } protected ConsoleReader createAnsiWindowsReader() throws Exception { // Get decorated OutputStream that parses ANSI-codes - final PrintStream ansiOut = (PrintStream) ClassUtils.forName(ANSI_CONSOLE_CLASSNAME, - JLineShell.class.getClassLoader()).getMethod("out").invoke(null); + final PrintStream ansiOut = (PrintStream) ClassUtils + .forName(ANSI_CONSOLE_CLASSNAME, JLineShell.class.getClassLoader()).getMethod("out").invoke(null); WindowsTerminal ansiTerminal = new WindowsTerminal() { @Override - public boolean isANSISupported() { + public synchronized boolean isAnsiSupported() { return true; } }; - ansiTerminal.initializeTerminal(); + ansiTerminal.init(); // Make sure to reset the original shell's colors on shutdown by closing the stream statusListener = new ShellStatusListener() { public void onShellStatusChange(final ShellStatus oldStatus, final ShellStatus newStatus) { @@ -289,14 +309,17 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl }; addShellStatusListener(statusListener); - return new ConsoleReader(new FileInputStream(FileDescriptor.in), new PrintWriter(new OutputStreamWriter( - ansiOut, - // Default to Cp850 encoding for Windows console output (ROO-439) - System.getProperty("jline.WindowsTerminal.output.encoding", "Cp850"))), null, ansiTerminal); + // return new ConsoleReader(new FileInputStream(FileDescriptor.in), new PrintWriter(new OutputStreamWriter( + // ansiOut, + // // Default to Cp850 encoding for Windows console output (ROO-439) + // System.getProperty("jline.WindowsTerminal.output.encoding", "Cp850"))), null, ansiTerminal); + + OutputStream out = AnsiConsole.wrapOutputStream(ansiOut); + return new ConsoleReader(new FileInputStream(FileDescriptor.in), out, ansiTerminal); } private void flashMessageRenderer() { - if (!reader.getTerminal().isANSISupported()) { + if (!reader.getTerminal().isAnsiSupported()) { return; } // Setup a thread to ensure flash messages are displayed and cleared correctly @@ -326,7 +349,8 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl } try { Thread.sleep(200); - } catch (InterruptedException ignore) { + } + catch (InterruptedException ignore) { } } } @@ -341,25 +365,25 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl Assert.hasText(slot, "Slot name must be specified for a flash message"); if (Shell.WINDOW_TITLE_SLOT.equals(slot)) { - if (reader != null && reader.getTerminal().isANSISupported()) { + if (reader != null && reader.getTerminal().isAnsiSupported()) { // We can probably update the window title, as requested if (!StringUtils.hasText(message)) { System.out.println("No text"); } - ANSIBuffer buff = JLineLogHandler.getANSIBuffer(); - buff.append(ESCAPE + "]0;").append(message).append(BEL); - String stg = buff.toString(); + Ansi ansi = ansi(); + ansi.a(ESCAPE + "]0;").a(message).a(BEL); try { - reader.printString(stg); - reader.flushConsole(); - } catch (IOException ignored) { + reader.print(ansi.toString()); + reader.flush(); + } + catch (IOException ignored) { } } return; } - if ((reader != null && !reader.getTerminal().isANSISupported())) { + if ((reader != null && !reader.getTerminal().isAnsiSupported())) { super.flash(level, message, slot); return; } @@ -408,12 +432,12 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl // Externally synchronized via the two calling methods having a mutex on flashInfoMap private void doAnsiFlash(final int row, final Level level, final String message) { - ANSIBuffer buff = JLineLogHandler.getANSIBuffer(); + Ansi ansi = ansi(); if (isAppleTerminal()) { - buff.append(ESCAPE + "7"); + ansi.a(ESCAPE + "7"); } else { - buff.append(ANSICodes.save()); + ansi.saveCursorPosition(); } // Figure out the longest line we're presently displaying (or were) and erase the line from that position @@ -428,8 +452,8 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl // There is nothing to erase } else { - buff.append(ANSICodes.gotoxy(row, mostFurtherLeftColNumber)); - buff.append(ANSICodes.clreol()); // Clear what was present on the line + ansi.cursor(row, mostFurtherLeftColNumber); + ansi.eraseLine(Erase.FORWARD); // Clear what was present on the line } if (("".equals(message))) { @@ -442,27 +466,27 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl return; // ROO-1599 } // They want some message displayed - int startFrom = reader.getTermwidth() - message.length() + 1; + int startFrom = reader.getTerminal().getWidth() - message.length() + 1; if (startFrom < 1) { startFrom = 1; } - buff.append(ANSICodes.gotoxy(row, startFrom)); - buff.reverse(message); + ansi.cursor(row, startFrom); + ansi.a(Attribute.NEGATIVE_ON).a(message).a(Attribute.NEGATIVE_OFF); // Record we want to erase from this positioning next time (so we clean up after ourselves) rowErasureMap.put(row, startFrom); } if (isAppleTerminal()) { - buff.append(ESCAPE + "8"); + ansi.a(ESCAPE + "8"); } else { - buff.append(ANSICodes.restore()); + ansi.reset(); } - String stg = buff.toString(); try { - reader.printString(stg); - reader.flushConsole(); - } catch (IOException ignored) { + reader.print(ansi.toString()); + reader.flush(); + } + catch (IOException ignored) { } } @@ -487,9 +511,10 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl prompt = newPrmpt; setPromptPath(null); } - //System.out.println("executed command:" + line); + // System.out.println("executed command:" + line); } - } catch (IOException ioe) { + } + catch (IOException ioe) { throw new IllegalStateException("Shell line reading failure", ioe); } setShellStatus(Status.SHUTTING_DOWN); @@ -497,7 +522,8 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl public void setDevelopmentMode(final boolean developmentMode) { JLineLogHandler.setIncludeThreadName(developmentMode); - JLineLogHandler.setSuppressDuplicateMessages(!developmentMode); // We want to see duplicate messages during development time (ROO-1873) + JLineLogHandler.setSuppressDuplicateMessages(!developmentMode); // We want to see duplicate messages during + // development time (ROO-1873) this.developmentMode = developmentMode; } @@ -509,14 +535,14 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl try { fileLog = new FileWriter(getHistoryFileName(), true); // First write, so let's record the date and time of the first user command - fileLog.write("// " + getProductName() + " " + versionInfo() + " log opened at " + df.format(new Date()) + "\n"); + fileLog.write("// " + getProductName() + " " + versionInfo() + " log opened at " + df.format(new Date()) + + "\n"); fileLog.flush(); - } catch (IOException ignoreIt) { + } + catch (IOException ignoreIt) { } } - - @Override protected void logCommandToOutput(final String processedLine) { if (fileLog == null) { @@ -531,17 +557,19 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl fileLog.flush(); // So tail -f will show it's working if (getExitShellRequest() != null) { // Shutting down, so close our file (we can always reopen it later if needed) - fileLog.write("// " + getProductName() + " " + versionInfo() + " log closed at " + df.format(new Date()) + "\n"); + fileLog.write("// " + getProductName() + " " + versionInfo() + " log closed at " + + df.format(new Date()) + "\n"); IOUtils.closeQuietly(fileLog); fileLog = null; } - } catch (IOException ignoreIt) { + } + catch (IOException ignoreIt) { } } /** * Obtains the "roo.home" from the system property, falling back to the current working directory if missing. - * + * * @return the 'roo.home' system property */ @Override @@ -550,7 +578,8 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl if (rooHome == null) { try { rooHome = new File(".").getCanonicalPath(); - } catch (Exception e) { + } + catch (Exception e) { throw new IllegalStateException(e); } } @@ -561,7 +590,8 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl * Should be called by a subclass before deactivating the shell. */ protected void closeShell() { - // Notify we're closing down (normally our status is already shutting_down, but if it was a CTRL+C via the o.s.r.bootstrap.Main hook) + // Notify we're closing down (normally our status is already shutting_down, but if it was a CTRL+C via the + // o.s.r.bootstrap.Main hook) setShellStatus(Status.SHUTTING_DOWN); if (statusListener != null) { removeShellStatusListener(statusListener); @@ -570,15 +600,18 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl private static class FlashInfo { String flashMessage; + long flashMessageUntil; + Level flashLevel; + int rowNumber; } /** * get history file name from provider. The provider has highest order * org.springframework.core.Ordered.getOder will win. - * + * * @return history file name */ abstract protected String getHistoryFileName(); @@ -586,21 +619,21 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl /** * get prompt text from provider. The provider has highest order * org.springframework.core.Ordered.getOder will win. - * + * * @return prompt text */ abstract protected String getPromptText(); /** * get product name - * + * * @return Product Name */ abstract protected String getProductName(); /** * get version information - * + * * @return Version */ protected String getVersion() { @@ -620,11 +653,10 @@ public abstract class JLineShell extends AbstractShell implements Shell, Runnabl public void setHistorySize(int historySize) { this.historySize = historySize; } - - private static boolean isAppleTerminal() - { - final String terminalName = System.getenv( "TERM_PROGRAM" ); - return ("Apple_Terminal".equalsIgnoreCase( terminalName ) || Boolean.getBoolean("is.apple.terminal")); + + private static boolean isAppleTerminal() { + final String terminalName = System.getenv("TERM_PROGRAM"); + return ("Apple_Terminal".equalsIgnoreCase(terminalName) || Boolean.getBoolean("is.apple.terminal")); } }