langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/EditingHistory.java
changeset 38521 f2fe39ab9256
parent 38520 17e72b872ffd
child 38522 c4fdb181cd64
equal deleted inserted replaced
38520:17e72b872ffd 38521:f2fe39ab9256
     1 /*
       
     2  * Copyright (c) 2015, 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.jshell.tool;
       
    27 
       
    28 import java.util.ArrayList;
       
    29 import java.util.HashSet;
       
    30 import java.util.Iterator;
       
    31 import java.util.List;
       
    32 import java.util.ListIterator;
       
    33 import java.util.Set;
       
    34 import java.util.prefs.BackingStoreException;
       
    35 import java.util.prefs.Preferences;
       
    36 import java.util.stream.Collectors;
       
    37 import java.util.stream.Stream;
       
    38 
       
    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 import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
       
    43 
       
    44 /*Public for tests (HistoryTest).
       
    45  */
       
    46 public abstract class EditingHistory implements History {
       
    47 
       
    48     private final Preferences prefs;
       
    49     private final History fullHistory;
       
    50     private History currentDelegate;
       
    51 
       
    52     protected EditingHistory(Preferences prefs) {
       
    53         this.prefs = prefs;
       
    54         this.fullHistory = new MemoryHistory();
       
    55         this.currentDelegate = fullHistory;
       
    56         load();
       
    57     }
       
    58 
       
    59     @Override
       
    60     public int size() {
       
    61         return currentDelegate.size();
       
    62     }
       
    63 
       
    64     @Override
       
    65     public boolean isEmpty() {
       
    66         return currentDelegate.isEmpty();
       
    67     }
       
    68 
       
    69     @Override
       
    70     public int index() {
       
    71         return currentDelegate.index();
       
    72     }
       
    73 
       
    74     @Override
       
    75     public void clear() {
       
    76         if (currentDelegate != fullHistory)
       
    77             throw new IllegalStateException("narrowed");
       
    78         currentDelegate.clear();
       
    79     }
       
    80 
       
    81     @Override
       
    82     public CharSequence get(int index) {
       
    83         return currentDelegate.get(index);
       
    84     }
       
    85 
       
    86     @Override
       
    87     public void add(CharSequence line) {
       
    88         NarrowingHistoryLine currentLine = null;
       
    89         int origIndex = fullHistory.index();
       
    90         int fullSize;
       
    91         try {
       
    92             fullHistory.moveToEnd();
       
    93             fullSize = fullHistory.index();
       
    94             if (currentDelegate == fullHistory) {
       
    95                 if (origIndex < fullHistory.index()) {
       
    96                     for (Entry entry : fullHistory) {
       
    97                         if (!(entry.value() instanceof NarrowingHistoryLine))
       
    98                             continue;
       
    99                         int[] cluster = ((NarrowingHistoryLine) entry.value()).span;
       
   100                         if (cluster[0] == origIndex && cluster[1] > cluster[0]) {
       
   101                             currentDelegate = new MemoryHistory();
       
   102                             for (int i = cluster[0]; i <= cluster[1]; i++) {
       
   103                                 currentDelegate.add(fullHistory.get(i));
       
   104                             }
       
   105                         }
       
   106                     }
       
   107                 }
       
   108             }
       
   109             fullHistory.moveToEnd();
       
   110             while (fullHistory.previous()) {
       
   111                 CharSequence c = fullHistory.current();
       
   112                 if (c instanceof NarrowingHistoryLine) {
       
   113                     currentLine = (NarrowingHistoryLine) c;
       
   114                     break;
       
   115                 }
       
   116             }
       
   117         } finally {
       
   118             fullHistory.moveTo(origIndex);
       
   119         }
       
   120         if (currentLine == null || currentLine.span[1] != (-1)) {
       
   121             line = currentLine = new NarrowingHistoryLine(line, fullSize);
       
   122         }
       
   123         StringBuilder complete = new StringBuilder();
       
   124         for (int i = currentLine.span[0]; i < fullSize; i++) {
       
   125             complete.append(fullHistory.get(i));
       
   126         }
       
   127         complete.append(line);
       
   128         if (analyzeCompletion(complete.toString()).completeness.isComplete) {
       
   129             currentLine.span[1] = fullSize; //TODO: +1?
       
   130             currentDelegate = fullHistory;
       
   131         }
       
   132         fullHistory.add(line);
       
   133     }
       
   134 
       
   135     protected abstract CompletionInfo analyzeCompletion(String input);
       
   136 
       
   137     @Override
       
   138     public void set(int index, CharSequence item) {
       
   139         if (currentDelegate != fullHistory)
       
   140             throw new IllegalStateException("narrowed");
       
   141         currentDelegate.set(index, item);
       
   142     }
       
   143 
       
   144     @Override
       
   145     public CharSequence remove(int i) {
       
   146         if (currentDelegate != fullHistory)
       
   147             throw new IllegalStateException("narrowed");
       
   148         return currentDelegate.remove(i);
       
   149     }
       
   150 
       
   151     @Override
       
   152     public CharSequence removeFirst() {
       
   153         if (currentDelegate != fullHistory)
       
   154             throw new IllegalStateException("narrowed");
       
   155         return currentDelegate.removeFirst();
       
   156     }
       
   157 
       
   158     @Override
       
   159     public CharSequence removeLast() {
       
   160         if (currentDelegate != fullHistory)
       
   161             throw new IllegalStateException("narrowed");
       
   162         return currentDelegate.removeLast();
       
   163     }
       
   164 
       
   165     @Override
       
   166     public void replace(CharSequence item) {
       
   167         if (currentDelegate != fullHistory)
       
   168             throw new IllegalStateException("narrowed");
       
   169         currentDelegate.replace(item);
       
   170     }
       
   171 
       
   172     @Override
       
   173     public ListIterator<Entry> entries(int index) {
       
   174         return currentDelegate.entries(index);
       
   175     }
       
   176 
       
   177     @Override
       
   178     public ListIterator<Entry> entries() {
       
   179         return currentDelegate.entries();
       
   180     }
       
   181 
       
   182     @Override
       
   183     public Iterator<Entry> iterator() {
       
   184         return currentDelegate.iterator();
       
   185     }
       
   186 
       
   187     @Override
       
   188     public CharSequence current() {
       
   189         return currentDelegate.current();
       
   190     }
       
   191 
       
   192     @Override
       
   193     public boolean previous() {
       
   194         return currentDelegate.previous();
       
   195     }
       
   196 
       
   197     @Override
       
   198     public boolean next() {
       
   199         return currentDelegate.next();
       
   200     }
       
   201 
       
   202     @Override
       
   203     public boolean moveToFirst() {
       
   204         return currentDelegate.moveToFirst();
       
   205     }
       
   206 
       
   207     @Override
       
   208     public boolean moveToLast() {
       
   209         return currentDelegate.moveToLast();
       
   210     }
       
   211 
       
   212     @Override
       
   213     public boolean moveTo(int index) {
       
   214         return currentDelegate.moveTo(index);
       
   215     }
       
   216 
       
   217     @Override
       
   218     public void moveToEnd() {
       
   219         currentDelegate.moveToEnd();
       
   220     }
       
   221 
       
   222     public boolean previousSnippet() {
       
   223         for (int i = index() - 1; i >= 0; i--) {
       
   224             if (get(i) instanceof NarrowingHistoryLine) {
       
   225                 moveTo(i);
       
   226                 return true;
       
   227             }
       
   228         }
       
   229 
       
   230         return false;
       
   231     }
       
   232 
       
   233     public boolean nextSnippet() {
       
   234         for (int i = index() + 1; i < size(); i++) {
       
   235             if (get(i) instanceof NarrowingHistoryLine) {
       
   236                 moveTo(i);
       
   237                 return true;
       
   238             }
       
   239         }
       
   240 
       
   241         if (index() < size()) {
       
   242             moveToEnd();
       
   243             return true;
       
   244         }
       
   245 
       
   246         return false;
       
   247     }
       
   248 
       
   249     private static final String HISTORY_LINE_PREFIX = "HISTORY_LINE_";
       
   250     private static final String HISTORY_SNIPPET_START = "HISTORY_SNIPPET";
       
   251 
       
   252     public final void load() {
       
   253         try {
       
   254             Set<Integer> snippetsStart = new HashSet<>();
       
   255             for (String start : prefs.get(HISTORY_SNIPPET_START, "").split(";")) {
       
   256                 if (!start.isEmpty())
       
   257                     snippetsStart.add(Integer.parseInt(start));
       
   258             }
       
   259             List<String> keys = Stream.of(prefs.keys()).sorted().collect(Collectors.toList());
       
   260             NarrowingHistoryLine currentHistoryLine = null;
       
   261             int currentLine = 0;
       
   262             for (String key : keys) {
       
   263                 if (!key.startsWith(HISTORY_LINE_PREFIX))
       
   264                     continue;
       
   265                 CharSequence line = prefs.get(key, "");
       
   266                 if (snippetsStart.contains(currentLine)) {
       
   267                     class PersistentNarrowingHistoryLine extends NarrowingHistoryLine implements PersistentEntryMarker {
       
   268                         public PersistentNarrowingHistoryLine(CharSequence delegate, int start) {
       
   269                             super(delegate, start);
       
   270                         }
       
   271                     }
       
   272                     line = currentHistoryLine = new PersistentNarrowingHistoryLine(line, currentLine);
       
   273                 } else {
       
   274                     class PersistentLine implements CharSequence, PersistentEntryMarker {
       
   275                         private final CharSequence delegate;
       
   276                         public PersistentLine(CharSequence delegate) {
       
   277                             this.delegate = delegate;
       
   278                         }
       
   279                         @Override public int length() {
       
   280                             return delegate.length();
       
   281                         }
       
   282                         @Override public char charAt(int index) {
       
   283                             return delegate.charAt(index);
       
   284                         }
       
   285                         @Override public CharSequence subSequence(int start, int end) {
       
   286                             return delegate.subSequence(start, end);
       
   287                         }
       
   288                         @Override public String toString() {
       
   289                             return delegate.toString();
       
   290                         }
       
   291                     }
       
   292                     line = new PersistentLine(line);
       
   293                 }
       
   294                 if (currentHistoryLine != null)
       
   295                     currentHistoryLine.span[1] = currentLine;
       
   296                 currentLine++;
       
   297                 fullHistory.add(line);
       
   298             }
       
   299             currentLine = 0;
       
   300         } catch (BackingStoreException ex) {
       
   301             throw new IllegalStateException(ex);
       
   302         }
       
   303     }
       
   304 
       
   305     public void save() {
       
   306         try {
       
   307             for (String key : prefs.keys()) {
       
   308                 if (key.startsWith(HISTORY_LINE_PREFIX))
       
   309                     prefs.remove(key);
       
   310             }
       
   311             Iterator<Entry> entries = fullHistory.iterator();
       
   312             if (entries.hasNext()) {
       
   313                 int len = (int) Math.ceil(Math.log10(fullHistory.size()+1));
       
   314                 String format = HISTORY_LINE_PREFIX + "%0" + len + "d";
       
   315                 StringBuilder snippetStarts = new StringBuilder();
       
   316                 String snippetStartDelimiter = "";
       
   317                 while (entries.hasNext()) {
       
   318                     Entry entry = entries.next();
       
   319                     prefs.put(String.format(format, entry.index()), entry.value().toString());
       
   320                     if (entry.value() instanceof NarrowingHistoryLine) {
       
   321                         snippetStarts.append(snippetStartDelimiter);
       
   322                         snippetStarts.append(entry.index());
       
   323                         snippetStartDelimiter = ";";
       
   324                     }
       
   325                 }
       
   326                 prefs.put(HISTORY_SNIPPET_START, snippetStarts.toString());
       
   327             }
       
   328         } catch (BackingStoreException ex) {
       
   329             throw new IllegalStateException(ex);
       
   330         }
       
   331     }
       
   332 
       
   333     public List<String> currentSessionEntries() {
       
   334         List<String> result = new ArrayList<>();
       
   335 
       
   336         for (Entry e : fullHistory) {
       
   337             if (!(e.value() instanceof PersistentEntryMarker)) {
       
   338                 result.add(e.value().toString());
       
   339             }
       
   340         }
       
   341 
       
   342         return result;
       
   343     }
       
   344 
       
   345     void fullHistoryReplace(String source) {
       
   346         fullHistory.replace(source);
       
   347     }
       
   348 
       
   349     private class NarrowingHistoryLine implements CharSequence {
       
   350         private final CharSequence delegate;
       
   351         private final int[] span;
       
   352 
       
   353         public NarrowingHistoryLine(CharSequence delegate, int start) {
       
   354             this.delegate = delegate;
       
   355             this.span = new int[] {start, -1};
       
   356         }
       
   357 
       
   358         @Override
       
   359         public int length() {
       
   360             return delegate.length();
       
   361         }
       
   362 
       
   363         @Override
       
   364         public char charAt(int index) {
       
   365             return delegate.charAt(index);
       
   366         }
       
   367 
       
   368         @Override
       
   369         public CharSequence subSequence(int start, int end) {
       
   370             return delegate.subSequence(start, end);
       
   371         }
       
   372 
       
   373         @Override
       
   374         public String toString() {
       
   375             return delegate.toString();
       
   376         }
       
   377 
       
   378     }
       
   379 
       
   380     private interface PersistentEntryMarker {}
       
   381 }
       
   382