/*
 * 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; } }