Skip to content

Commit

Permalink
Add option to do list view of autocomplete suggestions, #582
Browse files Browse the repository at this point in the history
  • Loading branch information
mattirn committed Oct 5, 2020
1 parent 0d861dd commit 8053dc1
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 15 deletions.
11 changes: 9 additions & 2 deletions demo/src/main/scripts/init.jline
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ def _tailTipToggle() {
def al = _reader.getWidgets().get('accept-line').toString()
def enabled = al == '_tailtip-accept-line'
if (enabled) {
_reader.setVariable('LIST_MAX',100)
_reader.setVariable('list-max',100)
_reader.option(org.jline.reader.LineReader.Option.INSERT_BRACKET, true)
} else {
_reader.setVariable('LIST_MAX',50)
_reader.setVariable('list-max',50)
_reader.option(org.jline.reader.LineReader.Option.INSERT_BRACKET, false)
}
_widget 'tailtip-toggle'
Expand Down Expand Up @@ -153,6 +153,11 @@ def _toggleMethods() {
def _toggleMetaMethods() {
GROOVY_OPTIONS.metaMethodsCompletion = !GROOVY_OPTIONS.metaMethodsCompletion
}

def _toggleMenuList() {
def optAutoMenu = org.jline.reader.LineReader.Option.AUTO_MENU_LIST
_reader.option(optAutoMenu, !_reader.isSet(optAutoMenu))
}
#
# resolve function keys
#
Expand All @@ -171,12 +176,14 @@ widget -N _doc-groovy _docGroovy
widget -N _toggle-fields _toggleFields
widget -N _toggle-methods _toggleMethods
widget -N _toggle-meta-methods _toggleMetaMethods
widget -N _toggle-menu-list _toggleMenuList

keymap '^[^x' _test-widget
keymap '^[s' _tailtip-toggle
keymap '^[f' _toggle-fields
keymap '^[g' _toggle-meta-methods
keymap '^[m' _toggle-methods
keymap '^[l' _toggle-menu-list
if (_f1 && _f2) {
:keymap $_f1 _doc-jline
:keymap $_f2 _doc-groovy
Expand Down
5 changes: 5 additions & 0 deletions reader/src/main/java/org/jline/reader/LineReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,12 @@ public interface LineReader {
String COMPLETION_STYLE_SELECTION = "COMPLETION_STYLE_SELECTION";
/** Completion style for displaying the candidate description */
String COMPLETION_STYLE_DESCRIPTION = "COMPLETION_STYLE_DESCRIPTION";
String COMPLETION_COLOR_DESCRIPTION = "COMPLETION_COLOR_DESCRIPTION";
/** Completion style for displaying the matching part of candidates */
String COMPLETION_STYLE_STARTING = "COMPLETION_STYLE_STARTING";
String COMPLETION_COLOR_STARTING = "COMPLETION_COLOR_STARTING";
/** Completion list background color */
String COMPLETION_LIST_BACKGROUND_COLOR = "COMPLETION_LIST_BACKGROUND_COLOR";
/**
* Set the template for prompts for secondary (continuation) lines.
* This is a prompt template as described in the class header.
Expand Down Expand Up @@ -386,6 +390,7 @@ enum Option {
AUTO_GROUP(true),
AUTO_MENU(true),
AUTO_LIST(true),
AUTO_MENU_LIST(false),
RECOGNIZE_EXACT,
/** display group name before each group (else display all group names first) */
GROUP(true),
Expand Down
106 changes: 93 additions & 13 deletions reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,13 @@ public class LineReaderImpl implements LineReader, Flushable
public static final String DEFAULT_SECONDARY_PROMPT_PATTERN = "%M> ";
public static final String DEFAULT_OTHERS_GROUP_NAME = "others";
public static final String DEFAULT_ORIGINAL_GROUP_NAME = "original";
public static final String DEFAULT_COMPLETION_STYLE_STARTING = "36"; // cyan
public static final String DEFAULT_COMPLETION_STYLE_DESCRIPTION = "90"; // dark gray
public static final String DEFAULT_COMPLETION_STYLE_STARTING = "";
public static final String DEFAULT_COMPLETION_STYLE_DESCRIPTION = "";
public static final String DEFAULT_COMPLETION_STYLE_GROUP = "35;1"; // magenta
public static final String DEFAULT_COMPLETION_STYLE_SELECTION = "7"; // inverted
public static final int DEFAULT_COMPLETION_COLOR_STARTING = 6; // cyan
public static final int DEFAULT_COMPLETION_COLOR_DESCRIPTION = 8; // dark gray
public static final int DEFAULT_COMPLETION_LIST_BACKGROUND_COLOR = 13;
public static final int DEFAULT_INDENTATION = 0;
public static final int DEFAULT_FEATURES_MAX_BUFFER_SIZE = 1000;

Expand Down Expand Up @@ -270,6 +273,8 @@ protected enum BellType {
*/
protected List<String> commandsBuffer = new ArrayList<>();

int candidateStartPosition = 0;

public LineReaderImpl(Terminal terminal) throws IOException {
this(terminal, null, null);
}
Expand Down Expand Up @@ -1069,7 +1074,7 @@ public void editAndAddInBuffer(File file) throws Exception {
Constructor<?> ctor = Class.forName("org.jline.builtins.Nano").getConstructor(Terminal.class, File.class);
Editor editor = (Editor) ctor.newInstance(terminal, new File(file.getParent()));
editor.setRestricted(true);
editor.open(Arrays.asList(file.getName()));
editor.open(Collections.singletonList(file.getName()));
editor.run();
BufferedReader br = new BufferedReader(new FileReader(file));
String line;
Expand Down Expand Up @@ -4469,9 +4474,7 @@ protected boolean doComplete(CompletionType lst, boolean useMenu, boolean prefix
typoMatcher(wdi, errors, caseInsensitive)
);
} else {
matchers = Arrays.asList(
simpleMatcher(s -> !s.startsWith("-"))
);
matchers = Collections.singletonList(simpleMatcher(s -> !s.startsWith("-")));
}
exact = s -> caseInsensitive ? s.equalsIgnoreCase(wd) : s.equals(wd);
}
Expand Down Expand Up @@ -4977,7 +4980,7 @@ && getLastBinding().charAt(0) != ' '
}

protected boolean clearChoices() {
return doList(new ArrayList<Candidate>(), "", false, null, false);
return doList(new ArrayList<>(), "", false, null, false);
}

protected boolean doList(List<Candidate> possible
Expand Down Expand Up @@ -5032,6 +5035,7 @@ protected boolean doList(List<Candidate> possible
.sorted(getCandidateComparator(caseInsensitive, current))
.collect(Collectors.toList());
}
candidateStartPosition = candidateStartPosition();
post = () -> {
AttributedString t = insertSecondaryPrompts(AttributedStringBuilder.append(prompt, buf.toString()), new ArrayList<>());
int pl = t.columnSplitLength(size.getColumns(), false, display.delayLineWrap()).size();
Expand Down Expand Up @@ -5165,6 +5169,33 @@ protected PostResult computePost(List<Candidate> possible, Candidate selection,
private static final String DESC_SUFFIX = ")";
private static final int MARGIN_BETWEEN_DISPLAY_AND_DESC = 1;
private static final int MARGIN_BETWEEN_COLUMNS = 3;
private static final int MENU_LIST_WIDTH = 25;

private int candidateStartPosition() {
int out = prompt != null ? prompt.length() : 0;
String buffer = buf.substring(0, buf.cursor());
buffer = buffer.substring(buffer.lastIndexOf('\n') + 1);
boolean first = true;
int width = size.getColumns();
while (buffer.length() + (first ? out : 0) > width) {
if (first) {
buffer = buffer.substring(width - prompt.length());
} else {
buffer = buffer.substring(width);
}
first = false;
}
if (!first) {
out = 0;
}
for (int i = buffer.length(); i > 0; i--) {
if (buffer.substring(0, i).matches(".*\\W")) {
out += i;
break;
}
}
return out;
}

@SuppressWarnings("unchecked")
protected PostResult toColumns(List<Object> items, Candidate selection, String completed, Function<String, Integer> wcwidth, int width, boolean rowsFirst) {
Expand Down Expand Up @@ -5192,6 +5223,11 @@ else if (item instanceof List) {
}
// Build columns
AttributedStringBuilder sb = new AttributedStringBuilder();
if (isSet(Option.AUTO_MENU_LIST)) {
maxWidth = Math.max(maxWidth, MENU_LIST_WIDTH);
sb.tabs(Math.min(candidateStartPosition, width - maxWidth - 1));
width = maxWidth + 2;
}
for (Object list : items) {
toColumns(list, width, maxWidth, sb, selection, completed, rowsFirst, out);
}
Expand All @@ -5206,8 +5242,9 @@ protected void toColumns(Object items, int width, int maxWidth, AttributedString
if (maxWidth <= 0 || width <= 0) {
return;
}
boolean isMenuList = isSet(Option.AUTO_MENU_LIST);
// This is a group
if (items instanceof String) {
if (items instanceof String && !isMenuList) {
sb.style(getCompletionStyleGroup())
.append((String) items)
.style(AttributedStyle.DEFAULT)
Expand All @@ -5233,6 +5270,10 @@ else if (items instanceof List) {
index = (i, j) -> j * lines + i;
}
for (int i = 0; i < lines; i++) {
if (isMenuList) {
sb.style(AttributedStyle.DEFAULT);
sb.append('\t');
}
for (int j = 0; j < columns; j++) {
int idx = index.applyAsInt(i, j);
if (idx < candidates.size()) {
Expand Down Expand Up @@ -5271,33 +5312,42 @@ else if (items instanceof List) {
if (right != null) {
sb.append(right);
}
sb.style(AttributedStyle.DEFAULT);
} else {
if (left.toString().regionMatches(
isSet(Option.CASE_INSENSITIVE), 0, completed, 0, completed.length())) {
sb.style(getCompletionStyleStarting());
sb.append(left, 0, completed.length());
sb.style(AttributedStyle.DEFAULT);
sb.style(getCompletionListBackgroundStyle());
sb.append(left, completed.length(), left.length());
} else {
sb.style(getCompletionListBackgroundStyle());
sb.append(left);
}
if (right != null || hasRightItem) {
sb.style(getCompletionListBackgroundStyle());
for (int k = 0; k < maxWidth - lw - rw; k++) {
sb.append(' ');
}
}
if (right != null) {
sb.style(getCompletionStyleDescription());
sb.append(right);
sb.style(AttributedStyle.DEFAULT);
} else if (isMenuList) {
sb.style(getCompletionListBackgroundStyle());
for (int k = lw; k < maxWidth; k++) {
sb.append(' ');
}
}
}
sb.style(getCompletionListBackgroundStyle());
if (hasRightItem) {
for (int k = 0; k < MARGIN_BETWEEN_COLUMNS; k++) {
sb.append(' ');
}
}
if (isMenuList) {
sb.append(' ');
}
}
}
sb.append('\n');
Expand All @@ -5307,11 +5357,19 @@ else if (items instanceof List) {
}

private AttributedStyle getCompletionStyleStarting() {
return getCompletionStyle(COMPLETION_STYLE_STARTING, DEFAULT_COMPLETION_STYLE_STARTING);
String str = getString(COMPLETION_STYLE_STARTING, DEFAULT_COMPLETION_STYLE_STARTING);
if (str.isEmpty()) {
return buildStyle(getInt(COMPLETION_COLOR_STARTING, DEFAULT_COMPLETION_COLOR_STARTING));
}
return buildStyle(str);
}

protected AttributedStyle getCompletionStyleDescription() {
return getCompletionStyle(COMPLETION_STYLE_DESCRIPTION, DEFAULT_COMPLETION_STYLE_DESCRIPTION);
String str = getString(COMPLETION_STYLE_DESCRIPTION, DEFAULT_COMPLETION_STYLE_DESCRIPTION);
if (str.isEmpty()) {
return buildStyle(getInt(COMPLETION_COLOR_DESCRIPTION, DEFAULT_COMPLETION_COLOR_DESCRIPTION));
}
return buildStyle(str);
}

protected AttributedStyle getCompletionStyleGroup() {
Expand All @@ -5326,10 +5384,32 @@ protected AttributedStyle getCompletionStyle(String name, String value) {
return buildStyle(getString(name, value));
}

protected AttributedStyle getCompletionListBackgroundStyle() {
if (isSet(Option.AUTO_MENU_LIST)) {
return AttributedStyle.DEFAULT.background(getCompletionListBackgroundColor());
}
return AttributedStyle.DEFAULT.backgroundDefault();
}

protected int getCompletionListBackgroundColor() {
return getInt(COMPLETION_LIST_BACKGROUND_COLOR, DEFAULT_COMPLETION_LIST_BACKGROUND_COLOR);
}

protected AttributedStyle buildStyle(String str) {
if (isSet(Option.AUTO_MENU_LIST)) {
return AttributedString.fromAnsi("\u001b[" + str + "m ").styleAt(0)
.background(getCompletionListBackgroundColor());
}
return AttributedString.fromAnsi("\u001b[" + str + "m ").styleAt(0);
}

protected AttributedStyle buildStyle(int fg) {
if (isSet(Option.AUTO_MENU_LIST)) {
return AttributedStyle.DEFAULT.foreground(fg).background(getCompletionListBackgroundColor());
}
return AttributedStyle.DEFAULT.foreground(fg);
}

private String getCommonStart(String str1, String str2, boolean caseInsensitive) {
int[] s1 = str1.codePoints().toArray();
int[] s2 = str2.codePoints().toArray();
Expand Down

0 comments on commit 8053dc1

Please sign in to comment.