src/jdk.internal.le/share/classes/jdk/internal/jline/extra/EditingHistory.java
branchJDK-8200758-branch
changeset 57072 29604aafa0fc
parent 57071 94e9270166f0
parent 52979 7384e00d5860
child 57076 687505381ca4
equal deleted inserted replaced
57071:94e9270166f0 57072:29604aafa0fc
     1 /*
       
     2  * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package jdk.internal.jline.extra;
       
    27 
       
    28 import java.io.IOException;
       
    29 import java.lang.reflect.Method;
       
    30 import java.util.ArrayList;
       
    31 import java.util.Collection;
       
    32 import java.util.Iterator;
       
    33 import java.util.List;
       
    34 import java.util.ListIterator;
       
    35 import java.util.function.Supplier;
       
    36 
       
    37 import jdk.internal.jline.console.ConsoleReader;
       
    38 import jdk.internal.jline.console.KeyMap;
       
    39 import jdk.internal.jline.console.history.History;
       
    40 import jdk.internal.jline.console.history.History.Entry;
       
    41 import jdk.internal.jline.console.history.MemoryHistory;
       
    42 
       
    43 /*Public for tests (HistoryTest).
       
    44  */
       
    45 public abstract class EditingHistory implements History {
       
    46 
       
    47     private final History fullHistory;
       
    48     private History currentDelegate;
       
    49 
       
    50     protected EditingHistory(ConsoleReader in, Iterable<? extends String> originalHistory) {
       
    51         MemoryHistory fullHistory = new MemoryHistory();
       
    52         fullHistory.setIgnoreDuplicates(false);
       
    53         this.fullHistory = fullHistory;
       
    54         this.currentDelegate = fullHistory;
       
    55         bind(in, CTRL_UP,
       
    56              (Runnable) () -> moveHistoryToSnippet(in, ((EditingHistory) in.getHistory())::previousSnippet));
       
    57         bind(in, CTRL_DOWN,
       
    58              (Runnable) () -> moveHistoryToSnippet(in, ((EditingHistory) in.getHistory())::nextSnippet));
       
    59         if (originalHistory != null) {
       
    60             load(originalHistory);
       
    61         }
       
    62     }
       
    63 
       
    64     private void moveHistoryToSnippet(ConsoleReader in, Supplier<Boolean> action) {
       
    65         if (!action.get()) {
       
    66             try {
       
    67                 in.beep();
       
    68             } catch (IOException ex) {
       
    69                 throw new IllegalStateException(ex);
       
    70             }
       
    71         } else {
       
    72             try {
       
    73                 //could use:
       
    74                 //in.resetPromptLine(in.getPrompt(), in.getHistory().current().toString(), -1);
       
    75                 //but that would mean more re-writing on the screen, (and prints an additional
       
    76                 //empty line), so using setBuffer directly:
       
    77                 Method setBuffer = ConsoleReader.class.getDeclaredMethod("setBuffer", String.class);
       
    78 
       
    79                 setBuffer.setAccessible(true);
       
    80                 setBuffer.invoke(in, in.getHistory().current().toString());
       
    81                 in.flush();
       
    82             } catch (ReflectiveOperationException | IOException ex) {
       
    83                 throw new IllegalStateException(ex);
       
    84             }
       
    85         }
       
    86     }
       
    87 
       
    88     private void bind(ConsoleReader in, String shortcut, Object action) {
       
    89         KeyMap km = in.getKeys();
       
    90         for (int i = 0; i < shortcut.length(); i++) {
       
    91             Object value = km.getBound(Character.toString(shortcut.charAt(i)));
       
    92             if (value instanceof KeyMap) {
       
    93                 km = (KeyMap) value;
       
    94             } else {
       
    95                 km.bind(shortcut.substring(i), action);
       
    96             }
       
    97         }
       
    98     }
       
    99 
       
   100     private static final String CTRL_UP = "\033\133\061\073\065\101"; //Ctrl-UP
       
   101     private static final String CTRL_DOWN = "\033\133\061\073\065\102"; //Ctrl-DOWN
       
   102 
       
   103     @Override
       
   104     public int size() {
       
   105         return currentDelegate.size();
       
   106     }
       
   107 
       
   108     @Override
       
   109     public boolean isEmpty() {
       
   110         return currentDelegate.isEmpty();
       
   111     }
       
   112 
       
   113     @Override
       
   114     public int index() {
       
   115         return currentDelegate.index();
       
   116     }
       
   117 
       
   118     @Override
       
   119     public void clear() {
       
   120         if (currentDelegate != fullHistory)
       
   121             throw new IllegalStateException("narrowed");
       
   122         currentDelegate.clear();
       
   123     }
       
   124 
       
   125     @Override
       
   126     public CharSequence get(int index) {
       
   127         return currentDelegate.get(index);
       
   128     }
       
   129 
       
   130     @Override
       
   131     public void add(CharSequence line) {
       
   132         NarrowingHistoryLine currentLine = null;
       
   133         int origIndex = fullHistory.index();
       
   134         int fullSize;
       
   135         try {
       
   136             fullHistory.moveToEnd();
       
   137             fullSize = fullHistory.index();
       
   138             if (currentDelegate == fullHistory) {
       
   139                 if (origIndex < fullHistory.index()) {
       
   140                     for (Entry entry : fullHistory) {
       
   141                         if (!(entry.value() instanceof NarrowingHistoryLine))
       
   142                             continue;
       
   143                         int[] cluster = ((NarrowingHistoryLine) entry.value()).span;
       
   144                         if (cluster[0] == origIndex && cluster[1] > cluster[0]) {
       
   145                             currentDelegate = new MemoryHistory();
       
   146                             for (int i = cluster[0]; i <= cluster[1]; i++) {
       
   147                                 currentDelegate.add(fullHistory.get(i));
       
   148                             }
       
   149                         }
       
   150                     }
       
   151                 }
       
   152             }
       
   153             fullHistory.moveToEnd();
       
   154             while (fullHistory.previous()) {
       
   155                 CharSequence c = fullHistory.current();
       
   156                 if (c instanceof NarrowingHistoryLine) {
       
   157                     currentLine = (NarrowingHistoryLine) c;
       
   158                     break;
       
   159                 }
       
   160             }
       
   161         } finally {
       
   162             fullHistory.moveTo(origIndex);
       
   163         }
       
   164         if (currentLine == null || currentLine.span[1] != (-1)) {
       
   165             line = currentLine = new NarrowingHistoryLine(line, fullSize);
       
   166         }
       
   167         StringBuilder complete = new StringBuilder();
       
   168         for (int i = currentLine.span[0]; i < fullSize; i++) {
       
   169             complete.append(fullHistory.get(i));
       
   170         }
       
   171         complete.append(line);
       
   172         if (isComplete(complete)) {
       
   173             currentLine.span[1] = fullSize; //TODO: +1?
       
   174             currentDelegate = fullHistory;
       
   175         }
       
   176         fullHistory.add(line);
       
   177     }
       
   178 
       
   179     protected abstract boolean isComplete(CharSequence input);
       
   180 
       
   181     @Override
       
   182     public void set(int index, CharSequence item) {
       
   183         if (currentDelegate != fullHistory)
       
   184             throw new IllegalStateException("narrowed");
       
   185         currentDelegate.set(index, item);
       
   186     }
       
   187 
       
   188     @Override
       
   189     public CharSequence remove(int i) {
       
   190         if (currentDelegate != fullHistory)
       
   191             throw new IllegalStateException("narrowed");
       
   192         return currentDelegate.remove(i);
       
   193     }
       
   194 
       
   195     @Override
       
   196     public CharSequence removeFirst() {
       
   197         if (currentDelegate != fullHistory)
       
   198             throw new IllegalStateException("narrowed");
       
   199         return currentDelegate.removeFirst();
       
   200     }
       
   201 
       
   202     @Override
       
   203     public CharSequence removeLast() {
       
   204         if (currentDelegate != fullHistory)
       
   205             throw new IllegalStateException("narrowed");
       
   206         return currentDelegate.removeLast();
       
   207     }
       
   208 
       
   209     @Override
       
   210     public void replace(CharSequence item) {
       
   211         if (currentDelegate != fullHistory)
       
   212             throw new IllegalStateException("narrowed");
       
   213         currentDelegate.replace(item);
       
   214     }
       
   215 
       
   216     @Override
       
   217     public ListIterator<Entry> entries(int index) {
       
   218         return currentDelegate.entries(index);
       
   219     }
       
   220 
       
   221     @Override
       
   222     public ListIterator<Entry> entries() {
       
   223         return currentDelegate.entries();
       
   224     }
       
   225 
       
   226     @Override
       
   227     public Iterator<Entry> iterator() {
       
   228         return currentDelegate.iterator();
       
   229     }
       
   230 
       
   231     @Override
       
   232     public CharSequence current() {
       
   233         return currentDelegate.current();
       
   234     }
       
   235 
       
   236     @Override
       
   237     public boolean previous() {
       
   238         return currentDelegate.previous();
       
   239     }
       
   240 
       
   241     @Override
       
   242     public boolean next() {
       
   243         return currentDelegate.next();
       
   244     }
       
   245 
       
   246     @Override
       
   247     public boolean moveToFirst() {
       
   248         return currentDelegate.moveToFirst();
       
   249     }
       
   250 
       
   251     @Override
       
   252     public boolean moveToLast() {
       
   253         return currentDelegate.moveToLast();
       
   254     }
       
   255 
       
   256     @Override
       
   257     public boolean moveTo(int index) {
       
   258         return currentDelegate.moveTo(index);
       
   259     }
       
   260 
       
   261     @Override
       
   262     public void moveToEnd() {
       
   263         currentDelegate.moveToEnd();
       
   264     }
       
   265 
       
   266     public boolean previousSnippet() {
       
   267         while (previous()) {
       
   268             if (current() instanceof NarrowingHistoryLine) {
       
   269                 return true;
       
   270             }
       
   271         }
       
   272 
       
   273         return false;
       
   274     }
       
   275 
       
   276     public boolean nextSnippet() {
       
   277         boolean success = false;
       
   278 
       
   279         while (next()) {
       
   280             success = true;
       
   281 
       
   282             if (current() instanceof NarrowingHistoryLine) {
       
   283                 return true;
       
   284             }
       
   285         }
       
   286 
       
   287         return success;
       
   288     }
       
   289 
       
   290     public final void load(Iterable<? extends String> originalHistory) {
       
   291         NarrowingHistoryLine currentHistoryLine = null;
       
   292         boolean start = true;
       
   293         int currentLine = 0;
       
   294         for (String historyItem : originalHistory) {
       
   295             StringBuilder line = new StringBuilder(historyItem);
       
   296             int trailingBackSlashes = countTrailintBackslashes(line);
       
   297             boolean continuation = trailingBackSlashes % 2 != 0;
       
   298             line.delete(line.length() - trailingBackSlashes / 2 - (continuation ? 1 : 0), line.length());
       
   299             if (start) {
       
   300                 class PersistentNarrowingHistoryLine extends NarrowingHistoryLine implements PersistentEntryMarker {
       
   301                     public PersistentNarrowingHistoryLine(CharSequence delegate, int start) {
       
   302                         super(delegate, start);
       
   303                     }
       
   304                 }
       
   305                 fullHistory.add(currentHistoryLine = new PersistentNarrowingHistoryLine(line, currentLine));
       
   306             } else {
       
   307                 class PersistentLine implements CharSequence, PersistentEntryMarker {
       
   308                     private final CharSequence delegate;
       
   309                     public PersistentLine(CharSequence delegate) {
       
   310                         this.delegate = delegate;
       
   311                     }
       
   312                     @Override public int length() {
       
   313                         return delegate.length();
       
   314                     }
       
   315                     @Override public char charAt(int index) {
       
   316                         return delegate.charAt(index);
       
   317                     }
       
   318                     @Override public CharSequence subSequence(int start, int end) {
       
   319                         return delegate.subSequence(start, end);
       
   320                     }
       
   321                     @Override public String toString() {
       
   322                         return delegate.toString();
       
   323                     }
       
   324                 }
       
   325                 fullHistory.add(new PersistentLine(line));
       
   326             }
       
   327             start = !continuation;
       
   328             currentHistoryLine.span[1] = currentLine;
       
   329             currentLine++;
       
   330         }
       
   331     }
       
   332 
       
   333     public Collection<? extends String> save() {
       
   334         Collection<String> result = new ArrayList<>();
       
   335         Iterator<Entry> entries = fullHistory.iterator();
       
   336 
       
   337         if (entries.hasNext()) {
       
   338             Entry entry = entries.next();
       
   339             while (entry != null) {
       
   340                 StringBuilder historyLine = new StringBuilder(entry.value());
       
   341                 int trailingBackSlashes = countTrailintBackslashes(historyLine);
       
   342                 for (int i = 0; i < trailingBackSlashes; i++) {
       
   343                     historyLine.append("\\");
       
   344                 }
       
   345                 entry = entries.hasNext() ? entries.next() : null;
       
   346                 if (entry != null && !(entry.value() instanceof NarrowingHistoryLine)) {
       
   347                     historyLine.append("\\");
       
   348                 }
       
   349                 result.add(historyLine.toString());
       
   350             }
       
   351         }
       
   352 
       
   353         return result;
       
   354     }
       
   355 
       
   356     private int countTrailintBackslashes(CharSequence text) {
       
   357         int count = 0;
       
   358 
       
   359         for (int i = text.length() - 1; i >= 0; i--) {
       
   360             if (text.charAt(i) == '\\') {
       
   361                 count++;
       
   362             } else {
       
   363                 break;
       
   364             }
       
   365         }
       
   366 
       
   367         return count;
       
   368     }
       
   369 
       
   370     public List<String> entries(boolean currentSession) {
       
   371         List<String> result = new ArrayList<>();
       
   372 
       
   373         for (Entry e : fullHistory) {
       
   374             if (!currentSession || !(e.value() instanceof PersistentEntryMarker)) {
       
   375                 result.add(e.value().toString());
       
   376             }
       
   377         }
       
   378 
       
   379         return result;
       
   380     }
       
   381 
       
   382     public void fullHistoryReplace(String source) {
       
   383         fullHistory.removeLast();
       
   384         for (String line : source.split("\\R")) {
       
   385             fullHistory.add(line);
       
   386         }
       
   387     }
       
   388 
       
   389     private class NarrowingHistoryLine implements CharSequence {
       
   390         private final CharSequence delegate;
       
   391         private final int[] span;
       
   392 
       
   393         public NarrowingHistoryLine(CharSequence delegate, int start) {
       
   394             this.delegate = delegate;
       
   395             this.span = new int[] {start, -1};
       
   396         }
       
   397 
       
   398         @Override
       
   399         public int length() {
       
   400             return delegate.length();
       
   401         }
       
   402 
       
   403         @Override
       
   404         public char charAt(int index) {
       
   405             return delegate.charAt(index);
       
   406         }
       
   407 
       
   408         @Override
       
   409         public CharSequence subSequence(int start, int end) {
       
   410             return delegate.subSequence(start, end);
       
   411         }
       
   412 
       
   413         @Override
       
   414         public String toString() {
       
   415             return delegate.toString();
       
   416         }
       
   417 
       
   418     }
       
   419 
       
   420     private interface PersistentEntryMarker {}
       
   421 }
       
   422