/*
* Copyright (c) 2002-2016, 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 jdk.internal.org.jline.reader.impl;
import java.util.ListIterator;
import jdk.internal.org.jline.reader.Expander;
import jdk.internal.org.jline.reader.History;
import jdk.internal.org.jline.reader.History.Entry;
public class DefaultExpander implements Expander {
Expand event designator such as !!, !#, !3, etc...
See http://www.gnu.org/software/bash/manual/html_node/Event-Designators.html
/**
* Expand event designator such as !!, !#, !3, etc...
* See http://www.gnu.org/software/bash/manual/html_node/Event-Designators.html
*/
@SuppressWarnings("fallthrough")
@Override
public String expandHistory(History history, String line) {
boolean inQuote = false;
StringBuilder sb = new StringBuilder();
boolean escaped = false;
int unicode = 0;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (unicode > 0) {
escaped = (--unicode >= 0);
sb.append(c);
}
else if (escaped) {
if (c == 'u') {
unicode = 4;
} else {
escaped = false;
}
sb.append(c);
}
else if (c == '\'') {
inQuote = !inQuote;
sb.append(c);
}
else if (inQuote) {
sb.append(c);
}
else {
switch (c) {
case '\\':
// any '\!' should be considered an expansion escape, so skip expansion and strip the escape character
// a leading '\^' should be considered an expansion escape, so skip expansion and strip the escape character
// otherwise, add the escape
escaped = true;
sb.append(c);
break;
case '!':
if (i + 1 < line.length()) {
c = line.charAt(++i);
boolean neg = false;
String rep = null;
int i1, idx;
switch (c) {
case '!':
if (history.size() == 0) {
throw new IllegalArgumentException("!!: event not found");
}
rep = history.get(history.index() - 1);
break;
case '#':
sb.append(sb.toString());
break;
case '?':
i1 = line.indexOf('?', i + 1);
if (i1 < 0) {
i1 = line.length();
}
String sc = line.substring(i + 1, i1);
i = i1;
idx = searchBackwards(history, sc, history.index(), false);
if (idx < 0) {
throw new IllegalArgumentException("!?" + sc + ": event not found");
} else {
rep = history.get(idx);
}
break;
case '$':
if (history.size() == 0) {
throw new IllegalArgumentException("!$: event not found");
}
String previous = history.get(history.index() - 1).trim();
int lastSpace = previous.lastIndexOf(' ');
if (lastSpace != -1) {
rep = previous.substring(lastSpace + 1);
} else {
rep = previous;
}
break;
case ' ':
case '\t':
sb.append('!');
sb.append(c);
break;
case '-':
neg = true;
i++;
// fall through
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
i1 = i;
for (; i < line.length(); i++) {
c = line.charAt(i);
if (c < '0' || c > '9') {
break;
}
}
try {
idx = Integer.parseInt(line.substring(i1, i));
} catch (NumberFormatException e) {
throw new IllegalArgumentException((neg ? "!-" : "!") + line.substring(i1, i) + ": event not found");
}
if (neg && idx > 0 && idx <= history.size()) {
rep = history.get(history.index() - idx);
} else if (!neg && idx > history.index() - history.size() && idx <= history.index()) {
rep = history.get(idx - 1);
} else {
throw new IllegalArgumentException((neg ? "!-" : "!") + line.substring(i1, i) + ": event not found");
}
break;
default:
String ss = line.substring(i);
i = line.length();
idx = searchBackwards(history, ss, history.index(), true);
if (idx < 0) {
throw new IllegalArgumentException("!" + ss + ": event not found");
} else {
rep = history.get(idx);
}
break;
}
if (rep != null) {
sb.append(rep);
}
} else {
sb.append(c);
}
break;
case '^':
if (i == 0) {
int i1 = line.indexOf('^', i + 1);
int i2 = line.indexOf('^', i1 + 1);
if (i2 < 0) {
i2 = line.length();
}
if (i1 > 0 && i2 > 0) {
String s1 = line.substring(i + 1, i1);
String s2 = line.substring(i1 + 1, i2);
String s = history.get(history.index() - 1).replace(s1, s2);
sb.append(s);
i = i2 + 1;
break;
}
}
sb.append(c);
break;
default:
sb.append(c);
break;
}
}
}
return sb.toString();
}
@Override
public String expandVar(String word) {
return word;
}
protected int searchBackwards(History history, String searchTerm, int startIndex, boolean startsWith) {
ListIterator<Entry> it = history.iterator(startIndex);
while (it.hasPrevious()) {
History.Entry e = it.previous();
if (startsWith) {
if (e.line().startsWith(searchTerm)) {
return e.index();
}
} else {
if (e.line().contains(searchTerm)) {
return e.index();
}
}
}
return -1;
}
}