diff --git a/builtins/src/main/java/org/jline/builtins/Commands.java b/builtins/src/main/java/org/jline/builtins/Commands.java index fe117baee..06fd5cff0 100644 --- a/builtins/src/main/java/org/jline/builtins/Commands.java +++ b/builtins/src/main/java/org/jline/builtins/Commands.java @@ -12,6 +12,7 @@ import java.io.InputStream; import java.io.PrintStream; import java.nio.file.Path; +import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -24,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.regex.Pattern; import java.util.Set; import java.util.TreeSet; import java.util.function.Consumer; @@ -31,6 +33,7 @@ import java.util.function.Supplier; import org.jline.builtins.Completers.CompletionData; +import org.jline.builtins.Options; import org.jline.builtins.Source.StdInSource; import org.jline.builtins.Source.URLSource; import org.jline.keymap.KeyMap; @@ -159,26 +162,31 @@ public static void less(Terminal terminal, InputStream in, PrintStream out, Prin } public static void history(LineReader reader, PrintStream out, PrintStream err, - String[] argv) throws IOException { + String[] argv) throws IOException, IllegalArgumentException { final String[] usage = { "history - list history of commands", - "Usage: history [OPTIONS]", + "Usage: history [-dnrfEi] [-m match] [first] [last]", + " history --clear", + " history --save", " -? --help Displays command help", " --clear Clear history", " --save Save history", - " -d Print timestamps for each event"}; - + " -m match If option -m is present the first argument is taken as a pattern", + " and only the history events matching the pattern will be shown", + " -d Print timestamps for each event", + " -f Print full time-date stamps in the US format", + " -E Print full time-date stamps in the European format", + " -i Print full time-date stamps in ISO8601 format", + " -n Suppresses command numbers", + " -r Reverses the order of the commands", + " [first] [last] These optional arguments are numbers. A negative number is", + " used as an offset to the current history event number"}; Options opt = Options.compile(usage).parse(argv); if (opt.isSet("help")) { opt.usage(err); return; } - if (!opt.args().isEmpty()) { - err.println("usage: history [OPTIONS]"); - return; - } - History history = reader.getHistory(); if (opt.isSet("clear")) { history.purge(); @@ -189,16 +197,59 @@ public static void history(LineReader reader, PrintStream out, PrintStream err, if (opt.isSet("clear") || opt.isSet("save")) { return; } + int argId = 0; + Pattern pattern = null; + if (opt.isSet("m")) { + if (opt.args().size() == 0) { + throw new IllegalArgumentException(); + } + String sp = opt.args().get(argId++); + pattern = Pattern.compile(sp.toString()); + } + int firstId = opt.args().size() > argId ? parseInteger(opt.args().get(argId++)) : -17; + int lastId = opt.args().size() > argId ? parseInteger(opt.args().get(argId++)) : -1; + firstId = historyId(firstId, history.size() - 1); + lastId = historyId(lastId, history.size() - 1); + if (firstId > lastId) { + throw new IllegalArgumentException(); + } + int tot = lastId - firstId + 1; + int listed = 0; final Highlighter highlighter = reader.getHighlighter(); - for (History.Entry entry : history) { + Iterator iter = null; + if (opt.isSet("r")) { + iter = history.reverseIterator(lastId); + } else { + iter = history.iterator(firstId); + } + while (iter.hasNext() && listed < tot) { + History.Entry entry = iter.next(); + listed++; + if (pattern != null && !pattern.matcher(entry.line()).matches()) { + continue; + } AttributedStringBuilder sb = new AttributedStringBuilder(); - sb.append(" "); - sb.styled(AttributedStyle::bold, String.format("%3d", entry.index() + 1)); - if (opt.isSet("d")) { + if (!opt.isSet("n")) { + sb.append(" "); + sb.styled(AttributedStyle::bold, String.format("%3d", entry.index())); + } + if (opt.isSet("d") || opt.isSet("f") || opt.isSet("E") || opt.isSet("i")) { sb.append(" "); - LocalTime lt = LocalTime.from(entry.time().atZone(ZoneId.systemDefault())) - .truncatedTo(ChronoUnit.SECONDS); - DateTimeFormatter.ISO_LOCAL_TIME.formatTo(lt, sb); + if (opt.isSet("d")) { + LocalTime lt = LocalTime.from(entry.time().atZone(ZoneId.systemDefault())) + .truncatedTo(ChronoUnit.SECONDS); + DateTimeFormatter.ISO_LOCAL_TIME.formatTo(lt, sb); + } else { + LocalDateTime lt = LocalDateTime.from(entry.time().atZone(ZoneId.systemDefault()) + .truncatedTo(ChronoUnit.MINUTES)); + String format = "yyyy-MM-dd hh:mm"; + if (opt.isSet("f")) { + format = "MM/dd/yy hh:mm"; + } else if (opt.isSet("E")) { + format = "dd.MM.yyyy hh:mm"; + } + DateTimeFormatter.ofPattern(format).formatTo(lt, sb); + } } sb.append(" "); sb.append(highlighter.highlight(reader, entry.line())); @@ -206,7 +257,28 @@ public static void history(LineReader reader, PrintStream out, PrintStream err, } } - public static void complete(LineReader reader, PrintStream out, PrintStream err, + private static int historyId(int id, int maxId) { + int out = id; + if (id < 0) { + out = maxId + id + 1; + } + if (out < 0) { + out = 0; + } else if (out > maxId) { + out = maxId; + } + return out; + } + + private static int parseInteger(String s) throws IllegalArgumentException { + try { + return Integer.parseInt(s); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + } + + public static void complete(LineReader reader, PrintStream out, PrintStream err, Map> completions, String[] argv) { final String[] usage = { diff --git a/builtins/src/main/java/org/jline/builtins/Options.java b/builtins/src/main/java/org/jline/builtins/Options.java index e49b4ec51..d07889f80 100644 --- a/builtins/src/main/java/org/jline/builtins/Options.java +++ b/builtins/src/main/java/org/jline/builtins/Options.java @@ -398,7 +398,7 @@ else if (needArg != null) { needArg = null; needOpt = null; } - else if (!arg.startsWith("-") || "-".equals(oarg)) { + else if (!arg.startsWith("-") || (arg.length() > 1 && Character.isDigit(arg.charAt(1))) || "-".equals(oarg)) { if (optionsFirst) endOpt = true; xargs.add(oarg); diff --git a/builtins/src/test/java/org/jline/example/Example.java b/builtins/src/test/java/org/jline/example/Example.java index ed0be2fc4..febdacb38 100644 --- a/builtins/src/test/java/org/jline/example/Example.java +++ b/builtins/src/test/java/org/jline/example/Example.java @@ -17,6 +17,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import org.jline.builtins.Commands; import org.jline.builtins.Completers; import org.jline.builtins.Completers.TreeCompleter; import org.jline.keymap.KeyMap; @@ -134,7 +135,7 @@ public static void main(String[] args) throws IOException { case "brackets": prompt = "long-prompt> "; DefaultParser p2 = new DefaultParser(); - p2.eofOnUnclosedBracket(Bracket.CURLY,Bracket.ROUND,Bracket.SQUARE); + p2.setEofOnUnclosedBracket(Bracket.CURLY, Bracket.ROUND, Bracket.SQUARE); parser = p2; break label; case "foo": @@ -305,6 +306,7 @@ public void complete(LineReader reader, ParsedLine line, List candida break; } ParsedLine pl = reader.getParser().parse(line, 0); + String[] argv = pl.words().subList(1, pl.words().size()).toArray(new String[0]); if ("set".equals(pl.word())) { if (pl.words().size() == 3) { reader.setVariable(pl.words().get(1), pl.words().get(2)); @@ -380,6 +382,9 @@ else if ("cls".equals(pl.word())) { else if ("sleep".equals(pl.word())) { Thread.sleep(3000); } + else if ("history".equals(pl.word())) { + Commands.history(reader, System.out, System.err, argv); + } } } catch (Throwable t) {