From c31cca7e6b4b48518c6aee5a076b00b67c7be9ea Mon Sep 17 00:00:00 2001 From: Nikita Gryzlov Date: Thu, 3 Oct 2019 17:49:51 +0300 Subject: [PATCH 01/14] Fix NPE --- .../java/org/jline/terminal/impl/AbstractWindowsTerminal.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java index 3523a8deb..586d61353 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java @@ -216,7 +216,9 @@ public void setSize(Size size) { public void close() throws IOException { super.close(); closing = true; - pump.interrupt(); + if (pump != null) { + pump.interrupt(); + } ShutdownHooks.remove(closer); for (Map.Entry entry : nativeHandlers.entrySet()) { Signals.unregister(entry.getKey().name(), entry.getValue()); From f151862b1be23da74d56246cca18f3d0e245eae5 Mon Sep 17 00:00:00 2001 From: mattirn Date: Thu, 3 Oct 2019 19:27:48 +0200 Subject: [PATCH 02/14] Added autopair custom widgets. --- .../main/java/org/jline/builtins/Widgets.java | 277 ++++++++++++++++++ .../test/java/org/jline/example/Example.java | 23 +- 2 files changed, 294 insertions(+), 6 deletions(-) create mode 100644 builtins/src/main/java/org/jline/builtins/Widgets.java diff --git a/builtins/src/main/java/org/jline/builtins/Widgets.java b/builtins/src/main/java/org/jline/builtins/Widgets.java new file mode 100644 index 000000000..41fb0c81b --- /dev/null +++ b/builtins/src/main/java/org/jline/builtins/Widgets.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2002-2019, the original author or authors. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.builtins; + +import static org.jline.keymap.KeyMap.del; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jline.keymap.KeyMap; +import org.jline.reader.Binding; +import org.jline.reader.Buffer; +import org.jline.reader.LineReader; +import org.jline.reader.Reference; +import org.jline.reader.Widget; +import org.jline.reader.impl.LineReaderImpl; + +public class Widgets { + + private static void addWidget(Map widgets, String name, Widget widget) { + widgets.put(name, namedWidget(name, widget)); + } + + private static Widget namedWidget(final String name, final Widget widget) { + return new Widget() { + @Override + public String toString() { + return name; + } + @Override + public boolean apply() { + return widget.apply(); + } + }; + } + + public static class AutopairWidgets { + /* + * Inspired by zsh-autopair + * https://github.com/hlissner/zsh-autopair + */ + private static final Map LBOUNDS; + private static final Map RBOUNDS; + private final Map pairs; + private final LineReaderImpl reader; + private boolean autopair = false; + { + pairs = new HashMap<>(); + pairs.put("`", "`"); + pairs.put("'", "'"); + pairs.put("\"", "\""); + pairs.put("[", "]"); + pairs.put("(", ")"); + pairs.put(" ", " "); + } + static { + LBOUNDS = new HashMap<>(); + LBOUNDS.put("all", "[.:/\\!]"); + LBOUNDS.put("quotes", "[\\]})a-zA-Z0-9]"); + LBOUNDS.put("spaces", "[^{(\\[]"); + LBOUNDS.put("braces", ""); + LBOUNDS.put("`", "`"); + LBOUNDS.put("\"", "\""); + LBOUNDS.put("'", "'"); + RBOUNDS = new HashMap<>(); + RBOUNDS.put("all", "[\\[{(<,.:?/%$!a-zA-Z0-9]"); + RBOUNDS.put("quotes", "[a-zA-Z0-9]"); + RBOUNDS.put("spaces", "[^\\]})]"); + RBOUNDS.put("braces", ""); + RBOUNDS.put("`", ""); + RBOUNDS.put("\"", ""); + RBOUNDS.put("'", ""); + } + + public AutopairWidgets(LineReader reader) { + this(reader, false); + } + + public AutopairWidgets(LineReader reader, boolean addCurlyBrackets) { + this.reader = (LineReaderImpl)reader; + if (addCurlyBrackets) { + pairs.put("{", "}"); + } + addWidget(reader.getWidgets(), "autopair-insert", this::autopairInsert); + addWidget(reader.getWidgets(), "autopair-close", this::autopairClose); + addWidget(reader.getWidgets(), "autopair-delete", this::autopairDelete); + } + + /* + * Widgets + */ + public boolean autopairInsert() { + if (pairs.containsKey(reader.getLastBinding())) { + if (canSkip(reader.getLastBinding())) { + reader.callWidget(LineReader.FORWARD_CHAR); + } else if (canPair(reader.getLastBinding())) { + reader.callWidget(LineReader.SELF_INSERT); + reader.putString(pairs.get(reader.getLastBinding())); + reader.callWidget(LineReader.BACKWARD_CHAR); + } else { + reader.callWidget(LineReader.SELF_INSERT); + } + } else { + reader.callWidget(LineReader.SELF_INSERT); + } + return true; + } + + public boolean autopairClose() { + if (pairs.containsValue(reader.getLastBinding()) + && reader.getBuffer().currChar() == reader.getLastBinding().charAt(0)) { + reader.callWidget(LineReader.FORWARD_CHAR); + } else { + reader.callWidget(LineReader.SELF_INSERT); + } + return true; + } + + public boolean autopairDelete() { + if (pairs.containsKey(prevChar()) && pairs.get(prevChar()).charAt(0) == currChar().charAt(0) + && canDelete(prevChar())) { + reader.callWidget(LineReader.DELETE_CHAR); + } + reader.callWidget(LineReader.BACKWARD_DELETE_CHAR); + return false; + } + + /* + * key bindings... + * + */ + public boolean toggleKeyBindings() { + if(autopair) { + defaultBindings(); + } else { + autopairBindings(); + } + return autopair; + } + + private void autopairBindings() { + if (autopair) { + return; + } + KeyMap map = reader.getKeyMaps().get(LineReader.MAIN); + for (Map.Entry p: pairs.entrySet()) { + map.bind(new Reference("autopair-insert"), p.getKey()); + if (!p.getKey().equals(p.getValue())) { + map.bind(new Reference("autopair-close"), p.getValue()); + } + } + map.bind(new Reference("autopair-delete"), del()); + autopair = true; + } + + private void defaultBindings() { + if (!autopair) { + return; + } + KeyMap map = reader.getKeyMaps().get(LineReader.MAIN); + for (Map.Entry p: pairs.entrySet()) { + map.bind(new Reference(LineReader.SELF_INSERT), p.getKey()); + if (p.getKey().equals(p.getValue())) { + map.bind(new Reference(LineReader.SELF_INSERT), p.getValue()); + } + } + map.bind(new Reference(LineReader.BACKWARD_DELETE_CHAR), del()); + autopair = false; + } + /* + * helpers + */ + private boolean canPair(String d) { + if (balanced(d) && !nexToBoundary(d)) { + if (d.equals(" ") && (prevChar().equals(" ") || currChar().equals(" "))) { + return false; + } else { + return true; + } + } + return false; + } + + private boolean canSkip(String d) { + if (pairs.get(d).equals(d) && d.charAt(0) != ' ' && reader.getBuffer().currChar() == d.charAt(0) + && balanced(d)) { + return true; + } + return false; + } + + private boolean canDelete(String d) { + if (balanced(d)) { + return true; + } + return false; + } + + private boolean balanced(String d) { + boolean out = false; + Buffer buf = reader.getBuffer(); + String lbuf = buf.upToCursor(); + String rbuf = buf.substring(lbuf.length()); + String regx1 = pairs.get(d).equals(d)? d : "\\"+d; + String regx2 = pairs.get(d).equals(d)? pairs.get(d) : "\\"+pairs.get(d); + int llen = lbuf.length() - lbuf.replaceAll(regx1, "").length(); + int rlen = rbuf.length() - rbuf.replaceAll(regx2, "").length(); + if (llen == 0 && rlen == 0) { + out = true; + } else if (d.charAt(0) == ' ') { + out = true; + } else if (pairs.get(d).equals(d)) { + if ( llen == rlen || (llen + rlen) % 2 == 0 ) { + out = true; + } + } else { + int l2len = lbuf.length() - lbuf.replaceAll(regx2, "").length(); + int r2len = rbuf.length() - rbuf.replaceAll(regx1, "").length(); + int ltotal = llen - l2len; + int rtotal = rlen - r2len; + if (ltotal < 0) { + ltotal = 0; + } + if (ltotal >= rtotal) { + out = true; + } + } + return out; + } + + private String prevChar() { + return String.valueOf((char)reader.getBuffer().prevChar()); + } + + private String currChar() { + return String.valueOf((char)reader.getBuffer().currChar()); + } + + private boolean boundary(String lb, String rb) { + if((lb.length() > 0 && prevChar().matches(lb)) + || + (rb.length() > 0 && currChar().matches(rb))) { + return true; + } + return false; + } + + private boolean nexToBoundary(String d) { + List bk = new ArrayList<>(); + bk.add("all"); + if (d.matches("['\"`]")) { + bk.add("quotes"); + } else if (d.matches("[{\\[(<]")) { + bk.add("braces"); + } else if (d.charAt(0) == ' ') { + bk.add("spaces"); + } + if (LBOUNDS.containsKey(d) && RBOUNDS.containsKey(d)) { + bk.add(d); + } + for (String k: bk) { + if (boundary(LBOUNDS.get(k), RBOUNDS.get(k))) { + return true; + } + } + return false; + } + } +} diff --git a/builtins/src/test/java/org/jline/example/Example.java b/builtins/src/test/java/org/jline/example/Example.java index 2ceb52dd6..45fa800e8 100644 --- a/builtins/src/test/java/org/jline/example/Example.java +++ b/builtins/src/test/java/org/jline/example/Example.java @@ -24,6 +24,7 @@ import org.jline.builtins.Completers.TreeCompleter; import org.jline.builtins.Options.HelpException; import org.jline.builtins.TTop; +import org.jline.builtins.Widgets.AutopairWidgets; import org.jline.keymap.KeyMap; import org.jline.reader.*; import org.jline.reader.impl.DefaultParser; @@ -79,7 +80,7 @@ public static void usage() { System.out.println(u); } } - + public static void help() { String[] help = { "List of available commands:" @@ -94,6 +95,7 @@ public static void help() { , " ttop display and update sorted information about threads" , " unsetopt unset options" , " widget UNAVAILABLE" + , " autopair toggle brackets/quotes autopair key bindings" , " Example:" , " cls clear screen" , " help list available commands" @@ -107,7 +109,7 @@ public static void help() { for (String u: help) { System.out.println(u); } - + } public static void main(String[] args) throws IOException { @@ -120,7 +122,7 @@ public static void main(String[] args) throws IOException { boolean timer = false; TerminalBuilder builder = TerminalBuilder.builder(); - + if ((args == null) || (args.length == 0)) { usage(); @@ -289,8 +291,8 @@ public void complete(LineReader reader, ParsedLine line, List candida } } } - - Terminal terminal = builder.build(); + + Terminal terminal = builder.build(); System.out.println(terminal.getName()+": "+terminal.getType()); System.out.println("\nhelp: list available commands"); LineReader reader = LineReaderBuilder.builder() @@ -299,6 +301,7 @@ public void complete(LineReader reader, ParsedLine line, List candida .parser(parser) .variable(LineReader.SECONDARY_PROMPT_PATTERN, "%M%P > ") .build(); + AutopairWidgets autopairWidgets = new AutopairWidgets(reader); if (timer) { Executors.newScheduledThreadPool(1) @@ -402,7 +405,7 @@ else if ("sleep".equals(pl.word())) { } else if ("tmux".equals(pl.word())) { Commands.tmux(terminal, System.out, System.err, - null, //Supplier getter, + null, //Supplier getter, null, //Consumer setter, null, //Consumer runner, argv); @@ -442,6 +445,14 @@ else if ("unsetopt".equals(pl.word())) { else if ("ttop".equals(pl.word())) { TTop.ttop(terminal, System.out, System.err, argv); } + else if ("autopair".equals(pl.word())) { + terminal.writer().print("Autopair widgets are "); + if (autopairWidgets.toggleKeyBindings()){ + terminal.writer().println("bounded."); + } else { + terminal.writer().println("unbounded."); + } + } else if ("help".equals(pl.word()) || "?".equals(pl.word())) { help(); } From e4839f8934d4a2c7d966a0bc809dd5c66dbe7210 Mon Sep 17 00:00:00 2001 From: mattirn Date: Fri, 4 Oct 2019 08:47:48 +0200 Subject: [PATCH 03/14] AutopairWidget: fixed typo & small improvements --- builtins/src/main/java/org/jline/builtins/Widgets.java | 8 ++++---- builtins/src/test/java/org/jline/example/Example.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/builtins/src/main/java/org/jline/builtins/Widgets.java b/builtins/src/main/java/org/jline/builtins/Widgets.java index 41fb0c81b..ea9d3b2c3 100644 --- a/builtins/src/main/java/org/jline/builtins/Widgets.java +++ b/builtins/src/main/java/org/jline/builtins/Widgets.java @@ -116,7 +116,7 @@ public boolean autopairInsert() { public boolean autopairClose() { if (pairs.containsValue(reader.getLastBinding()) - && reader.getBuffer().currChar() == reader.getLastBinding().charAt(0)) { + && currChar().equals(reader.getLastBinding())) { reader.callWidget(LineReader.FORWARD_CHAR); } else { reader.callWidget(LineReader.SELF_INSERT); @@ -125,12 +125,12 @@ public boolean autopairClose() { } public boolean autopairDelete() { - if (pairs.containsKey(prevChar()) && pairs.get(prevChar()).charAt(0) == currChar().charAt(0) + if (pairs.containsKey(prevChar()) && pairs.get(prevChar()).equals(currChar()) && canDelete(prevChar())) { reader.callWidget(LineReader.DELETE_CHAR); } reader.callWidget(LineReader.BACKWARD_DELETE_CHAR); - return false; + return true; } /* @@ -190,7 +190,7 @@ private boolean canPair(String d) { } private boolean canSkip(String d) { - if (pairs.get(d).equals(d) && d.charAt(0) != ' ' && reader.getBuffer().currChar() == d.charAt(0) + if (pairs.get(d).equals(d) && d.charAt(0) != ' ' && currChar().equals(d) && balanced(d)) { return true; } diff --git a/builtins/src/test/java/org/jline/example/Example.java b/builtins/src/test/java/org/jline/example/Example.java index 45fa800e8..4ae9e3535 100644 --- a/builtins/src/test/java/org/jline/example/Example.java +++ b/builtins/src/test/java/org/jline/example/Example.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002-2017, the original author or authors. + * Copyright (c) 2002-2019, the original author or authors. * * This software is distributable under the BSD license. See the terms of the * BSD license in the documentation provided with this software. @@ -447,7 +447,7 @@ else if ("ttop".equals(pl.word())) { } else if ("autopair".equals(pl.word())) { terminal.writer().print("Autopair widgets are "); - if (autopairWidgets.toggleKeyBindings()){ + if (autopairWidgets.toggleKeyBindings()) { terminal.writer().println("bounded."); } else { terminal.writer().println("unbounded."); From 20076f29a50035d5ab80841d28f83176e213130b Mon Sep 17 00:00:00 2001 From: mattirn Date: Fri, 4 Oct 2019 08:57:25 +0200 Subject: [PATCH 04/14] nano: fixed bug in 'search and replace' --- builtins/src/main/java/org/jline/builtins/Nano.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtins/src/main/java/org/jline/builtins/Nano.java b/builtins/src/main/java/org/jline/builtins/Nano.java index b6484fdc9..a4bbc0553 100644 --- a/builtins/src/main/java/org/jline/builtins/Nano.java +++ b/builtins/src/main/java/org/jline/builtins/Nano.java @@ -2898,8 +2898,8 @@ void searchAndReplace() { found = buffer.nextSearch(); if (found) { int[] re = buffer.highlightStart(); - int col = searchBackwards ? buffer.getLine(re[0]).length() - re[1] : re[1]; - int match = re[0]*100000 + col; + int col = searchBackwards ? buffer.length(buffer.getLine(re[0])) - re[1] : re[1]; + int match = re[0]*10000 + col; if (matches.contains(match)) { found = false; break; From 4abce4520d887e6f174790b6b70abe5220775532 Mon Sep 17 00:00:00 2001 From: mattirn Date: Sat, 5 Oct 2019 11:48:03 +0200 Subject: [PATCH 05/14] AutopairWidgets: now restores defaultBindings correctly --- .../main/java/org/jline/builtins/Widgets.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/builtins/src/main/java/org/jline/builtins/Widgets.java b/builtins/src/main/java/org/jline/builtins/Widgets.java index ea9d3b2c3..ec9009901 100644 --- a/builtins/src/main/java/org/jline/builtins/Widgets.java +++ b/builtins/src/main/java/org/jline/builtins/Widgets.java @@ -50,6 +50,7 @@ public static class AutopairWidgets { private static final Map LBOUNDS; private static final Map RBOUNDS; private final Map pairs; + private final Map defaultBindings = new HashMap<>(); private final LineReaderImpl reader; private boolean autopair = false; { @@ -92,6 +93,15 @@ public AutopairWidgets(LineReader reader, boolean addCurlyBrackets) { addWidget(reader.getWidgets(), "autopair-insert", this::autopairInsert); addWidget(reader.getWidgets(), "autopair-close", this::autopairClose); addWidget(reader.getWidgets(), "autopair-delete", this::autopairDelete); + + KeyMap map = reader.getKeyMaps().get(LineReader.MAIN); + for (Map.Entry p: pairs.entrySet()) { + defaultBindings.put(p.getKey(), map.getBound(p.getKey())); + if (!p.getKey().equals(p.getValue())) { + defaultBindings.put(p.getValue(), map.getBound(p.getValue())); + } + } + defaultBindings.put(del(), map.getBound(del())); } /* @@ -167,12 +177,12 @@ private void defaultBindings() { } KeyMap map = reader.getKeyMaps().get(LineReader.MAIN); for (Map.Entry p: pairs.entrySet()) { - map.bind(new Reference(LineReader.SELF_INSERT), p.getKey()); - if (p.getKey().equals(p.getValue())) { - map.bind(new Reference(LineReader.SELF_INSERT), p.getValue()); + map.bind(defaultBindings.get(p.getKey()), p.getKey()); + if (!p.getKey().equals(p.getValue())) { + map.bind(defaultBindings.get(p.getValue()), p.getValue()); } } - map.bind(new Reference(LineReader.BACKWARD_DELETE_CHAR), del()); + map.bind(defaultBindings.get(del()), del()); autopair = false; } /* From 54dfee6fe756313f48ee865fd2732917662bb018 Mon Sep 17 00:00:00 2001 From: mattirn Date: Sat, 5 Oct 2019 20:52:05 +0200 Subject: [PATCH 06/14] nano: now manages tabs correctly also in wrapped lines --- builtins/src/main/java/org/jline/builtins/Nano.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/builtins/src/main/java/org/jline/builtins/Nano.java b/builtins/src/main/java/org/jline/builtins/Nano.java index a4bbc0553..447a7c309 100644 --- a/builtins/src/main/java/org/jline/builtins/Nano.java +++ b/builtins/src/main/java/org/jline/builtins/Nano.java @@ -404,7 +404,8 @@ void computeAllOffsets() { } } - LinkedList computeOffsets(String text) { + LinkedList computeOffsets(String line) { + String text = new AttributedStringBuilder().tabs(tabs).append(line).toString(); int width = size.getColumns() - (printLineNumbers ? 8 : 0); LinkedList offsets = new LinkedList<>(); offsets.add(0); @@ -661,7 +662,7 @@ private void cursorDown(int lines) { column = Math.min(wantedColumn, next - offsetInLine); } } - moveToChar(column); + moveToChar(offsetInLine + column); } private void cursorUp(int lines) { @@ -690,7 +691,7 @@ private void cursorUp(int lines) { } } } - moveToChar(column); + moveToChar(offsetInLine + column); } void ensureCursorVisible() { From 58d60156aa429f1a7b979966efde35676c4faf1d Mon Sep 17 00:00:00 2001 From: mattirn Date: Sun, 6 Oct 2019 14:29:17 +0200 Subject: [PATCH 07/14] AutopairWidgets: refactoring... --- .../main/java/org/jline/builtins/Widgets.java | 112 +++++++++++------- 1 file changed, 70 insertions(+), 42 deletions(-) diff --git a/builtins/src/main/java/org/jline/builtins/Widgets.java b/builtins/src/main/java/org/jline/builtins/Widgets.java index ec9009901..6effd0040 100644 --- a/builtins/src/main/java/org/jline/builtins/Widgets.java +++ b/builtins/src/main/java/org/jline/builtins/Widgets.java @@ -9,6 +9,7 @@ package org.jline.builtins; import static org.jline.keymap.KeyMap.del; +import static org.jline.keymap.KeyMap.ctrl; import java.util.ArrayList; import java.util.HashMap; @@ -23,13 +24,18 @@ import org.jline.reader.Widget; import org.jline.reader.impl.LineReaderImpl; -public class Widgets { +public abstract class Widgets { + private final LineReaderImpl reader; + + public Widgets(LineReader reader) { + this.reader = (LineReaderImpl)reader; + } - private static void addWidget(Map widgets, String name, Widget widget) { - widgets.put(name, namedWidget(name, widget)); + public void addWidget(String name, Widget widget) { + reader.getWidgets().put(name, namedWidget(name, widget)); } - private static Widget namedWidget(final String name, final Widget widget) { + private Widget namedWidget(final String name, final Widget widget) { return new Widget() { @Override public String toString() { @@ -41,8 +47,36 @@ public boolean apply() { } }; } + + public void callWidget(String name) { + reader.callWidget(name); + } + + public KeyMap getKeyMap(String name) { + return reader.getKeyMaps().get(name); + } + + public Buffer buffer() { + return reader.getBuffer(); + } + + public String prevChar() { + return String.valueOf((char)reader.getBuffer().prevChar()); + } - public static class AutopairWidgets { + public String currChar() { + return String.valueOf((char)reader.getBuffer().currChar()); + } + + public String lastBinding() { + return reader.getLastBinding(); + } + + public void putString(String string) { + reader.putString(string); + } + + public static class AutopairWidgets extends Widgets { /* * Inspired by zsh-autopair * https://github.com/hlissner/zsh-autopair @@ -51,7 +85,6 @@ public static class AutopairWidgets { private static final Map RBOUNDS; private final Map pairs; private final Map defaultBindings = new HashMap<>(); - private final LineReaderImpl reader; private boolean autopair = false; { pairs = new HashMap<>(); @@ -86,21 +119,23 @@ public AutopairWidgets(LineReader reader) { } public AutopairWidgets(LineReader reader, boolean addCurlyBrackets) { - this.reader = (LineReaderImpl)reader; + super(reader); if (addCurlyBrackets) { pairs.put("{", "}"); } - addWidget(reader.getWidgets(), "autopair-insert", this::autopairInsert); - addWidget(reader.getWidgets(), "autopair-close", this::autopairClose); - addWidget(reader.getWidgets(), "autopair-delete", this::autopairDelete); + addWidget("autopair-insert", this::autopairInsert); + addWidget("autopair-close", this::autopairClose); + addWidget("autopair-delete", this::autopairDelete); + addWidget("autopair-toggle", this::toggleKeyBindings); - KeyMap map = reader.getKeyMaps().get(LineReader.MAIN); + KeyMap map = getKeyMap(LineReader.MAIN); for (Map.Entry p: pairs.entrySet()) { defaultBindings.put(p.getKey(), map.getBound(p.getKey())); if (!p.getKey().equals(p.getValue())) { defaultBindings.put(p.getValue(), map.getBound(p.getValue())); } } + defaultBindings.put(ctrl('H'), map.getBound(ctrl('H'))); defaultBindings.put(del(), map.getBound(del())); } @@ -108,28 +143,28 @@ public AutopairWidgets(LineReader reader, boolean addCurlyBrackets) { * Widgets */ public boolean autopairInsert() { - if (pairs.containsKey(reader.getLastBinding())) { - if (canSkip(reader.getLastBinding())) { - reader.callWidget(LineReader.FORWARD_CHAR); - } else if (canPair(reader.getLastBinding())) { - reader.callWidget(LineReader.SELF_INSERT); - reader.putString(pairs.get(reader.getLastBinding())); - reader.callWidget(LineReader.BACKWARD_CHAR); + if (pairs.containsKey(lastBinding())) { + if (canSkip(lastBinding())) { + callWidget(LineReader.FORWARD_CHAR); + } else if (canPair(lastBinding())) { + callWidget(LineReader.SELF_INSERT); + putString(pairs.get(lastBinding())); + callWidget(LineReader.BACKWARD_CHAR); } else { - reader.callWidget(LineReader.SELF_INSERT); + callWidget(LineReader.SELF_INSERT); } } else { - reader.callWidget(LineReader.SELF_INSERT); + callWidget(LineReader.SELF_INSERT); } return true; } public boolean autopairClose() { - if (pairs.containsValue(reader.getLastBinding()) - && currChar().equals(reader.getLastBinding())) { - reader.callWidget(LineReader.FORWARD_CHAR); + if (pairs.containsValue(lastBinding()) + && currChar().equals(lastBinding())) { + callWidget(LineReader.FORWARD_CHAR); } else { - reader.callWidget(LineReader.SELF_INSERT); + callWidget(LineReader.SELF_INSERT); } return true; } @@ -137,16 +172,12 @@ && currChar().equals(reader.getLastBinding())) { public boolean autopairDelete() { if (pairs.containsKey(prevChar()) && pairs.get(prevChar()).equals(currChar()) && canDelete(prevChar())) { - reader.callWidget(LineReader.DELETE_CHAR); + callWidget(LineReader.DELETE_CHAR); } - reader.callWidget(LineReader.BACKWARD_DELETE_CHAR); + callWidget(LineReader.BACKWARD_DELETE_CHAR); return true; } - /* - * key bindings... - * - */ public boolean toggleKeyBindings() { if(autopair) { defaultBindings(); @@ -155,18 +186,22 @@ public boolean toggleKeyBindings() { } return autopair; } - + /* + * key bindings... + * + */ private void autopairBindings() { if (autopair) { return; } - KeyMap map = reader.getKeyMaps().get(LineReader.MAIN); + KeyMap map = getKeyMap(LineReader.MAIN); for (Map.Entry p: pairs.entrySet()) { map.bind(new Reference("autopair-insert"), p.getKey()); if (!p.getKey().equals(p.getValue())) { map.bind(new Reference("autopair-close"), p.getValue()); } } + map.bind(new Reference("autopair-delete"), ctrl('H')); map.bind(new Reference("autopair-delete"), del()); autopair = true; } @@ -175,13 +210,14 @@ private void defaultBindings() { if (!autopair) { return; } - KeyMap map = reader.getKeyMaps().get(LineReader.MAIN); + KeyMap map = getKeyMap(LineReader.MAIN); for (Map.Entry p: pairs.entrySet()) { map.bind(defaultBindings.get(p.getKey()), p.getKey()); if (!p.getKey().equals(p.getValue())) { map.bind(defaultBindings.get(p.getValue()), p.getValue()); } } + map.bind(defaultBindings.get(ctrl('H')), ctrl('H')); map.bind(defaultBindings.get(del()), del()); autopair = false; } @@ -216,7 +252,7 @@ private boolean canDelete(String d) { private boolean balanced(String d) { boolean out = false; - Buffer buf = reader.getBuffer(); + Buffer buf = buffer(); String lbuf = buf.upToCursor(); String rbuf = buf.substring(lbuf.length()); String regx1 = pairs.get(d).equals(d)? d : "\\"+d; @@ -246,14 +282,6 @@ private boolean balanced(String d) { return out; } - private String prevChar() { - return String.valueOf((char)reader.getBuffer().prevChar()); - } - - private String currChar() { - return String.valueOf((char)reader.getBuffer().currChar()); - } - private boolean boundary(String lb, String rb) { if((lb.length() > 0 && prevChar().matches(lb)) || From 4c48c039c0559638cfa2ac8ba12d3aa237d8870e Mon Sep 17 00:00:00 2001 From: mattirn Date: Mon, 7 Oct 2019 14:17:22 +0200 Subject: [PATCH 08/14] Added custom autosuggestion widgets --- .../main/java/org/jline/builtins/Widgets.java | 162 ++++++++++++++++-- .../test/java/org/jline/example/Example.java | 48 ++++-- .../java/org/jline/reader/LineReader.java | 6 + .../org/jline/reader/impl/LineReaderImpl.java | 60 ++++++- 4 files changed, 234 insertions(+), 42 deletions(-) diff --git a/builtins/src/main/java/org/jline/builtins/Widgets.java b/builtins/src/main/java/org/jline/builtins/Widgets.java index 6effd0040..e2a864ca2 100644 --- a/builtins/src/main/java/org/jline/builtins/Widgets.java +++ b/builtins/src/main/java/org/jline/builtins/Widgets.java @@ -13,8 +13,11 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.jline.keymap.KeyMap; import org.jline.reader.Binding; @@ -23,12 +26,13 @@ import org.jline.reader.Reference; import org.jline.reader.Widget; import org.jline.reader.impl.LineReaderImpl; +import org.jline.reader.impl.BufferImpl; public abstract class Widgets { - private final LineReaderImpl reader; - + private final LineReader reader; + public Widgets(LineReader reader) { - this.reader = (LineReaderImpl)reader; + this.reader = reader; } public void addWidget(String name, Widget widget) { @@ -47,7 +51,7 @@ public boolean apply() { } }; } - + public void callWidget(String name) { reader.callWidget(name); } @@ -55,11 +59,15 @@ public void callWidget(String name) { public KeyMap getKeyMap(String name) { return reader.getKeyMaps().get(name); } - + public Buffer buffer() { return reader.getBuffer(); } - + + public void replaceBuffer(Buffer buffer) { + reader.getBuffer().copyFrom(buffer); + } + public String prevChar() { return String.valueOf((char)reader.getBuffer().prevChar()); } @@ -71,9 +79,17 @@ public String currChar() { public String lastBinding() { return reader.getLastBinding(); } - + public void putString(String string) { - reader.putString(string); + reader.getBuffer().write(string); + } + + public String tailTip() { + return reader.getTailTip(); + } + + public void enableTailTip(boolean enable) { + reader.enableTailTip(enable); } public static class AutopairWidgets extends Widgets { @@ -130,13 +146,13 @@ public AutopairWidgets(LineReader reader, boolean addCurlyBrackets) { KeyMap map = getKeyMap(LineReader.MAIN); for (Map.Entry p: pairs.entrySet()) { - defaultBindings.put(p.getKey(), map.getBound(p.getKey())); + defaultBindings.put(p.getKey(), map.getBound(p.getKey())); if (!p.getKey().equals(p.getValue())) { - defaultBindings.put(p.getValue(), map.getBound(p.getValue())); + defaultBindings.put(p.getValue(), map.getBound(p.getValue())); } } defaultBindings.put(ctrl('H'), map.getBound(ctrl('H'))); - defaultBindings.put(del(), map.getBound(del())); + defaultBindings.put(del(), map.getBound(del())); } /* @@ -179,7 +195,7 @@ && canDelete(prevChar())) { } public boolean toggleKeyBindings() { - if(autopair) { + if (autopair) { defaultBindings(); } else { autopairBindings(); @@ -191,9 +207,6 @@ public boolean toggleKeyBindings() { * */ private void autopairBindings() { - if (autopair) { - return; - } KeyMap map = getKeyMap(LineReader.MAIN); for (Map.Entry p: pairs.entrySet()) { map.bind(new Reference("autopair-insert"), p.getKey()); @@ -207,9 +220,6 @@ private void autopairBindings() { } private void defaultBindings() { - if (!autopair) { - return; - } KeyMap map = getKeyMap(LineReader.MAIN); for (Map.Entry p: pairs.entrySet()) { map.bind(defaultBindings.get(p.getKey()), p.getKey()); @@ -283,7 +293,7 @@ private boolean balanced(String d) { } private boolean boundary(String lb, String rb) { - if((lb.length() > 0 && prevChar().matches(lb)) + if ((lb.length() > 0 && prevChar().matches(lb)) || (rb.length() > 0 && currChar().matches(rb))) { return true; @@ -312,4 +322,118 @@ private boolean nexToBoundary(String d) { return false; } } + + public static class AutosuggestionWidgets extends Widgets { + private final Map> defaultBindings = new HashMap<>(); + private boolean autosuggestion = false; + + public AutosuggestionWidgets(LineReader reader) { + super(reader); + addWidget("autosuggest-forward-char", this::autosuggestForwardChar); + addWidget("autosuggest-end-of-line", this::autosuggestEndOfLine); + addWidget("autosuggest-forward-word", this::partialAccept); + addWidget("autosuggest-toggle", this::toggleKeyBindings); + KeyMap map = getKeyMap(LineReader.MAIN); + for (Map.Entry bound : map.getBoundKeys().entrySet()) { + if (bound.getValue() instanceof Reference) { + Reference w = (Reference)bound.getValue(); + if (w.name().equals(LineReader.FORWARD_CHAR)){ + addKeySequence(w, bound.getKey()); + } else if (w.name().equals(LineReader.END_OF_LINE)){ + addKeySequence(w, bound.getKey()); + } else if (w.name().equals(LineReader.FORWARD_WORD)){ + addKeySequence(w, bound.getKey()); + } + } + } + } + + private void addKeySequence(Reference widget, String keySequence) { + if (!defaultBindings.containsKey(widget)) { + defaultBindings.put(widget, new HashSet()); + } + defaultBindings.get(widget).add(keySequence); + } + /* + * Widgets + */ + public boolean partialAccept() { + Buffer buffer = buffer(); + if (buffer.cursor() == buffer.length()) { + int curPos = buffer.cursor(); + buffer.write(tailTip()); + buffer.cursor(curPos); + replaceBuffer(buffer); + callWidget(LineReader.FORWARD_WORD); + Buffer newBuf = new BufferImpl(); + newBuf.write(buffer().substring(0, buffer().cursor())); + replaceBuffer(newBuf); + } else { + callWidget(LineReader.FORWARD_WORD); + } + return true; + } + + public boolean autosuggestForwardChar() { + return accept(LineReader.FORWARD_CHAR); + } + + public boolean autosuggestEndOfLine() { + return accept(LineReader.END_OF_LINE); + } + + public boolean toggleKeyBindings() { + if (autosuggestion) { + defaultBindings(); + } else { + autosuggestionBindings(); + } + enableTailTip(autosuggestion); + return autosuggestion; + } + + + private boolean accept(String widget) { + Buffer buffer = buffer(); + if (buffer.cursor() == buffer.length()) { + putString(tailTip()); + } else { + callWidget(widget); + } + return true; + } + /* + * key bindings... + * + */ + private void autosuggestionBindings() { + KeyMap map = getKeyMap(LineReader.MAIN); + for (Map.Entry> entry : defaultBindings.entrySet()) { + if (entry.getKey().name().equals(LineReader.FORWARD_CHAR)) { + for (String s: entry.getValue()) { + map.bind(new Reference("autosuggest-forward-char"), s); + } + } else if (entry.getKey().name().equals(LineReader.END_OF_LINE)) { + for (String s: entry.getValue()) { + map.bind(new Reference("autosuggest-end-of-line"), s); + } + } else if (entry.getKey().name().equals(LineReader.FORWARD_WORD)) { + for (String s: entry.getValue()) { + map.bind(new Reference("autosuggest-forward-word"), s); + } + } + } + autosuggestion = true; + } + + private void defaultBindings() { + KeyMap map = getKeyMap(LineReader.MAIN); + for (Map.Entry> entry : defaultBindings.entrySet()) { + for (String s: entry.getValue()) { + map.bind(entry.getKey(), s); + } + } + autosuggestion = false; + } + } } diff --git a/builtins/src/test/java/org/jline/example/Example.java b/builtins/src/test/java/org/jline/example/Example.java index 4ae9e3535..a847c4252 100644 --- a/builtins/src/test/java/org/jline/example/Example.java +++ b/builtins/src/test/java/org/jline/example/Example.java @@ -25,6 +25,7 @@ import org.jline.builtins.Options.HelpException; import org.jline.builtins.TTop; import org.jline.builtins.Widgets.AutopairWidgets; +import org.jline.builtins.Widgets.AutosuggestionWidgets; import org.jline.keymap.KeyMap; import org.jline.reader.*; import org.jline.reader.impl.DefaultParser; @@ -85,25 +86,26 @@ public static void help() { String[] help = { "List of available commands:" , " Builtin:" - , " complete UNAVAILABLE" - , " history list history of commands" - , " keymap manipulate keymaps" - , " less file pager" - , " nano nano editor" - , " setopt set options" - , " tmux UNAVAILABLE" - , " ttop display and update sorted information about threads" - , " unsetopt unset options" - , " widget UNAVAILABLE" - , " autopair toggle brackets/quotes autopair key bindings" + , " complete UNAVAILABLE" + , " history list history of commands" + , " keymap manipulate keymaps" + , " less file pager" + , " nano nano editor" + , " setopt set options" + , " tmux UNAVAILABLE" + , " ttop display and update sorted information about threads" + , " unsetopt unset options" + , " widget UNAVAILABLE" + , " autopair toggle brackets/quotes autopair key bindings" + , " autosuggestion toggle autosuggestion key bindings" , " Example:" - , " cls clear screen" - , " help list available commands" - , " exit exit from example app" - , " set set lineReader variable" - , " sleep sleep 3 seconds" - , " testkey display key events" - , " tput set terminal capability" + , " cls clear screen" + , " help list available commands" + , " exit exit from example app" + , " set set lineReader variable" + , " sleep sleep 3 seconds" + , " testkey display key events" + , " tput set terminal capability" , " Additional help:" , " --help"}; for (String u: help) { @@ -302,7 +304,7 @@ public void complete(LineReader reader, ParsedLine line, List candida .variable(LineReader.SECONDARY_PROMPT_PATTERN, "%M%P > ") .build(); AutopairWidgets autopairWidgets = new AutopairWidgets(reader); - + AutosuggestionWidgets autosuggestionWidgets = new AutosuggestionWidgets(reader); if (timer) { Executors.newScheduledThreadPool(1) .scheduleAtFixedRate(() -> { @@ -453,6 +455,14 @@ else if ("autopair".equals(pl.word())) { terminal.writer().println("unbounded."); } } + else if ("autosuggestion".equals(pl.word())) { + terminal.writer().print("Autosuggestion widgets are "); + if (autosuggestionWidgets.toggleKeyBindings()) { + terminal.writer().println("bounded."); + } else { + terminal.writer().println("unbounded."); + } + } else if ("help".equals(pl.word()) || "?".equals(pl.word())) { help(); } diff --git a/reader/src/main/java/org/jline/reader/LineReader.java b/reader/src/main/java/org/jline/reader/LineReader.java index 0bc42da8c..d4da12e46 100644 --- a/reader/src/main/java/org/jline/reader/LineReader.java +++ b/reader/src/main/java/org/jline/reader/LineReader.java @@ -662,4 +662,10 @@ enum RegionType { void editAndAddInBuffer(File file) throws Exception; + public String getLastBinding(); + + public String getTailTip(); + + public void enableTailTip(boolean enable); + } diff --git a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java index ed47e2a58..5f698a46e 100644 --- a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java +++ b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java @@ -116,7 +116,7 @@ protected enum State { */ DONE, /** - * readLine should exit return empty String + * readLine should exit and return empty String */ IGNORE, /** @@ -170,6 +170,8 @@ protected enum BellType { protected final Map options = new HashMap<>(); protected final Buffer buf = new BufferImpl(); + protected String tailTip = ""; + protected boolean doTailTip; protected final Size size = new Size(); @@ -327,6 +329,16 @@ public Buffer getBuffer() { return buf; } + @Override + public void enableTailTip(boolean enable) { + this.doTailTip = enable; + } + + @Override + public String getTailTip(){ + return tailTip; + } + @Override public void runMacro(String macro) { bindingReader.runMacro(macro); @@ -487,7 +499,7 @@ public String readLine(String prompt, String rightPrompt, MaskingCallback maskin // buffer may be null if (!commandsBuffer.isEmpty()) { String cmd = commandsBuffer.remove(0); - boolean done = false; + boolean done = false; do { try { parser.parse(cmd, cmd.length() + 1, ParseContext.ACCEPT_LINE); @@ -928,10 +940,12 @@ public ParsedLine getParsedLine() { return parsedLine; } + @Override public String getLastBinding() { return bindingReader.getLastBinding(); } + @Override public String getSearchTerm() { return searchTerm != null ? searchTerm.toString() : null; } @@ -1042,7 +1056,7 @@ public void editAndAddInBuffer(File file) throws Exception { } br.close(); } - + // // Widget implementation // @@ -3874,6 +3888,38 @@ private void concat(List lines, AttributedStringBuilder sb) { sb.append(lines.get(lines.size() - 1)); } + private String matchPreviousCommand(String buffer) { + if (buffer.length() == 0) { + return ""; + } + History history = getHistory(); + StringBuilder sb = new StringBuilder(); + char prev = '0'; + for (char c: buffer.toCharArray()) { + if ((c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^') && prev != '\\' ) { + sb.append('\\'); + } + sb.append(c); + prev = c; + } + Pattern pattern = Pattern.compile(sb.toString() + ".*", Pattern.DOTALL); + Iterator iter = history.reverseIterator(history.last()); + String suggestion = ""; + int tot = 0; + while (iter.hasNext()) { + History.Entry entry = iter.next(); + Matcher matcher = pattern.matcher(entry.line()); + if (matcher.matches()) { + suggestion = entry.line().substring(buffer.length()); + break; + } else if (tot > 200) { + break; + } + tot++; + } + return suggestion; + } + /** * Compute the full string to be displayed with the left, right and secondary prompts * @param secondaryPrompts a list to store the secondary prompts @@ -3886,6 +3932,12 @@ public AttributedString getDisplayedBufferWithPrompts(List sec AttributedStringBuilder full = new AttributedStringBuilder().tabs(TAB_WIDTH); full.append(prompt); full.append(tNewBuf); + if (doTailTip) { + AttributedStringBuilder sb = new AttributedStringBuilder(); + tailTip = matchPreviousCommand(buf.toString()); + sb.styled(AttributedStyle::faint, tailTip); + full.append(sb.toAttributedString()); + } if (post != null) { full.append("\n"); full.append(post.get()); @@ -3896,7 +3948,7 @@ public AttributedString getDisplayedBufferWithPrompts(List sec private AttributedString getHighlightedBuffer(String buffer) { if (maskingCallback != null) { buffer = maskingCallback.display(buffer); - } + } if (highlighter != null && !isSet(Option.DISABLE_HIGHLIGHTER)) { return highlighter.highlight(this, buffer); } From 969fa1a434ba10b3ab7a6e701e97713c09619b3d Mon Sep 17 00:00:00 2001 From: mattirn Date: Tue, 8 Oct 2019 17:40:03 +0200 Subject: [PATCH 09/14] Autosuggestion using command completer data, #254 --- .../main/java/org/jline/builtins/Widgets.java | 44 ++++++------ .../test/java/org/jline/example/Example.java | 26 +++++-- .../java/org/jline/reader/LineReader.java | 9 ++- .../org/jline/reader/impl/LineReaderImpl.java | 67 +++++++++++++++---- 4 files changed, 107 insertions(+), 39 deletions(-) diff --git a/builtins/src/main/java/org/jline/builtins/Widgets.java b/builtins/src/main/java/org/jline/builtins/Widgets.java index e2a864ca2..5bcb1a6ae 100644 --- a/builtins/src/main/java/org/jline/builtins/Widgets.java +++ b/builtins/src/main/java/org/jline/builtins/Widgets.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -23,9 +22,9 @@ import org.jline.reader.Binding; import org.jline.reader.Buffer; import org.jline.reader.LineReader; +import org.jline.reader.LineReader.SuggestionType; import org.jline.reader.Reference; import org.jline.reader.Widget; -import org.jline.reader.impl.LineReaderImpl; import org.jline.reader.impl.BufferImpl; public abstract class Widgets { @@ -89,7 +88,7 @@ public String tailTip() { } public void enableTailTip(boolean enable) { - reader.enableTailTip(enable); + reader.setAutosuggestion(enable ? SuggestionType.HISTORY : SuggestionType.NONE); } public static class AutopairWidgets extends Widgets { @@ -139,9 +138,9 @@ public AutopairWidgets(LineReader reader, boolean addCurlyBrackets) { if (addCurlyBrackets) { pairs.put("{", "}"); } - addWidget("autopair-insert", this::autopairInsert); - addWidget("autopair-close", this::autopairClose); - addWidget("autopair-delete", this::autopairDelete); + addWidget("_autopair-insert", this::autopairInsert); + addWidget("_autopair-close", this::autopairClose); + addWidget("_autopair-delete", this::autopairDelete); addWidget("autopair-toggle", this::toggleKeyBindings); KeyMap map = getKeyMap(LineReader.MAIN); @@ -209,13 +208,13 @@ public boolean toggleKeyBindings() { private void autopairBindings() { KeyMap map = getKeyMap(LineReader.MAIN); for (Map.Entry p: pairs.entrySet()) { - map.bind(new Reference("autopair-insert"), p.getKey()); + map.bind(new Reference("_autopair-insert"), p.getKey()); if (!p.getKey().equals(p.getValue())) { - map.bind(new Reference("autopair-close"), p.getValue()); + map.bind(new Reference("_autopair-close"), p.getValue()); } } - map.bind(new Reference("autopair-delete"), ctrl('H')); - map.bind(new Reference("autopair-delete"), del()); + map.bind(new Reference("_autopair-delete"), ctrl('H')); + map.bind(new Reference("_autopair-delete"), del()); autopair = true; } @@ -329,9 +328,9 @@ public static class AutosuggestionWidgets extends Widgets { public AutosuggestionWidgets(LineReader reader) { super(reader); - addWidget("autosuggest-forward-char", this::autosuggestForwardChar); - addWidget("autosuggest-end-of-line", this::autosuggestEndOfLine); - addWidget("autosuggest-forward-word", this::partialAccept); + addWidget("_autosuggest-forward-char", this::autosuggestForwardChar); + addWidget("_autosuggest-end-of-line", this::autosuggestEndOfLine); + addWidget("_autosuggest-forward-word", this::partialAccept); addWidget("autosuggest-toggle", this::toggleKeyBindings); KeyMap map = getKeyMap(LineReader.MAIN); for (Map.Entry bound : map.getBoundKeys().entrySet()) { @@ -388,7 +387,6 @@ public boolean toggleKeyBindings() { } else { autosuggestionBindings(); } - enableTailTip(autosuggestion); return autosuggestion; } @@ -406,27 +404,34 @@ private boolean accept(String widget) { * key bindings... * */ - private void autosuggestionBindings() { + public void autosuggestionBindings() { + if (autosuggestion) { + return; + } KeyMap map = getKeyMap(LineReader.MAIN); for (Map.Entry> entry : defaultBindings.entrySet()) { if (entry.getKey().name().equals(LineReader.FORWARD_CHAR)) { for (String s: entry.getValue()) { - map.bind(new Reference("autosuggest-forward-char"), s); + map.bind(new Reference("_autosuggest-forward-char"), s); } } else if (entry.getKey().name().equals(LineReader.END_OF_LINE)) { for (String s: entry.getValue()) { - map.bind(new Reference("autosuggest-end-of-line"), s); + map.bind(new Reference("_autosuggest-end-of-line"), s); } } else if (entry.getKey().name().equals(LineReader.FORWARD_WORD)) { for (String s: entry.getValue()) { - map.bind(new Reference("autosuggest-forward-word"), s); + map.bind(new Reference("_autosuggest-forward-word"), s); } } } autosuggestion = true; + enableTailTip(autosuggestion); } - private void defaultBindings() { + public void defaultBindings() { + if (!autosuggestion) { + return; + } KeyMap map = getKeyMap(LineReader.MAIN); for (Map.Entry> entry : defaultBindings.entrySet()) { for (String s: entry.getValue()) { @@ -434,6 +439,7 @@ private void defaultBindings() { } } autosuggestion = false; + enableTailTip(autosuggestion); } } } diff --git a/builtins/src/test/java/org/jline/example/Example.java b/builtins/src/test/java/org/jline/example/Example.java index a847c4252..7074fe6ea 100644 --- a/builtins/src/test/java/org/jline/example/Example.java +++ b/builtins/src/test/java/org/jline/example/Example.java @@ -28,6 +28,7 @@ import org.jline.builtins.Widgets.AutosuggestionWidgets; import org.jline.keymap.KeyMap; import org.jline.reader.*; +import org.jline.reader.LineReader.SuggestionType; import org.jline.reader.impl.DefaultParser; import org.jline.reader.impl.DefaultParser.Bracket; import org.jline.reader.impl.LineReaderImpl; @@ -97,7 +98,7 @@ public static void help() { , " unsetopt unset options" , " widget UNAVAILABLE" , " autopair toggle brackets/quotes autopair key bindings" - , " autosuggestion toggle autosuggestion key bindings" + , " autosuggestion history, completer or none" , " Example:" , " cls clear screen" , " help list available commands" @@ -456,11 +457,26 @@ else if ("autopair".equals(pl.word())) { } } else if ("autosuggestion".equals(pl.word())) { - terminal.writer().print("Autosuggestion widgets are "); - if (autosuggestionWidgets.toggleKeyBindings()) { - terminal.writer().println("bounded."); + if (pl.words().size() == 2) { + String type = pl.words().get(1); + if (type.toLowerCase().startsWith("his")) { + autosuggestionWidgets.autosuggestionBindings(); + } else if (type.toLowerCase().startsWith("com")) { + if (reader.getAutosuggestion() == SuggestionType.HISTORY) { + autosuggestionWidgets.defaultBindings(); + } + reader.setAutosuggestion(SuggestionType.COMPLETER); + } else if (type.toLowerCase().startsWith("non")) { + if (reader.getAutosuggestion() == SuggestionType.HISTORY) { + autosuggestionWidgets.defaultBindings(); + } else { + reader.setAutosuggestion(SuggestionType.NONE); + } + } else { + terminal.writer().println("Usage: autosuggestion history|completer|none"); + } } else { - terminal.writer().println("unbounded."); + terminal.writer().println("Autosuggestion: " + reader.getAutosuggestion()); } } else if ("help".equals(pl.word()) || "?".equals(pl.word())) { diff --git a/reader/src/main/java/org/jline/reader/LineReader.java b/reader/src/main/java/org/jline/reader/LineReader.java index d4da12e46..21c8cffd5 100644 --- a/reader/src/main/java/org/jline/reader/LineReader.java +++ b/reader/src/main/java/org/jline/reader/LineReader.java @@ -442,6 +442,12 @@ enum RegionType { PASTE } + enum SuggestionType { + NONE, + HISTORY, + COMPLETER + } + /** * Read the next line and return the contents of the buffer. * @@ -666,6 +672,7 @@ enum RegionType { public String getTailTip(); - public void enableTailTip(boolean enable); + public void setAutosuggestion(SuggestionType type); + public SuggestionType getAutosuggestion(); } diff --git a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java index 5f698a46e..f243f0b85 100644 --- a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java +++ b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java @@ -171,7 +171,7 @@ protected enum BellType { protected final Buffer buf = new BufferImpl(); protected String tailTip = ""; - protected boolean doTailTip; + protected SuggestionType autosuggestion = SuggestionType.NONE; protected final Size size = new Size(); @@ -330,8 +330,13 @@ public Buffer getBuffer() { } @Override - public void enableTailTip(boolean enable) { - this.doTailTip = enable; + public void setAutosuggestion(SuggestionType type) { + this.autosuggestion = type; + } + + @Override + public SuggestionType getAutosuggestion() { + return autosuggestion; } @Override @@ -3932,11 +3937,22 @@ public AttributedString getDisplayedBufferWithPrompts(List sec AttributedStringBuilder full = new AttributedStringBuilder().tabs(TAB_WIDTH); full.append(prompt); full.append(tNewBuf); - if (doTailTip) { + String lastBinding = getLastBinding() != null ? getLastBinding() : ""; + if (autosuggestion == SuggestionType.HISTORY) { AttributedStringBuilder sb = new AttributedStringBuilder(); tailTip = matchPreviousCommand(buf.toString()); sb.styled(AttributedStyle::faint, tailTip); full.append(sb.toAttributedString()); + } else if (autosuggestion == SuggestionType.COMPLETER) { + if (buf.length() > 0 && buf.length() == buf.cursor() + && (!lastBinding.equals("\t") || buf.prevChar() == ' ')) { + if (buf.prevChar() == ' ') { + doEmptyList(); + } + listChoices(true); + } else if (!lastBinding.equals("\t")){ + doEmptyList(); + } } if (post != null) { full.append("\n"); @@ -4237,7 +4253,11 @@ protected boolean completePrefix() { } protected boolean listChoices() { - return doComplete(CompletionType.List, isSet(Option.MENU_COMPLETE), false); + return listChoices(false); + } + + private boolean listChoices(boolean forSuggestion) { + return doComplete(CompletionType.List, isSet(Option.MENU_COMPLETE), false, forSuggestion); } protected boolean deleteCharOrList() { @@ -4249,6 +4269,10 @@ protected boolean deleteCharOrList() { } protected boolean doComplete(CompletionType lst, boolean useMenu, boolean prefix) { + return doComplete(lst, useMenu, prefix, false); + } + + protected boolean doComplete(CompletionType lst, boolean useMenu, boolean prefix, boolean forSuggestion) { // If completion is disabled, just bail out if (getBoolean(DISABLE_COMPLETION, false)) { return true; @@ -4375,7 +4399,7 @@ protected boolean doComplete(CompletionType lst, boolean useMenu, boolean prefix List possible = matching.entrySet().stream() .flatMap(e -> e.getValue().stream()) .collect(Collectors.toList()); - doList(possible, line.word(), false, line::escape); + doList(possible, line.word(), false, line::escape, forSuggestion); return !possible.isEmpty(); } @@ -4844,7 +4868,18 @@ && getLastBinding().charAt(0) != ' ' return false; } - protected boolean doList(List possible, String completed, boolean runLoop, BiFunction escaper) { + protected boolean doEmptyList() { + return doList(new ArrayList(), "", false, null, false); + } + + protected boolean doList(List possible + , String completed, boolean runLoop, BiFunction escaper) { + return doList(possible, completed, runLoop, escaper, false); + } + + protected boolean doList(List possible + , String completed + , boolean runLoop, BiFunction escaper, boolean forSuggestion) { // If we list only and if there's a big // number of items, we should ask the user // for confirmation, display the list @@ -4857,13 +4892,17 @@ protected boolean doList(List possible, String completed, boolean run int listMax = getInt(LIST_MAX, DEFAULT_LIST_MAX); if (listMax > 0 && possible.size() >= listMax || lines >= size.getRows() - promptLines) { - // prompt - post = () -> new AttributedString(getAppName() + ": do you wish to see all " + possible.size() - + " possibilities (" + lines + " lines)?"); - redisplay(true); - int c = readCharacter(); - if (c != 'y' && c != 'Y' && c != '\t') { - post = null; + if (!forSuggestion) { + // prompt + post = () -> new AttributedString(getAppName() + ": do you wish to see all " + possible.size() + + " possibilities (" + lines + " lines)?"); + redisplay(true); + int c = readCharacter(); + if (c != 'y' && c != 'Y' && c != '\t') { + post = null; + return false; + } + } else { return false; } } From 55567b0b6a3d0e661ef18da4fa8ff046b02a1d24 Mon Sep 17 00:00:00 2001 From: mattirn Date: Wed, 9 Oct 2019 13:18:52 +0200 Subject: [PATCH 10/14] Added TailTipWidgets, fixes #394 --- .../main/java/org/jline/builtins/Widgets.java | 180 +++++++++++++++++- .../test/java/org/jline/example/Example.java | 25 ++- .../java/org/jline/reader/LineReader.java | 13 +- .../org/jline/reader/impl/LineReaderImpl.java | 29 ++- 4 files changed, 225 insertions(+), 22 deletions(-) diff --git a/builtins/src/main/java/org/jline/builtins/Widgets.java b/builtins/src/main/java/org/jline/builtins/Widgets.java index 5bcb1a6ae..86f18b5fc 100644 --- a/builtins/src/main/java/org/jline/builtins/Widgets.java +++ b/builtins/src/main/java/org/jline/builtins/Widgets.java @@ -67,6 +67,10 @@ public void replaceBuffer(Buffer buffer) { reader.getBuffer().copyFrom(buffer); } + public List args(String line) { + return reader.getParser().parse(line, 0).words(); + } + public String prevChar() { return String.valueOf((char)reader.getBuffer().prevChar()); } @@ -87,8 +91,16 @@ public String tailTip() { return reader.getTailTip(); } - public void enableTailTip(boolean enable) { - reader.setAutosuggestion(enable ? SuggestionType.HISTORY : SuggestionType.NONE); + public void setTailTip(String tailTip) { + reader.setTailTip(tailTip); + } + + public void clearTailTip() { + reader.setTailTip(""); + } + + public void setSuggestionType(SuggestionType type) { + reader.setAutosuggestion(type); } public static class AutopairWidgets extends Widgets { @@ -425,7 +437,7 @@ public void autosuggestionBindings() { } } autosuggestion = true; - enableTailTip(autosuggestion); + setSuggestionType(SuggestionType.HISTORY); } public void defaultBindings() { @@ -439,7 +451,167 @@ public void defaultBindings() { } } autosuggestion = false; - enableTailTip(autosuggestion); + setSuggestionType(SuggestionType.NONE); } } + + public static class TailTipWidgets extends Widgets { + private final Map> defaultBindings = new HashMap<>(); + private boolean autosuggestion = false; + private Map> tailTips = new HashMap<>(); + private boolean withCompleter; + + public TailTipWidgets(LineReader reader, Map> tailTips) { + this(reader, tailTips, true); + } + + public TailTipWidgets(LineReader reader, Map> tailTips, boolean withCompleter) { + super(reader); + this.tailTips.putAll(tailTips); + this.withCompleter = withCompleter; + addWidget("_tailtip-accept-line", this::tailtipAcceptLine); + addWidget("_tailtip-insert", this::tailtipInsert); + addWidget("_tailtip-backward-delete-char", this::tailtipBackwardDelete); + addWidget("_tailtip-delete-char", this::tailtipDelete); + addWidget("tailtip-toggle", this::toggleKeyBindings); + KeyMap map = getKeyMap(LineReader.MAIN); + for (Map.Entry bound : map.getBoundKeys().entrySet()) { + if (bound.getValue() instanceof Reference) { + Reference w = (Reference)bound.getValue(); + if (w.name().equals(LineReader.ACCEPT_LINE)){ + addKeySequence(w, bound.getKey()); + } else if (w.name().equals(LineReader.BACKWARD_DELETE_CHAR)){ + addKeySequence(w, bound.getKey()); + } else if (w.name().equals(LineReader.DELETE_CHAR)){ + addKeySequence(w, bound.getKey()); + } + } + } + } + + private void addKeySequence(Reference widget, String keySequence) { + if (!defaultBindings.containsKey(widget)) { + defaultBindings.put(widget, new HashSet()); + } + defaultBindings.get(widget).add(keySequence); + } + + /* + * widgets + */ + public boolean tailtipAcceptLine() { + if (withCompleter){ + setSuggestionType(SuggestionType.COMPLETER); + } + return clearTailTip(LineReader.ACCEPT_LINE); + } + + public boolean tailtipBackwardDelete() { + return clearTailTip(LineReader.BACKWARD_DELETE_CHAR); + } + + private boolean clearTailTip(String widget) { + clearTailTip(); + callWidget(widget); + return true; + } + + public boolean tailtipDelete() { + clearTailTip(); + return doTailTip(LineReader.DELETE_CHAR); + } + + public boolean tailtipInsert() { + return doTailTip(LineReader.SELF_INSERT); + } + + private boolean doTailTip(String widget) { + Buffer buffer = buffer(); + if (buffer.length() == buffer.cursor()) { + List bp = args(buffer.toString()); + if (bp.size() > 0 && tailTips.containsKey(bp.get(0))) { + setSuggestionType(SuggestionType.TAIL_TIP); + List params = tailTips.get(bp.get(0)); + if (bp.size() - 1 < params.size()) { + StringBuilder tip = new StringBuilder(); + boolean first = true; + for (int i = bp.size() - 1; i < params.size(); i++) { + if (!first) { + tip.append(" "); + } + tip.append(params.get(i)); + first = false; + } + setTailTip(tip.toString()); + } else if (params.get(params.size() - 1).charAt(0) == '[') { + setTailTip(params.get(params.size() - 1)); + } + } else if (withCompleter){ + setSuggestionType(SuggestionType.COMPLETER); + } + } + callWidget(widget); + return true; + } + + public boolean toggleKeyBindings() { + if (autosuggestion) { + defaultBindings(); + } else { + autosuggestionBindings(); + } + return autosuggestion; + } + + /* + * key bindings... + * + */ + public void autosuggestionBindings() { + if (autosuggestion) { + return; + } + KeyMap map = getKeyMap(LineReader.MAIN); + for (Map.Entry> entry : defaultBindings.entrySet()) { + if (entry.getKey().name().equals(LineReader.ACCEPT_LINE)) { + for (String s: entry.getValue()) { + map.bind(new Reference("_tailtip-accept-line"), s); + } + } + if (entry.getKey().name().equals(LineReader.BACKWARD_DELETE_CHAR)) { + for (String s: entry.getValue()) { + map.bind(new Reference("_tailtip-backward-delete-char"), s); + } + } + if (entry.getKey().name().equals(LineReader.DELETE_CHAR)) { + for (String s: entry.getValue()) { + map.bind(new Reference("_tailtip-delete-char"), s); + } + } + } + map.bind(new Reference("_tailtip-insert"), " "); + if (withCompleter) { + setSuggestionType(SuggestionType.COMPLETER); + } else { + setSuggestionType(SuggestionType.TAIL_TIP); + } + autosuggestion = true; + } + + public void defaultBindings() { + if (!autosuggestion) { + return; + } + KeyMap map = getKeyMap(LineReader.MAIN); + for (Map.Entry> entry : defaultBindings.entrySet()) { + for (String s: entry.getValue()) { + map.bind(entry.getKey(), s); + } + } + map.bind(new Reference(LineReader.SELF_INSERT), " "); + setSuggestionType(SuggestionType.NONE); + autosuggestion = false; + } + } + } diff --git a/builtins/src/test/java/org/jline/example/Example.java b/builtins/src/test/java/org/jline/example/Example.java index 7074fe6ea..03fc2bb58 100644 --- a/builtins/src/test/java/org/jline/example/Example.java +++ b/builtins/src/test/java/org/jline/example/Example.java @@ -26,6 +26,7 @@ import org.jline.builtins.TTop; import org.jline.builtins.Widgets.AutopairWidgets; import org.jline.builtins.Widgets.AutosuggestionWidgets; +import org.jline.builtins.Widgets.TailTipWidgets; import org.jline.keymap.KeyMap; import org.jline.reader.*; import org.jline.reader.LineReader.SuggestionType; @@ -98,7 +99,7 @@ public static void help() { , " unsetopt unset options" , " widget UNAVAILABLE" , " autopair toggle brackets/quotes autopair key bindings" - , " autosuggestion history, completer or none" + , " autosuggestion history, completer, tailtip or none" , " Example:" , " cls clear screen" , " help list available commands" @@ -202,7 +203,7 @@ public static void main(String[] args) throws IOException { break; case "argument": completer = new ArgumentCompleter( - new StringsCompleter("foo11", "foo12", "foo13"), + new StringsCompleter("foo11", "foo12", "foo13", "tail"), new StringsCompleter("foo21", "foo22", "foo23"), new Completer() { @Override @@ -306,6 +307,9 @@ public void complete(LineReader reader, ParsedLine line, List candida .build(); AutopairWidgets autopairWidgets = new AutopairWidgets(reader); AutosuggestionWidgets autosuggestionWidgets = new AutosuggestionWidgets(reader); + Map> tailTips = new HashMap<>(); + tailTips.put("tail", Arrays.asList("param1", "param2", "[paramN...]")); + TailTipWidgets tailtipWidgets = new TailTipWidgets(reader, tailTips); if (timer) { Executors.newScheduledThreadPool(1) .scheduleAtFixedRate(() -> { @@ -460,18 +464,19 @@ else if ("autosuggestion".equals(pl.word())) { if (pl.words().size() == 2) { String type = pl.words().get(1); if (type.toLowerCase().startsWith("his")) { + tailtipWidgets.defaultBindings(); autosuggestionWidgets.autosuggestionBindings(); + } else if (type.toLowerCase().startsWith("tai")) { + autosuggestionWidgets.defaultBindings(); + tailtipWidgets.autosuggestionBindings(); } else if (type.toLowerCase().startsWith("com")) { - if (reader.getAutosuggestion() == SuggestionType.HISTORY) { - autosuggestionWidgets.defaultBindings(); - } + autosuggestionWidgets.defaultBindings(); + tailtipWidgets.defaultBindings(); reader.setAutosuggestion(SuggestionType.COMPLETER); } else if (type.toLowerCase().startsWith("non")) { - if (reader.getAutosuggestion() == SuggestionType.HISTORY) { - autosuggestionWidgets.defaultBindings(); - } else { - reader.setAutosuggestion(SuggestionType.NONE); - } + autosuggestionWidgets.defaultBindings(); + tailtipWidgets.defaultBindings(); + reader.setAutosuggestion(SuggestionType.NONE); } else { terminal.writer().println("Usage: autosuggestion history|completer|none"); } diff --git a/reader/src/main/java/org/jline/reader/LineReader.java b/reader/src/main/java/org/jline/reader/LineReader.java index 21c8cffd5..aacf3fcce 100644 --- a/reader/src/main/java/org/jline/reader/LineReader.java +++ b/reader/src/main/java/org/jline/reader/LineReader.java @@ -445,7 +445,8 @@ enum RegionType { enum SuggestionType { NONE, HISTORY, - COMPLETER + COMPLETER, + TAIL_TIP } /** @@ -668,11 +669,13 @@ enum SuggestionType { void editAndAddInBuffer(File file) throws Exception; - public String getLastBinding(); + String getLastBinding(); - public String getTailTip(); + String getTailTip(); - public void setAutosuggestion(SuggestionType type); + void setTailTip(String tailTip); - public SuggestionType getAutosuggestion(); + void setAutosuggestion(SuggestionType type); + + SuggestionType getAutosuggestion(); } diff --git a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java index f243f0b85..15af38207 100644 --- a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java +++ b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java @@ -344,6 +344,11 @@ public String getTailTip(){ return tailTip; } + @Override + public void setTailTip(String tailTip) { + this.tailTip = tailTip; + } + @Override public void runMacro(String macro) { bindingReader.runMacro(macro); @@ -3947,12 +3952,30 @@ public AttributedString getDisplayedBufferWithPrompts(List sec if (buf.length() > 0 && buf.length() == buf.cursor() && (!lastBinding.equals("\t") || buf.prevChar() == ' ')) { if (buf.prevChar() == ' ') { - doEmptyList(); + clearChoices(); } listChoices(true); } else if (!lastBinding.equals("\t")){ - doEmptyList(); + clearChoices(); + } + } else if (autosuggestion == SuggestionType.TAIL_TIP + && buf.length() == buf.cursor()) { + if (!lastBinding.equals("\t")){ + clearChoices(); + } + AttributedStringBuilder sb = new AttributedStringBuilder(); + if (buf.prevChar() != ' ') { + if (!tailTip.startsWith("[")) { + int idx = tailTip.indexOf(' '); + if (idx > 0) { + tailTip = tailTip.substring(idx); + } + } else { + sb.append(" "); + } } + sb.styled(AttributedStyle::faint, tailTip); + full.append(sb.toAttributedString()); } if (post != null) { full.append("\n"); @@ -4868,7 +4891,7 @@ && getLastBinding().charAt(0) != ' ' return false; } - protected boolean doEmptyList() { + protected boolean clearChoices() { return doList(new ArrayList(), "", false, null, false); } From 3462231f74195108703fe77abdedb0b0c8e7e382 Mon Sep 17 00:00:00 2001 From: mattirn Date: Thu, 10 Oct 2019 14:59:23 +0200 Subject: [PATCH 11/14] TailTipWidgets added argument descriptions, fixes #254 --- .../main/java/org/jline/builtins/Widgets.java | 187 +++++++++++++++--- .../test/java/org/jline/example/Example.java | 47 ++++- .../org/jline/reader/impl/LineReaderImpl.java | 41 ++-- .../src/main/java/org/jline/utils/Status.java | 16 +- 4 files changed, 239 insertions(+), 52 deletions(-) diff --git a/builtins/src/main/java/org/jline/builtins/Widgets.java b/builtins/src/main/java/org/jline/builtins/Widgets.java index 86f18b5fc..7674af3c4 100644 --- a/builtins/src/main/java/org/jline/builtins/Widgets.java +++ b/builtins/src/main/java/org/jline/builtins/Widgets.java @@ -26,6 +26,8 @@ import org.jline.reader.Reference; import org.jline.reader.Widget; import org.jline.reader.impl.BufferImpl; +import org.jline.utils.AttributedString; +import org.jline.utils.Status; public abstract class Widgets { private final LineReader reader; @@ -103,6 +105,30 @@ public void setSuggestionType(SuggestionType type) { reader.setAutosuggestion(type); } + public void addDescription(List desc) { + Status.getStatus(reader.getTerminal()).update(desc); + } + + public void clearDescription() { + clearDescription(0); + } + + public void clearDescription(int size) { + if (size > 0) { + List as = new ArrayList<>(); + for (int i = 0; i < size; i++) { + as.add(new AttributedString("")); + } + addDescription(as); + } else { + Status status = Status.getStatus(reader.getTerminal(), false); + if (status != null) { + status.clear(); + } + } + } + + public static class AutopairWidgets extends Widgets { /* * Inspired by zsh-autopair @@ -456,23 +482,40 @@ public void defaultBindings() { } public static class TailTipWidgets extends Widgets { + public enum TipType { + TAIL_TIP, + COMPLETER, + COMBINED + } private final Map> defaultBindings = new HashMap<>(); private boolean autosuggestion = false; - private Map> tailTips = new HashMap<>(); - private boolean withCompleter; + private Map> tailTips = new HashMap<>(); + private TipType tipType; + private int descriptionSize = 0; + + public TailTipWidgets(LineReader reader, Map> tailTips) { + this(reader, tailTips, 0, TipType.COMBINED); + } + + public TailTipWidgets(LineReader reader, Map> tailTips, TipType tipType) { + this(reader, tailTips, 0, tipType); + } - public TailTipWidgets(LineReader reader, Map> tailTips) { - this(reader, tailTips, true); + public TailTipWidgets(LineReader reader, Map> tailTips, int descriptionSize) { + this(reader, tailTips, descriptionSize, TipType.COMBINED); } - public TailTipWidgets(LineReader reader, Map> tailTips, boolean withCompleter) { + public TailTipWidgets(LineReader reader, Map> tailTips, int descriptionSize, TipType tipType) { super(reader); this.tailTips.putAll(tailTips); - this.withCompleter = withCompleter; + this.descriptionSize = descriptionSize; + this.tipType = tipType; + clearDescription(descriptionSize); addWidget("_tailtip-accept-line", this::tailtipAcceptLine); addWidget("_tailtip-insert", this::tailtipInsert); addWidget("_tailtip-backward-delete-char", this::tailtipBackwardDelete); addWidget("_tailtip-delete-char", this::tailtipDelete); + addWidget("_tailtip-expand-or-complete", this::tailtipComplete); addWidget("tailtip-toggle", this::toggleKeyBindings); KeyMap map = getKeyMap(LineReader.MAIN); for (Map.Entry bound : map.getBoundKeys().entrySet()) { @@ -484,6 +527,8 @@ public TailTipWidgets(LineReader reader, Map> tailTips, boo addKeySequence(w, bound.getKey()); } else if (w.name().equals(LineReader.DELETE_CHAR)){ addKeySequence(w, bound.getKey()); + } else if (w.name().equals(LineReader.EXPAND_OR_COMPLETE)){ + addKeySequence(w, bound.getKey()); } } } @@ -496,18 +541,49 @@ private void addKeySequence(Reference widget, String keySequence) { defaultBindings.get(widget).add(keySequence); } + public void setDescriptionSize(int descriptionSize) { + this.descriptionSize = descriptionSize; + clearDescription(descriptionSize); + } + + public int getDescriptionSize() { + return descriptionSize; + } + + public void setTipType(TipType type) { + this.tipType = type; + if (tipType == TipType.TAIL_TIP) { + setSuggestionType(SuggestionType.TAIL_TIP); + } else { + setSuggestionType(SuggestionType.COMPLETER); + } + } + + public TipType getTipType() { + return tipType; + } + + public boolean isActive() { + return autosuggestion; + } + /* * widgets */ + public boolean tailtipComplete() { + return doTailTip(LineReader.EXPAND_OR_COMPLETE); + } + public boolean tailtipAcceptLine() { - if (withCompleter){ + if (tipType != TipType.TAIL_TIP){ setSuggestionType(SuggestionType.COMPLETER); } + clearDescription(); return clearTailTip(LineReader.ACCEPT_LINE); } public boolean tailtipBackwardDelete() { - return clearTailTip(LineReader.BACKWARD_DELETE_CHAR); + return doTailTip(LineReader.BACKWARD_DELETE_CHAR); } private boolean clearTailTip(String widget) { @@ -527,33 +603,57 @@ public boolean tailtipInsert() { private boolean doTailTip(String widget) { Buffer buffer = buffer(); - if (buffer.length() == buffer.cursor()) { + callWidget(widget); + if (buffer.length() == buffer.cursor() + && ((!widget.equals(LineReader.BACKWARD_DELETE_CHAR) && prevChar().equals(" ")) || + (widget.equals(LineReader.BACKWARD_DELETE_CHAR) && !prevChar().equals(" ")))) { List bp = args(buffer.toString()); - if (bp.size() > 0 && tailTips.containsKey(bp.get(0))) { - setSuggestionType(SuggestionType.TAIL_TIP); - List params = tailTips.get(bp.get(0)); - if (bp.size() - 1 < params.size()) { + int bpsize = bp.size() + (widget.equals(LineReader.BACKWARD_DELETE_CHAR) ? -1 : 0); + List desc = new ArrayList<>(); + if (bpsize > 0 && tailTips.containsKey(bp.get(0))) { + List params = tailTips.get(bp.get(0)); + setSuggestionType(tipType == TipType.COMPLETER ? SuggestionType.COMPLETER : SuggestionType.TAIL_TIP); + if (bpsize - 1 < params.size()) { + desc = params.get(bpsize - 1).getDescription(); StringBuilder tip = new StringBuilder(); - boolean first = true; - for (int i = bp.size() - 1; i < params.size(); i++) { - if (!first) { - tip.append(" "); - } - tip.append(params.get(i)); - first = false; + for (int i = bpsize - 1; i < params.size(); i++) { + tip.append(params.get(i).getName()); + tip.append(" "); } setTailTip(tip.toString()); - } else if (params.get(params.size() - 1).charAt(0) == '[') { - setTailTip(params.get(params.size() - 1)); + } else if (params.get(params.size() - 1).getName().charAt(0) == '[') { + setTailTip(params.get(params.size() - 1).getName()); + desc = params.get(params.size() - 1).getDescription(); + } + } else { + setTailTip(""); + if (tipType != TipType.TAIL_TIP){ + setSuggestionType(SuggestionType.COMPLETER); } - } else if (withCompleter){ - setSuggestionType(SuggestionType.COMPLETER); } + doDescription(desc); } - callWidget(widget); return true; } + private void doDescription(List desc) { + if (descriptionSize == 0) { + return; + } + if (desc.isEmpty()) { + clearDescription(); + } else if (desc.size() == descriptionSize) { + addDescription(desc); + } else if (desc.size() > descriptionSize) { + addDescription(desc.subList(0, descriptionSize)); + } else if (desc.size() < descriptionSize) { + while (desc.size() != descriptionSize) { + desc.add(new AttributedString("")); + } + addDescription(desc); + } + } + public boolean toggleKeyBindings() { if (autosuggestion) { defaultBindings(); @@ -588,9 +688,14 @@ public void autosuggestionBindings() { map.bind(new Reference("_tailtip-delete-char"), s); } } + if (entry.getKey().name().equals(LineReader.EXPAND_OR_COMPLETE)) { + for (String s: entry.getValue()) { + map.bind(new Reference("_tailtip-expand-or-complete"), s); + } + } } map.bind(new Reference("_tailtip-insert"), " "); - if (withCompleter) { + if (tipType != TipType.TAIL_TIP) { setSuggestionType(SuggestionType.COMPLETER); } else { setSuggestionType(SuggestionType.TAIL_TIP); @@ -614,4 +719,34 @@ public void defaultBindings() { } } + public static class ArgDesc { + private String name; + private List description = new ArrayList(); + + public ArgDesc(String name) { + this(name, new ArrayList()); + } + + public ArgDesc(String name, List description) { + this.name = name; + this.description.addAll(description); + } + + public String getName() { + return name; + } + + public List getDescription() { + return description; + } + + public static List doArgNames(List names) { + List out = new ArrayList<>(); + for (String n: names) { + out.add(new ArgDesc(n)); + } + return out; + } + } + } diff --git a/builtins/src/test/java/org/jline/example/Example.java b/builtins/src/test/java/org/jline/example/Example.java index 03fc2bb58..f347e3092 100644 --- a/builtins/src/test/java/org/jline/example/Example.java +++ b/builtins/src/test/java/org/jline/example/Example.java @@ -27,6 +27,8 @@ import org.jline.builtins.Widgets.AutopairWidgets; import org.jline.builtins.Widgets.AutosuggestionWidgets; import org.jline.builtins.Widgets.TailTipWidgets; +import org.jline.builtins.Widgets.TailTipWidgets.TipType; +import org.jline.builtins.Widgets.ArgDesc; import org.jline.keymap.KeyMap; import org.jline.reader.*; import org.jline.reader.LineReader.SuggestionType; @@ -99,7 +101,7 @@ public static void help() { , " unsetopt unset options" , " widget UNAVAILABLE" , " autopair toggle brackets/quotes autopair key bindings" - , " autosuggestion history, completer, tailtip or none" + , " autosuggestion history, completer, tailtip [tailtip|completer|combined] or none" , " Example:" , " cls clear screen" , " help list available commands" @@ -307,9 +309,22 @@ public void complete(LineReader reader, ParsedLine line, List candida .build(); AutopairWidgets autopairWidgets = new AutopairWidgets(reader); AutosuggestionWidgets autosuggestionWidgets = new AutosuggestionWidgets(reader); - Map> tailTips = new HashMap<>(); - tailTips.put("tail", Arrays.asList("param1", "param2", "[paramN...]")); - TailTipWidgets tailtipWidgets = new TailTipWidgets(reader, tailTips); + Map> tailTips = new HashMap<>(); + tailTips.put("tail", ArgDesc.doArgNames(Arrays.asList("param1", "param2", "[paramN...]"))); + tailTips.put("foo11", Arrays.asList( + new ArgDesc("param1",Arrays.asList(new AttributedString("line 1") + , new AttributedString("line 2") + , new AttributedString("line 3") + , new AttributedString("line 4") + , new AttributedString("line 5") + , new AttributedString("line 6") + )) + , new ArgDesc("param2",Arrays.asList(new AttributedString("line 1") + , new AttributedString("line 2") + )) + , new ArgDesc("param3", new ArrayList<>()) + )); + TailTipWidgets tailtipWidgets = new TailTipWidgets(reader, tailTips, TipType.COMPLETER); if (timer) { Executors.newScheduledThreadPool(1) .scheduleAtFixedRate(() -> { @@ -461,7 +476,7 @@ else if ("autopair".equals(pl.word())) { } } else if ("autosuggestion".equals(pl.word())) { - if (pl.words().size() == 2) { + if (pl.words().size() > 1) { String type = pl.words().get(1); if (type.toLowerCase().startsWith("his")) { tailtipWidgets.defaultBindings(); @@ -469,6 +484,17 @@ else if ("autosuggestion".equals(pl.word())) { } else if (type.toLowerCase().startsWith("tai")) { autosuggestionWidgets.defaultBindings(); tailtipWidgets.autosuggestionBindings(); + tailtipWidgets.setDescriptionSize(5); + if (pl.words().size() > 2) { + String mode = pl.words().get(2); + if (mode.toLowerCase().startsWith("tai")) { + tailtipWidgets.setTipType(TipType.TAIL_TIP); + } else if (mode.toLowerCase().startsWith("comp")) { + tailtipWidgets.setTipType(TipType.COMPLETER); + } else if (mode.toLowerCase().startsWith("comb")) { + tailtipWidgets.setTipType(TipType.COMBINED); + } + } } else if (type.toLowerCase().startsWith("com")) { autosuggestionWidgets.defaultBindings(); tailtipWidgets.defaultBindings(); @@ -478,10 +504,17 @@ else if ("autosuggestion".equals(pl.word())) { tailtipWidgets.defaultBindings(); reader.setAutosuggestion(SuggestionType.NONE); } else { - terminal.writer().println("Usage: autosuggestion history|completer|none"); + terminal.writer().println("Usage: autosuggestion history|completer|tailtip|none"); } } else { - terminal.writer().println("Autosuggestion: " + reader.getAutosuggestion()); + if (tailtipWidgets.isActive()) { + terminal.writer().println("Autosuggestion: tailtip/" + tailtipWidgets.getTipType()); + } else { + terminal.writer().println("Autosuggestion: " + reader.getAutosuggestion()); + } + } + if (!tailtipWidgets.isActive()) { + Status.getStatus(terminal).update(null); } } else if ("help".equals(pl.word()) || "?".equals(pl.word())) { diff --git a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java index 15af38207..d52ac7fdf 100644 --- a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java +++ b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java @@ -3957,25 +3957,29 @@ public AttributedString getDisplayedBufferWithPrompts(List sec listChoices(true); } else if (!lastBinding.equals("\t")){ clearChoices(); + clearStatus(); } - } else if (autosuggestion == SuggestionType.TAIL_TIP - && buf.length() == buf.cursor()) { - if (!lastBinding.equals("\t")){ - clearChoices(); - } - AttributedStringBuilder sb = new AttributedStringBuilder(); - if (buf.prevChar() != ' ') { - if (!tailTip.startsWith("[")) { - int idx = tailTip.indexOf(' '); - if (idx > 0) { - tailTip = tailTip.substring(idx); + } else if (autosuggestion == SuggestionType.TAIL_TIP) { + if (buf.length() == buf.cursor()) { + if (!lastBinding.equals("\t")){ + clearChoices(); + } + AttributedStringBuilder sb = new AttributedStringBuilder(); + if (buf.prevChar() != ' ') { + if (!tailTip.startsWith("[")) { + int idx = tailTip.indexOf(' '); + if (idx > 0) { + tailTip = tailTip.substring(idx); + } + } else { + sb.append(" "); } - } else { - sb.append(" "); } + sb.styled(AttributedStyle::faint, tailTip); + full.append(sb.toAttributedString()); + } else { + clearStatus(); } - sb.styled(AttributedStyle::faint, tailTip); - full.append(sb.toAttributedString()); } if (post != null) { full.append("\n"); @@ -3984,6 +3988,13 @@ public AttributedString getDisplayedBufferWithPrompts(List sec return full.toAttributedString(); } + private void clearStatus() { + Status status = Status.getStatus(terminal, false); + if (status != null) { + status.clear(); + } + } + private AttributedString getHighlightedBuffer(String buffer) { if (maskingCallback != null) { buffer = maskingCallback.display(buffer); diff --git a/terminal/src/main/java/org/jline/utils/Status.java b/terminal/src/main/java/org/jline/utils/Status.java index c38b95d50..bd06aafb6 100644 --- a/terminal/src/main/java/org/jline/utils/Status.java +++ b/terminal/src/main/java/org/jline/utils/Status.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002-2018, the original author or authors. + * Copyright (c) 2002-2019, the original author or authors. * * This software is distributable under the BSD license. See the terms of the * BSD license in the documentation provided with this software. @@ -13,8 +13,6 @@ import java.util.ArrayList; import java.util.List; import org.jline.terminal.Terminal; -import org.jline.terminal.Terminal.Signal; -import org.jline.terminal.Terminal.SignalHandler; import org.jline.terminal.impl.AbstractTerminal; import org.jline.utils.InfoCmp.Capability; import org.jline.terminal.Size; @@ -79,6 +77,16 @@ public void redraw() { update(oldLines); } + public void clear() { + if (!oldLines.isEmpty()) { + List as = new ArrayList<>(); + for (int i = 0; i < oldLines.size(); i++) { + as.add(new AttributedString("")); + } + update(as); + } + } + public void update(List lines) { if (!supported) { return; @@ -138,4 +146,4 @@ public int size() { return oldLines.size(); } -} \ No newline at end of file +} From 5c464bce1f9566aaf325cb246712fab121e66635 Mon Sep 17 00:00:00 2001 From: mattirn Date: Fri, 11 Oct 2019 10:01:26 +0200 Subject: [PATCH 12/14] TailTipWidgets: bug fix & small improvements --- .../main/java/org/jline/builtins/Widgets.java | 20 ++++-- .../test/java/org/jline/example/Example.java | 38 ++++++----- .../org/jline/reader/impl/LineReaderImpl.java | 65 ++++++++++--------- 3 files changed, 70 insertions(+), 53 deletions(-) diff --git a/builtins/src/main/java/org/jline/builtins/Widgets.java b/builtins/src/main/java/org/jline/builtins/Widgets.java index 7674af3c4..954f5e4ba 100644 --- a/builtins/src/main/java/org/jline/builtins/Widgets.java +++ b/builtins/src/main/java/org/jline/builtins/Widgets.java @@ -27,6 +27,8 @@ import org.jline.reader.Widget; import org.jline.reader.impl.BufferImpl; import org.jline.utils.AttributedString; +import org.jline.utils.AttributedStringBuilder; +import org.jline.utils.AttributedStyle; import org.jline.utils.Status; public abstract class Widgets { @@ -110,10 +112,10 @@ public void addDescription(List desc) { } public void clearDescription() { - clearDescription(0); + initDescription(0); } - public void clearDescription(int size) { + public void initDescription(int size) { if (size > 0) { List as = new ArrayList<>(); for (int i = 0; i < size; i++) { @@ -507,10 +509,10 @@ public TailTipWidgets(LineReader reader, Map> tailTips, int public TailTipWidgets(LineReader reader, Map> tailTips, int descriptionSize, TipType tipType) { super(reader); - this.tailTips.putAll(tailTips); + this.tailTips = new HashMap<>(tailTips); this.descriptionSize = descriptionSize; this.tipType = tipType; - clearDescription(descriptionSize); + initDescription(descriptionSize); addWidget("_tailtip-accept-line", this::tailtipAcceptLine); addWidget("_tailtip-insert", this::tailtipInsert); addWidget("_tailtip-backward-delete-char", this::tailtipBackwardDelete); @@ -543,7 +545,7 @@ private void addKeySequence(Reference widget, String keySequence) { public void setDescriptionSize(int descriptionSize) { this.descriptionSize = descriptionSize; - clearDescription(descriptionSize); + initDescription(descriptionSize); } public int getDescriptionSize() { @@ -645,7 +647,11 @@ private void doDescription(List desc) { } else if (desc.size() == descriptionSize) { addDescription(desc); } else if (desc.size() > descriptionSize) { - addDescription(desc.subList(0, descriptionSize)); + AttributedStringBuilder asb = new AttributedStringBuilder(); + asb.append(desc.get(descriptionSize - 1)).append("...", new AttributedStyle(AttributedStyle.INVERSE)); + List mod = new ArrayList<>(desc.subList(0, descriptionSize-1)); + mod.add(asb.toAttributedString()); + addDescription(mod); } else if (desc.size() < descriptionSize) { while (desc.size() != descriptionSize) { desc.add(new AttributedString("")); @@ -729,7 +735,7 @@ public ArgDesc(String name) { public ArgDesc(String name, List description) { this.name = name; - this.description.addAll(description); + this.description = new ArrayList<>(description); } public String getName() { diff --git a/builtins/src/test/java/org/jline/example/Example.java b/builtins/src/test/java/org/jline/example/Example.java index f347e3092..126b3d6c9 100644 --- a/builtins/src/test/java/org/jline/example/Example.java +++ b/builtins/src/test/java/org/jline/example/Example.java @@ -61,7 +61,7 @@ public static void usage() { , " -system terminalBuilder.system(false)" , " +system terminalBuilder.system(true)" , " Completors:" - , " argumet an argument completor" + , " argumet an argument completor & autosuggestion" , " files a completor that completes file names" , " none no completors" , " param a paramenter completer using Java functional interface" @@ -205,7 +205,14 @@ public static void main(String[] args) throws IOException { break; case "argument": completer = new ArgumentCompleter( - new StringsCompleter("foo11", "foo12", "foo13", "tail"), + new Completer() { + @Override + public void complete(LineReader reader, ParsedLine line, List candidates) { + candidates.add(new Candidate("foo11", "foo11", null, "with complete argDesc", null, null, true)); + candidates.add(new Candidate("foo12", "foo12", null, "with argDesc -names only", null, null, true)); + candidates.add(new Candidate("foo13", "foo13", null, "-", null, null, true)); + } + }, new StringsCompleter("foo21", "foo22", "foo23"), new Completer() { @Override @@ -310,16 +317,17 @@ public void complete(LineReader reader, ParsedLine line, List candida AutopairWidgets autopairWidgets = new AutopairWidgets(reader); AutosuggestionWidgets autosuggestionWidgets = new AutosuggestionWidgets(reader); Map> tailTips = new HashMap<>(); - tailTips.put("tail", ArgDesc.doArgNames(Arrays.asList("param1", "param2", "[paramN...]"))); + tailTips.put("foo12", ArgDesc.doArgNames(Arrays.asList("param1", "param2", "[paramN...]"))); tailTips.put("foo11", Arrays.asList( - new ArgDesc("param1",Arrays.asList(new AttributedString("line 1") - , new AttributedString("line 2") + new ArgDesc("param1",Arrays.asList(new AttributedString("Param1 description...") + , new AttributedString("line 2: This is a very long line that does exceed the terminal width." + +" The line will be truncated automatically (by Status class) be before printing out.") , new AttributedString("line 3") , new AttributedString("line 4") , new AttributedString("line 5") , new AttributedString("line 6") )) - , new ArgDesc("param2",Arrays.asList(new AttributedString("line 1") + , new ArgDesc("param2",Arrays.asList(new AttributedString("Param2 description...") , new AttributedString("line 2") )) , new ArgDesc("param3", new ArrayList<>()) @@ -477,29 +485,29 @@ else if ("autopair".equals(pl.word())) { } else if ("autosuggestion".equals(pl.word())) { if (pl.words().size() > 1) { - String type = pl.words().get(1); - if (type.toLowerCase().startsWith("his")) { + String type = pl.words().get(1).toLowerCase(); + if (type.startsWith("his")) { tailtipWidgets.defaultBindings(); autosuggestionWidgets.autosuggestionBindings(); - } else if (type.toLowerCase().startsWith("tai")) { + } else if (type.startsWith("tai")) { autosuggestionWidgets.defaultBindings(); tailtipWidgets.autosuggestionBindings(); tailtipWidgets.setDescriptionSize(5); if (pl.words().size() > 2) { - String mode = pl.words().get(2); - if (mode.toLowerCase().startsWith("tai")) { + String mode = pl.words().get(2).toLowerCase(); + if (mode.startsWith("tai")) { tailtipWidgets.setTipType(TipType.TAIL_TIP); - } else if (mode.toLowerCase().startsWith("comp")) { + } else if (mode.startsWith("comp")) { tailtipWidgets.setTipType(TipType.COMPLETER); - } else if (mode.toLowerCase().startsWith("comb")) { + } else if (mode.startsWith("comb")) { tailtipWidgets.setTipType(TipType.COMBINED); } } - } else if (type.toLowerCase().startsWith("com")) { + } else if (type.startsWith("com")) { autosuggestionWidgets.defaultBindings(); tailtipWidgets.defaultBindings(); reader.setAutosuggestion(SuggestionType.COMPLETER); - } else if (type.toLowerCase().startsWith("non")) { + } else if (type.startsWith("non")) { autosuggestionWidgets.defaultBindings(); tailtipWidgets.defaultBindings(); reader.setAutosuggestion(SuggestionType.NONE); diff --git a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java index d52ac7fdf..17f0324cf 100644 --- a/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java +++ b/reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java @@ -187,6 +187,7 @@ protected enum BellType { protected boolean searchFailing; protected boolean searchBackward; protected int searchIndex = -1; + protected boolean inCompleterMenu; // Reading buffers @@ -3942,49 +3943,50 @@ public AttributedString getDisplayedBufferWithPrompts(List sec AttributedStringBuilder full = new AttributedStringBuilder().tabs(TAB_WIDTH); full.append(prompt); full.append(tNewBuf); - String lastBinding = getLastBinding() != null ? getLastBinding() : ""; - if (autosuggestion == SuggestionType.HISTORY) { - AttributedStringBuilder sb = new AttributedStringBuilder(); - tailTip = matchPreviousCommand(buf.toString()); - sb.styled(AttributedStyle::faint, tailTip); - full.append(sb.toAttributedString()); - } else if (autosuggestion == SuggestionType.COMPLETER) { - if (buf.length() > 0 && buf.length() == buf.cursor() - && (!lastBinding.equals("\t") || buf.prevChar() == ' ')) { - if (buf.prevChar() == ' ') { + if (!inCompleterMenu) { + String lastBinding = getLastBinding() != null ? getLastBinding() : ""; + if (autosuggestion == SuggestionType.HISTORY) { + AttributedStringBuilder sb = new AttributedStringBuilder(); + tailTip = matchPreviousCommand(buf.toString()); + sb.styled(AttributedStyle::faint, tailTip); + full.append(sb.toAttributedString()); + } else if (autosuggestion == SuggestionType.COMPLETER) { + if (buf.length() > 0 && buf.length() == buf.cursor() + && (!lastBinding.equals("\t") || buf.prevChar() == ' ')) { clearChoices(); - } - listChoices(true); - } else if (!lastBinding.equals("\t")){ - clearChoices(); - clearStatus(); - } - } else if (autosuggestion == SuggestionType.TAIL_TIP) { - if (buf.length() == buf.cursor()) { - if (!lastBinding.equals("\t")){ + listChoices(true); + } else if (!lastBinding.equals("\t")){ clearChoices(); + clearStatus(); } - AttributedStringBuilder sb = new AttributedStringBuilder(); - if (buf.prevChar() != ' ') { - if (!tailTip.startsWith("[")) { - int idx = tailTip.indexOf(' '); - if (idx > 0) { - tailTip = tailTip.substring(idx); + } else if (autosuggestion == SuggestionType.TAIL_TIP) { + if (buf.length() == buf.cursor()) { + if (!lastBinding.equals("\t")){ + clearChoices(); + } + AttributedStringBuilder sb = new AttributedStringBuilder(); + if (buf.prevChar() != ' ') { + if (!tailTip.startsWith("[")) { + int idx = tailTip.indexOf(' '); + if (idx > 0) { + tailTip = tailTip.substring(idx); + } + } else { + sb.append(" "); } - } else { - sb.append(" "); } + sb.styled(AttributedStyle::faint, tailTip); + full.append(sb.toAttributedString()); + } else { + clearStatus(); } - sb.styled(AttributedStyle::faint, tailTip); - full.append(sb.toAttributedString()); - } else { - clearStatus(); } } if (post != null) { full.append("\n"); full.append(post.get()); } + inCompleterMenu = false; return full.toAttributedString(); } @@ -4897,6 +4899,7 @@ && getLastBinding().charAt(0) != ' ' return true; } } + inCompleterMenu = true; redisplay(); } return false; From 2c581637446ad014a930a780976768b335ff0053 Mon Sep 17 00:00:00 2001 From: mattirn Date: Fri, 11 Oct 2019 13:17:02 +0200 Subject: [PATCH 13/14] Status: added indication of truncated status lines and status border --- .../main/java/org/jline/builtins/Widgets.java | 12 ++-- .../src/main/java/org/jline/utils/Status.java | 59 +++++++++++++++---- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/builtins/src/main/java/org/jline/builtins/Widgets.java b/builtins/src/main/java/org/jline/builtins/Widgets.java index 954f5e4ba..46cc9d52e 100644 --- a/builtins/src/main/java/org/jline/builtins/Widgets.java +++ b/builtins/src/main/java/org/jline/builtins/Widgets.java @@ -116,17 +116,19 @@ public void clearDescription() { } public void initDescription(int size) { + Status status = Status.getStatus(reader.getTerminal(), false); if (size > 0) { + if (status == null) { + status = Status.getStatus(reader.getTerminal()); + } + status.setBorder(true); List as = new ArrayList<>(); for (int i = 0; i < size; i++) { as.add(new AttributedString("")); } addDescription(as); - } else { - Status status = Status.getStatus(reader.getTerminal(), false); - if (status != null) { - status.clear(); - } + } else if (status != null) { + status.clear(); } } diff --git a/terminal/src/main/java/org/jline/utils/Status.java b/terminal/src/main/java/org/jline/utils/Status.java index bd06aafb6..dc7b57ea7 100644 --- a/terminal/src/main/java/org/jline/utils/Status.java +++ b/terminal/src/main/java/org/jline/utils/Status.java @@ -27,6 +27,8 @@ public class Status { protected int columns; protected boolean force; protected boolean suspended = false; + protected AttributedString borderString; + protected int border = 0; public static Status getStatus(Terminal terminal) { return getStatus(terminal, true); @@ -46,10 +48,21 @@ public Status(AbstractTerminal terminal) { && terminal.getStringCapability(Capability.restore_cursor) != null && terminal.getStringCapability(Capability.cursor_address) != null; if (supported) { + char borderChar = '─'; + AttributedStringBuilder bb = new AttributedStringBuilder(); + for (int i = 0; i < 200; i++) { + bb.append(borderChar); + } + borderString = bb.toAttributedString(); resize(); } } + public void setBorder(boolean border) { + clear(); + this.border = border ? 1 : 0; + } + public void resize() { Size size = terminal.getSize(); this.rows = size.getRows(); @@ -78,11 +91,21 @@ public void redraw() { } public void clear() { - if (!oldLines.isEmpty()) { - List as = new ArrayList<>(); - for (int i = 0; i < oldLines.size(); i++) { - as.add(new AttributedString("")); - } + privateClear(oldLines.size()); + } + + private void clearAll() { + int b = border; + border = 0; + privateClear(oldLines.size() + b); + } + + private void privateClear(int statusSize) { + List as = new ArrayList<>(); + for (int i = 0; i < statusSize; i++) { + as.add(new AttributedString("")); + } + if (!as.isEmpty()) { update(as); } } @@ -98,10 +121,14 @@ public void update(List lines) { linesToRestore = new ArrayList<>(lines); return; } + if (lines.isEmpty()) { + clearAll(); + } if (oldLines.equals(lines) && !force) { return; } - int nb = lines.size() - oldLines.size(); + int statusSize = lines.size() + (lines.size() == 0 ? 0 : border); + int nb = statusSize - oldLines.size() - (oldLines.size() == 0 ? 0 : border); if (nb > 0) { for (int i = 0; i < nb; i++) { terminal.puts(Capability.cursor_down); @@ -111,13 +138,23 @@ public void update(List lines) { } } terminal.puts(Capability.save_cursor); - terminal.puts(Capability.cursor_address, rows - lines.size(), 0); + terminal.puts(Capability.cursor_address, rows - statusSize, 0); terminal.puts(Capability.clr_eos); + if (border == 1 && lines.size() > 0) { + terminal.puts(Capability.cursor_address, rows - statusSize, 0); + borderString.columnSubSequence(0, columns).print(terminal); + } for (int i = 0; i < lines.size(); i++) { terminal.puts(Capability.cursor_address, rows - lines.size() + i, 0); - lines.get(i).columnSubSequence(0, columns).print(terminal); + if (lines.get(i).length() > columns) { + AttributedStringBuilder asb = new AttributedStringBuilder(); + asb.append(lines.get(i).substring(0, columns - 3)).append("...", new AttributedStyle(AttributedStyle.INVERSE)); + asb.toAttributedString().columnSubSequence(0, columns).print(terminal); + } else { + lines.get(i).columnSubSequence(0, columns).print(terminal); + } } - terminal.puts(Capability.change_scroll_region, 0, rows - 1 - lines.size()); + terminal.puts(Capability.change_scroll_region, 0, rows - 1 - statusSize); terminal.puts(Capability.restore_cursor); terminal.flush(); oldLines = new ArrayList<>(lines); @@ -136,14 +173,14 @@ public void suspend() { public void restore() { if (!suspended) { return; - } + } suspended = false; update(linesToRestore); linesToRestore = Collections.emptyList(); } public int size() { - return oldLines.size(); + return oldLines.size() + border; } } From cdbb55eb19ef6a533d5b5f2e382e20f828c14be8 Mon Sep 17 00:00:00 2001 From: mattirn Date: Wed, 25 Sep 2019 19:15:57 +0200 Subject: [PATCH 14/14] tmux: Added a couple of windows commands --- .../main/java/org/jline/builtins/Tmux.java | 505 ++++++++++++------ 1 file changed, 351 insertions(+), 154 deletions(-) diff --git a/builtins/src/main/java/org/jline/builtins/Tmux.java b/builtins/src/main/java/org/jline/builtins/Tmux.java index ac7ec5f3c..464e9487d 100644 --- a/builtins/src/main/java/org/jline/builtins/Tmux.java +++ b/builtins/src/main/java/org/jline/builtins/Tmux.java @@ -39,6 +39,7 @@ import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.IntStream; import org.jline.builtins.Options.HelpException; import org.jline.keymap.BindingReader; @@ -88,6 +89,14 @@ public class Tmux { public static final String CMD_BIND = "bind"; public static final String CMD_UNBIND_KEY = "unbind-key"; public static final String CMD_UNBIND = "unbind"; + public static final String CMD_NEW_WINDOW = "new-window"; + public static final String CMD_NEWW = "neww"; + public static final String CMD_NEXT_WINDOW = "next-window"; + public static final String CMD_NEXT = "next"; + public static final String CMD_PREVIOUS_WINDOW = "previous-window"; + public static final String CMD_PREV = "prev"; + public static final String CMD_LIST_WINDOWS = "list-windows"; + public static final String CMD_LSW = "lsw"; private static final int[][][] WINDOW_CLOCK_TABLE = { { { 1,1,1,1,1 }, /* 0 */ @@ -170,13 +179,11 @@ public class Tmux { private final PrintStream err; private final String term; private final Consumer runner; - private List panes = new CopyOnWriteArrayList<>(); - private VirtualConsole active; - private int lastActive; + private List windows = new ArrayList<>(); + private Integer windowsId = 0; + private int activeWindow = 0; private final AtomicBoolean running = new AtomicBoolean(true); private final Size size = new Size(); - private final AtomicInteger paneId = new AtomicInteger(); - private Layout layout; private boolean identify; private ScheduledExecutorService executor; @@ -190,6 +197,184 @@ enum Binding { Discard, SelfInsert, Mouse } + private class Window { + private List panes = new CopyOnWriteArrayList<>(); + private VirtualConsole active; + private int lastActive; + private final AtomicInteger paneId = new AtomicInteger(); + private Layout layout; + private Tmux tmux; + private String name; + + public Window(Tmux tmux) throws IOException { + this.tmux = tmux; + layout = new Layout(); + layout.sx = size.getColumns(); + layout.sy = size.getRows(); + layout.type = WindowPane; + active = new VirtualConsole(paneId.incrementAndGet(), term + , 0, 0, size.getColumns(), size.getRows() - 1 + , tmux::setDirty, tmux::close, layout); + active.active = lastActive++; + active.getConsole().setAttributes(terminal.getAttributes()); + panes.add(active); + name = "win" + (windowsId < 10 ? "0" + windowsId : windowsId); + windowsId++; + } + + public String getName() { + return name; + } + public List getPanes() { + return panes; + } + + public VirtualConsole getActive() { + return active; + } + + public void remove(VirtualConsole console) { + panes.remove(console); + if (!panes.isEmpty()) { + console.layout.remove(); + if (active == console) { + active = panes.stream() + .sorted(Comparator.comparingInt(p -> p.active).reversed()) + .findFirst().get(); + } + layout = active.layout; + while (layout.parent != null) { + layout = layout.parent; + } + layout.fixOffsets(); + layout.fixPanes(size.getColumns(), size.getRows()); + } + } + + public void handleResize() { + layout.resize(size.getColumns(), size.getRows() - 1); + panes.forEach(vc -> { + if (vc.width() != vc.layout.sx || vc.height() != vc.layout.sy + || vc.left() != vc.layout.xoff || vc.top() != vc.layout.yoff) { + vc.resize(vc.layout.xoff, vc.layout.yoff, vc.layout.sx, vc.layout.sy); + display.clear(); + } + }); + } + + public VirtualConsole splitPane(Options opt) throws IOException { + Layout.Type type = opt.isSet("horizontal") ? LeftRight : TopBottom; + // If we're splitting the main pane, create a parent + if (layout.type == WindowPane) { + Layout p = new Layout(); + p.sx = layout.sx; + p.sy = layout.sy; + p.type = type; + p.cells.add(layout); + layout.parent = p; + layout = p; + } + Layout cell = active.layout(); + if (opt.isSet("f")) { + while (cell.parent != layout) { + cell = cell.parent; + } + } + int size = -1; + if (opt.isSet("size")) { + size = opt.getNumber("size"); + } else if (opt.isSet("perc")) { + int p = opt.getNumber("perc"); + if (type == TopBottom) { + size = (cell.sy * p) / 100; + } else { + size = (cell.sx * p) / 100; + } + } + // Split now + Layout newCell = cell.split(type, size, opt.isSet("before")); + if (newCell == null) { + err.println("create pane failed: pane too small"); + return null; + } + + VirtualConsole newConsole = new VirtualConsole(paneId.incrementAndGet(), term + , newCell.xoff, newCell.yoff, newCell.sx, newCell.sy + , tmux::setDirty, tmux::close, newCell); + panes.add(newConsole); + newConsole.getConsole().setAttributes(terminal.getAttributes()); + if (!opt.isSet("d")) { + active = newConsole; + active.active = lastActive++; + } + return newConsole; + } + + public boolean selectPane(Options opt) { + VirtualConsole prevActive = active; + if (opt.isSet("L")) { + active = panes.stream() + .filter(c -> c.bottom() > active.top() && c.top() < active.bottom()) + .filter(c -> c != active) + .sorted(Comparator + .comparingInt(c -> c.left() > active.left() ? c.left() : c.left() + size.getColumns()).reversed() + .thenComparingInt(c -> - c.active)) + .findFirst().orElse(active); + } + else if (opt.isSet("R")) { + active = panes.stream() + .filter(c -> c.bottom() > active.top() && c.top() < active.bottom()) + .filter(c -> c != active) + .sorted(Comparator + .comparingInt(c -> c.left() > active.left() ? c.left() : c.left() + size.getColumns()) + .thenComparingInt(c -> - c.active)) + .findFirst().orElse(active); + } else if (opt.isSet("U")) { + active = panes.stream() + .filter(c -> c.right() > active.left() && c.left() < active.right()) + .filter(c -> c != active) + .sorted(Comparator + .comparingInt(c -> c.top() > active.top() ? c.top() : c.top() + size.getRows()).reversed() + .thenComparingInt(c -> - c.active)) + .findFirst().orElse(active); + } else if (opt.isSet("D")) { + active = panes.stream() + .filter(c -> c.right() > active.left() && c.left() < active.right()) + .filter(c -> c != active) + .sorted(Comparator + .comparingInt(c -> c.top() > active.top() ? c.top() : c.top() + size.getRows()) + .thenComparingInt(c -> - c.active)) + .findFirst().orElse(active); + } + boolean out = false; + if (prevActive != active) { + active.active = lastActive++; + out = true; + } + return out; + } + + public void resizePane(Options opt, int adjust) { + if (opt.isSet("width")) { + int x = opt.getNumber("width"); + active.layout().resizeTo(LeftRight, x); + } + if (opt.isSet("height")) { + int y = opt.getNumber("height"); + active.layout().resizeTo(TopBottom, y); + } + if (opt.isSet("L")) { + active.layout().resize(LeftRight, -adjust, true); + } else if (opt.isSet("R")) { + active.layout().resize(LeftRight, adjust, true); + } else if (opt.isSet("U")) { + active.layout().resize(TopBottom, -adjust, true); + } else if (opt.isSet("D")) { + active.layout().resize(TopBottom, adjust, true); + } + } + } + public Tmux(Terminal terminal, PrintStream err, Consumer runner) throws IOException { this.terminal = terminal; this.err = err; @@ -222,6 +407,9 @@ protected KeyMap createKeyMap(String prefix) { keyMap.bind(CMD_RESIZE_PANE + " -R", prefix + translate("^[[1;5D"), prefix + alt(translate("^[[D"))); // ctrl-right keyMap.bind(CMD_DISPLAY_PANES, prefix + "q"); keyMap.bind(CMD_CLOCK_MODE, prefix + "t"); + keyMap.bind(CMD_NEW_WINDOW, prefix + "c"); + keyMap.bind(CMD_NEXT_WINDOW, prefix + "n"); + keyMap.bind(CMD_PREVIOUS_WINDOW, prefix + "p"); return keyMap; } @@ -249,15 +437,9 @@ public void run() throws IOException { try { // Create first pane size.copy(terminal.getSize()); - layout = new Layout(); - layout.sx = size.getColumns(); - layout.sy = size.getRows(); - layout.type = WindowPane; - active = new VirtualConsole(paneId.incrementAndGet(), term, 0, 0, size.getColumns(), size.getRows() - 1, this::setDirty, this::close, layout); - active.active = lastActive++; - active.getConsole().setAttributes(terminal.getAttributes()); - panes.add(active); - runner.accept(active.getConsole()); + windows.add(new Window(this)); + activeWindow = 0; + runner.accept(active().getConsole()); // Start input loop new Thread(this::inputLoop, "Mux input loop").start(); // Redraw loop @@ -277,6 +459,18 @@ public void run() throws IOException { } } + private VirtualConsole active() { + return windows.get(activeWindow).getActive(); + } + + private List panes() { + return windows.get(activeWindow).getPanes(); + } + + private Window window() { + return windows.get(activeWindow); + } + private void redrawLoop() { while (running.get()) { try { @@ -314,22 +508,22 @@ private void inputLoop() { b = null; } if (b == Binding.SelfInsert) { - if (active.clock) { - active.clock = false; - if (clockFuture != null && panes.stream().noneMatch(vc -> vc.clock)) { + if (active().clock) { + active().clock = false; + if (clockFuture != null && panes().stream().noneMatch(vc -> vc.clock)) { clockFuture.cancel(false); clockFuture = null; } setDirty(); } else { - active.getMasterInputOutput().write(reader.getLastBinding().getBytes()); + active().getMasterInputOutput().write(reader.getLastBinding().getBytes()); first = false; } } else { if (first) { first = false; } else { - active.getMasterInputOutput().flush(); + active().getMasterInputOutput().flush(); first = true; } if (b == Binding.Mouse) { @@ -362,25 +556,29 @@ private void inputLoop() { } private synchronized void close(VirtualConsole terminal) { - int idx = panes.indexOf(terminal); + int idx = -1; + Window window = null; + for (Window w: windows) { + idx = w.getPanes().indexOf(terminal); + if (idx >= 0) { + window = w; + break; + } + } if (idx >= 0) { - panes.remove(idx); - if (panes.isEmpty()) { - running.set(false); - setDirty(); - } else { - terminal.layout.remove(); - if (active == terminal) { - active = panes.stream() - .sorted(Comparator.comparingInt(p -> p.active).reversed()) - .findFirst().get(); - } - layout = active.layout; - while (layout.parent != null) { - layout = layout.parent; + window.remove(terminal); + if (window.getPanes().isEmpty()) { + if (windows.size() > 1) { + windows.remove(window); + if (activeWindow >= windows.size()) { + activeWindow--; + } + resize(Signal.WINCH); + } else { + running.set(false); + setDirty(); } - layout.fixOffsets(); - layout.fixPanes(size.getColumns(), size.getRows()); + } else { resize(Signal.WINCH); } } @@ -392,11 +590,11 @@ private void resize(Signal signal) { } private void interrupt(Signal signal) { - active.getConsole().raise(signal); + active().getConsole().raise(signal); } private void suspend(Signal signal) { - active.getConsole().raise(signal); + active().getConsole().raise(signal); } private void handleResize() { @@ -404,14 +602,7 @@ private void handleResize() { if (resized.compareAndSet(true, false)) { size.copy(terminal.getSize()); } - layout.resize(size.getColumns(), size.getRows() - 1); - panes.forEach(vc -> { - if (vc.width() != vc.layout.sx || vc.height() != vc.layout.sy - || vc.left() != vc.layout.xoff || vc.top() != vc.layout.yoff) { - vc.resize(vc.layout.xoff, vc.layout.yoff, vc.layout.sx, vc.layout.sy); - display.clear(); - } - }); + window().handleResize(); } public void execute(PrintStream out, PrintStream err, String command) throws Exception { @@ -465,7 +656,106 @@ public synchronized void execute(PrintStream out, PrintStream err, List case CMD_SET: setOption(out, err, args); break; + case CMD_NEW_WINDOW: + case CMD_NEWW: + newWindow(out, err, args); + break; + case CMD_NEXT_WINDOW: + case CMD_NEXT: + nextWindow(out, err, args); + break; + case CMD_PREVIOUS_WINDOW: + case CMD_PREV: + previousWindow(out, err, args); + break; + case CMD_LIST_WINDOWS: + case CMD_LSW: + listWindows(out, err, args); + break; + } + } + + protected void listWindows(PrintStream out, PrintStream err, List args) throws Exception { + final String[] usage = { + "list-windows - ", + "Usage: list-windows", + " -? --help Show help" + }; + Options opt = Options.compile(usage).parse(args); + if (opt.isSet("help")) { + throw new HelpException(opt.usage()); } + IntStream.range(0, windows.size()) + .mapToObj(i -> { + StringBuilder sb = new StringBuilder(); + sb.append(i); + sb.append(": "); + sb.append(windows.get(i).getName()); + sb.append(i == activeWindow ? "* " : " "); + sb.append("("); + sb.append(windows.get(i).getPanes().size()); + sb.append(" panes)"); + if (i == activeWindow) { + sb.append(" (active)"); + } + return sb.toString(); + }) + .sorted() + .forEach(out::println); + } + + protected void previousWindow(PrintStream out, PrintStream err, List args) throws Exception { + final String[] usage = { + "previous-window - ", + "Usage: previous-window", + " -? --help Show help" + }; + Options opt = Options.compile(usage).parse(args); + if (opt.isSet("help")) { + throw new HelpException(opt.usage()); + } + if (windows.size() > 1) { + activeWindow--; + if (activeWindow < 0) { + activeWindow = windows.size() - 1; + } + setDirty(); + } + } + + protected void nextWindow(PrintStream out, PrintStream err, List args) throws Exception { + final String[] usage = { + "next-window - ", + "Usage: next-window", + " -? --help Show help" + }; + Options opt = Options.compile(usage).parse(args); + if (opt.isSet("help")) { + throw new HelpException(opt.usage()); + } + if (windows.size() > 1) { + activeWindow++; + if (activeWindow >= windows.size()) { + activeWindow = 0; + } + setDirty(); + } + } + + protected void newWindow(PrintStream out, PrintStream err, List args) throws Exception { + final String[] usage = { + "new-window - ", + "Usage: new-window", + " -? --help Show help" + }; + Options opt = Options.compile(usage).parse(args); + if (opt.isSet("help")) { + throw new HelpException(opt.usage()); + } + windows.add(new Window(this)); + activeWindow = windows.size() - 1; + runner.accept(active().getConsole()); + setDirty(); } protected void setOption(PrintStream out, PrintStream err, List args) throws Exception { @@ -604,7 +894,7 @@ protected void sendKeys(PrintStream out, PrintStream err, List args) thr for (int i = 0, n = opt.getNumber("number"); i < n; i++) { for (String arg : opt.args()) { String s = opt.isSet("literal") ? arg : KeyMap.translate(arg); - active.getMasterInputOutput().write(s.getBytes()); + active().getMasterInputOutput().write(s.getBytes()); } } } @@ -619,7 +909,7 @@ protected void clockMode(PrintStream out, PrintStream err, List args) th if (opt.isSet("help")) { throw new HelpException(opt.usage()); } - active.clock = true; + active().clock = true; if (clockFuture == null) { long initial = Instant.now().until(Instant.now().truncatedTo(ChronoUnit.MINUTES).plusSeconds(60), ChronoUnit.MILLIS); @@ -671,23 +961,7 @@ protected void resizePane(PrintStream out, PrintStream err, List args) t } else { throw new HelpException(opt.usage()); } - if (opt.isSet("width")) { - int x = opt.getNumber("width"); - active.layout().resizeTo(LeftRight, x); - } - if (opt.isSet("height")) { - int y = opt.getNumber("height"); - active.layout().resizeTo(TopBottom, y); - } - if (opt.isSet("L")) { - active.layout().resize(LeftRight, -adjust, true); - } else if (opt.isSet("R")) { - active.layout().resize(LeftRight, adjust, true); - } else if (opt.isSet("U")) { - active.layout().resize(TopBottom, -adjust, true); - } else if (opt.isSet("D")) { - active.layout().resize(TopBottom, adjust, true); - } + window().resizePane(opt, adjust); setDirty(); } @@ -705,44 +979,8 @@ protected void selectPane(PrintStream out, PrintStream err, List args) t if (opt.isSet("help")) { throw new HelpException(opt.usage()); } - VirtualConsole prevActive = active; - if (opt.isSet("L")) { - active = panes.stream() - .filter(c -> c.bottom() > active.top() && c.top() < active.bottom()) - .filter(c -> c != active) - .sorted(Comparator - .comparingInt(c -> c.left() > active.left() ? c.left() : c.left() + size.getColumns()).reversed() - .thenComparingInt(c -> - c.active)) - .findFirst().orElse(active); - } - else if (opt.isSet("R")) { - active = panes.stream() - .filter(c -> c.bottom() > active.top() && c.top() < active.bottom()) - .filter(c -> c != active) - .sorted(Comparator - .comparingInt(c -> c.left() > active.left() ? c.left() : c.left() + size.getColumns()) - .thenComparingInt(c -> - c.active)) - .findFirst().orElse(active); - } else if (opt.isSet("U")) { - active = panes.stream() - .filter(c -> c.right() > active.left() && c.left() < active.right()) - .filter(c -> c != active) - .sorted(Comparator - .comparingInt(c -> c.top() > active.top() ? c.top() : c.top() + size.getRows()).reversed() - .thenComparingInt(c -> - c.active)) - .findFirst().orElse(active); - } else if (opt.isSet("D")) { - active = panes.stream() - .filter(c -> c.right() > active.left() && c.left() < active.right()) - .filter(c -> c != active) - .sorted(Comparator - .comparingInt(c -> c.top() > active.top() ? c.top() : c.top() + size.getRows()) - .thenComparingInt(c -> - c.active)) - .findFirst().orElse(active); - } - if (prevActive != active) { + if (window().selectPane(opt)) { setDirty(); - active.active = lastActive++; } } @@ -756,7 +994,7 @@ protected void sendPrefix(PrintStream out, PrintStream err, List args) t if (opt.isSet("help")) { throw new HelpException(opt.usage()); } - active.getMasterInputOutput().write(serverOptions.get(OPT_PREFIX).getBytes()); + active().getMasterInputOutput().write(serverOptions.get(OPT_PREFIX).getBytes()); } protected void splitWindow(PrintStream out, PrintStream err, List args) throws Exception { @@ -776,48 +1014,7 @@ protected void splitWindow(PrintStream out, PrintStream err, List args) if (opt.isSet("help")) { throw new HelpException(opt.usage()); } - Layout.Type type = opt.isSet("horizontal") ? LeftRight : TopBottom; - // If we're splitting the main pane, create a parent - if (layout.type == WindowPane) { - Layout p = new Layout(); - p.sx = layout.sx; - p.sy = layout.sy; - p.type = type; - p.cells.add(layout); - layout.parent = p; - layout = p; - } - Layout cell = active.layout(); - if (opt.isSet("f")) { - while (cell.parent != layout) { - cell = cell.parent; - } - } - int size = -1; - if (opt.isSet("size")) { - size = opt.getNumber("size"); - } else if (opt.isSet("perc")) { - int p = opt.getNumber("perc"); - if (type == TopBottom) { - size = (cell.sy * p) / 100; - } else { - size = (cell.sx * p) / 100; - } - } - // Split now - Layout newCell = cell.split(type, size, opt.isSet("before")); - if (newCell == null) { - err.println("create pane failed: pane too small"); - return; - } - - VirtualConsole newConsole = new VirtualConsole(paneId.incrementAndGet(), term, newCell.xoff, newCell.yoff, newCell.sx, newCell.sy, this::setDirty, this::close, newCell); - panes.add(newConsole); - newConsole.getConsole().setAttributes(terminal.getAttributes()); - if (!opt.isSet("d")) { - active = newConsole; - active.active = lastActive++; - } + VirtualConsole newConsole = window().splitPane(opt); runner.accept(newConsole.getConsole()); setDirty(); } @@ -835,37 +1032,37 @@ protected synchronized void redraw() { // Fill Arrays.fill(screen, 0x00000020L); int[] cursor = new int[2]; - for (VirtualConsole terminal : panes) { + for (VirtualConsole terminal : panes()) { if (terminal.clock) { String str = DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date()); print(screen, terminal, str, CLOCK_COLOR); } else { // Dump terminal terminal.dump(screen, terminal.top(), terminal.left(), size.getRows(), size.getColumns(), - terminal == active ? cursor : null); + terminal == active() ? cursor : null); } if (identify) { String id = Integer.toString(terminal.id); - print(screen, terminal, id, terminal == active ? ACTIVE_COLOR : INACTIVE_COLOR); + print(screen, terminal, id, terminal == active() ? ACTIVE_COLOR : INACTIVE_COLOR); } // Draw border drawBorder(screen, size, terminal, 0x0L); } - drawBorder(screen, size, active, 0x010080000L << 32); + drawBorder(screen, size, active(), 0x010080000L << 32); // Draw status Arrays.fill(screen, (size.getRows() - 1) * size.getColumns(), size.getRows() * size.getColumns(), 0x20000080L << 32 | 0x0020L); // Attribute mask: 0xYXFFFBBB00000000L - // X: Bit 0 - Underlined - // Bit 1 - Negative - // Bit 2 - Concealed + // X: Bit 0 - Underlined + // Bit 1 - Negative + // Bit 2 - Concealed // Bit 3 - Bold // Y: Bit 0 - Foreground set // Bit 1 - Background set - // F: Foreground r-g-b - // B: Background r-g-b + // F: Foreground r-g-b + // B: Background r-g-b List lines = new ArrayList<>(); int prevBg = 0;