< prev index next >

src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java

Print this page




   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 jdk.jshell.SourceCodeAnalysis.CompletionInfo;

  29 import jdk.jshell.SourceCodeAnalysis.Suggestion;
  30 
  31 import java.awt.event.ActionListener;
  32 import java.io.IOException;
  33 import java.io.InputStream;
  34 import java.io.PrintStream;
  35 import java.io.UncheckedIOException;
  36 import java.lang.reflect.Method;



  37 import java.util.List;
  38 import java.util.Locale;

  39 import java.util.Objects;
  40 import java.util.Optional;
  41 import java.util.function.Supplier;
  42 
  43 import jdk.internal.jline.NoInterruptUnixTerminal;
  44 import jdk.internal.jline.Terminal;
  45 import jdk.internal.jline.TerminalFactory;
  46 import jdk.internal.jline.WindowsTerminal;
  47 import jdk.internal.jline.console.ConsoleReader;
  48 import jdk.internal.jline.console.KeyMap;
  49 import jdk.internal.jline.console.UserInterruptException;
  50 import jdk.internal.jline.console.completer.Completer;
  51 import jdk.internal.jshell.tool.StopDetectingInputStream.State;
  52 
  53 class ConsoleIOContext extends IOContext {
  54 
  55     final JShellTool repl;
  56     final StopDetectingInputStream input;
  57     final ConsoleReader in;
  58     final EditingHistory history;


 127                     result.add("<press tab to see more>");
 128                     return cursor; //anchor should not be used.
 129                 }
 130 
 131                 if (result.isEmpty()) {
 132                     try {
 133                         //provide "empty completion" feedback
 134                         //XXX: this only works correctly when there is only one Completer:
 135                         in.beep();
 136                     } catch (IOException ex) {
 137                         throw new UncheckedIOException(ex);
 138                     }
 139                 }
 140 
 141                 return anchor[0];
 142             }
 143         });
 144         bind(DOCUMENTATION_SHORTCUT, (ActionListener) evt -> documentation(repl));
 145         bind(CTRL_UP, (ActionListener) evt -> moveHistoryToSnippet(((EditingHistory) in.getHistory())::previousSnippet));
 146         bind(CTRL_DOWN, (ActionListener) evt -> moveHistoryToSnippet(((EditingHistory) in.getHistory())::nextSnippet));





 147     }
 148 
 149     @Override
 150     public String readLine(String prompt, String prefix) throws IOException, InputInterruptedException {
 151         this.prefix = prefix;
 152         try {
 153             return in.readLine(prompt);
 154         } catch (UserInterruptException ex) {
 155             throw (InputInterruptedException) new InputInterruptedException().initCause(ex);
 156         }
 157     }
 158 
 159     @Override
 160     public boolean interactiveOutput() {
 161         return true;
 162     }
 163 
 164     @Override
 165     public Iterable<String> currentSessionHistory() {
 166         return history.currentSessionEntries();


 199                 throw new IllegalStateException(ex);
 200             }
 201         }
 202     }
 203 
 204     private void bind(String shortcut, Object action) {
 205         KeyMap km = in.getKeys();
 206         for (int i = 0; i < shortcut.length(); i++) {
 207             Object value = km.getBound(Character.toString(shortcut.charAt(i)));
 208             if (value instanceof KeyMap) {
 209                 km = (KeyMap) value;
 210             } else {
 211                 km.bind(shortcut.substring(i), action);
 212             }
 213         }
 214     }
 215 
 216     private static final String DOCUMENTATION_SHORTCUT = "\033\133\132"; //Shift-TAB
 217     private static final String CTRL_UP = "\033\133\061\073\065\101"; //Ctrl-UP
 218     private static final String CTRL_DOWN = "\033\133\061\073\065\102"; //Ctrl-DOWN





 219 
 220     private void documentation(JShellTool repl) {
 221         String buffer = in.getCursorBuffer().buffer.toString();
 222         int cursor = in.getCursorBuffer().cursor;
 223         String doc;
 224         if (prefix.isEmpty() && buffer.trim().startsWith("/")) {
 225             doc = repl.commandDocumentation(buffer, cursor);
 226         } else {
 227             doc = repl.analysis.documentation(prefix + buffer, cursor + prefix.length());
 228         }
 229 
 230         try {
 231             if (doc != null) {
 232                 in.println();
 233                 in.println(doc);
 234                 in.redrawLine();
 235                 in.flush();
 236             } else {
 237                 in.beep();
 238             }


 273         try {
 274             in.getTerminal().init();
 275         } catch (Exception ex) {
 276             throw new IllegalStateException(ex);
 277         }
 278     }
 279 
 280     public void beforeUserCode() {
 281         input.setState(State.BUFFER);
 282     }
 283 
 284     public void afterUserCode() {
 285         input.setState(State.WAIT);
 286     }
 287 
 288     @Override
 289     public void replaceLastHistoryEntry(String source) {
 290         history.fullHistoryReplace(source);
 291     }
 292 



























































































































































 293     private static final class JShellUnixTerminal extends NoInterruptUnixTerminal {
 294 
 295         private final StopDetectingInputStream input;
 296 
 297         public JShellUnixTerminal(StopDetectingInputStream input) throws Exception {
 298             this.input = input;
 299         }
 300 
 301         public boolean isRaw() {
 302             try {
 303                 return getSettings().get("-a").contains("-icanon");
 304             } catch (IOException | InterruptedException ex) {
 305                 return false;
 306             }
 307         }
 308 
 309         @Override
 310         public InputStream wrapInIfNeeded(InputStream in) throws IOException {
 311             return input.setInputStream(super.wrapInIfNeeded(in));
 312         }




   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 jdk.jshell.SourceCodeAnalysis.CompletionInfo;
  29 import jdk.jshell.SourceCodeAnalysis.IndexResult;
  30 import jdk.jshell.SourceCodeAnalysis.Suggestion;
  31 
  32 import java.awt.event.ActionListener;
  33 import java.io.IOException;
  34 import java.io.InputStream;
  35 import java.io.PrintStream;
  36 import java.io.UncheckedIOException;
  37 import java.lang.reflect.Method;
  38 import java.util.ArrayList;
  39 import java.util.Collections;
  40 import java.util.HashMap;
  41 import java.util.List;
  42 import java.util.Locale;
  43 import java.util.Map;
  44 import java.util.Objects;
  45 import java.util.Optional;
  46 import java.util.function.Supplier;
  47 
  48 import jdk.internal.jline.NoInterruptUnixTerminal;
  49 import jdk.internal.jline.Terminal;
  50 import jdk.internal.jline.TerminalFactory;
  51 import jdk.internal.jline.WindowsTerminal;
  52 import jdk.internal.jline.console.ConsoleReader;
  53 import jdk.internal.jline.console.KeyMap;
  54 import jdk.internal.jline.console.UserInterruptException;
  55 import jdk.internal.jline.console.completer.Completer;
  56 import jdk.internal.jshell.tool.StopDetectingInputStream.State;
  57 
  58 class ConsoleIOContext extends IOContext {
  59 
  60     final JShellTool repl;
  61     final StopDetectingInputStream input;
  62     final ConsoleReader in;
  63     final EditingHistory history;


 132                     result.add("<press tab to see more>");
 133                     return cursor; //anchor should not be used.
 134                 }
 135 
 136                 if (result.isEmpty()) {
 137                     try {
 138                         //provide "empty completion" feedback
 139                         //XXX: this only works correctly when there is only one Completer:
 140                         in.beep();
 141                     } catch (IOException ex) {
 142                         throw new UncheckedIOException(ex);
 143                     }
 144                 }
 145 
 146                 return anchor[0];
 147             }
 148         });
 149         bind(DOCUMENTATION_SHORTCUT, (ActionListener) evt -> documentation(repl));
 150         bind(CTRL_UP, (ActionListener) evt -> moveHistoryToSnippet(((EditingHistory) in.getHistory())::previousSnippet));
 151         bind(CTRL_DOWN, (ActionListener) evt -> moveHistoryToSnippet(((EditingHistory) in.getHistory())::nextSnippet));
 152         for (FixComputer computer : fixComputers) {
 153             for (String shortcuts : SHORTCUT_FIXES) {
 154                 bind(shortcuts + computer.shortcut, (ActionListener) evt -> fixes(computer));
 155             }
 156         }
 157     }
 158 
 159     @Override
 160     public String readLine(String prompt, String prefix) throws IOException, InputInterruptedException {
 161         this.prefix = prefix;
 162         try {
 163             return in.readLine(prompt);
 164         } catch (UserInterruptException ex) {
 165             throw (InputInterruptedException) new InputInterruptedException().initCause(ex);
 166         }
 167     }
 168 
 169     @Override
 170     public boolean interactiveOutput() {
 171         return true;
 172     }
 173 
 174     @Override
 175     public Iterable<String> currentSessionHistory() {
 176         return history.currentSessionEntries();


 209                 throw new IllegalStateException(ex);
 210             }
 211         }
 212     }
 213 
 214     private void bind(String shortcut, Object action) {
 215         KeyMap km = in.getKeys();
 216         for (int i = 0; i < shortcut.length(); i++) {
 217             Object value = km.getBound(Character.toString(shortcut.charAt(i)));
 218             if (value instanceof KeyMap) {
 219                 km = (KeyMap) value;
 220             } else {
 221                 km.bind(shortcut.substring(i), action);
 222             }
 223         }
 224     }
 225 
 226     private static final String DOCUMENTATION_SHORTCUT = "\033\133\132"; //Shift-TAB
 227     private static final String CTRL_UP = "\033\133\061\073\065\101"; //Ctrl-UP
 228     private static final String CTRL_DOWN = "\033\133\061\073\065\102"; //Ctrl-DOWN
 229     private static final String[] SHORTCUT_FIXES = {
 230         "\033\015", //Alt-Enter (Linux)
 231         "\033\133\061\067\176", //F6/Alt-F1 (Mac)
 232         "\u001BO3P" //Alt-F1 (Linux)
 233     };
 234 
 235     private void documentation(JShellTool repl) {
 236         String buffer = in.getCursorBuffer().buffer.toString();
 237         int cursor = in.getCursorBuffer().cursor;
 238         String doc;
 239         if (prefix.isEmpty() && buffer.trim().startsWith("/")) {
 240             doc = repl.commandDocumentation(buffer, cursor);
 241         } else {
 242             doc = repl.analysis.documentation(prefix + buffer, cursor + prefix.length());
 243         }
 244 
 245         try {
 246             if (doc != null) {
 247                 in.println();
 248                 in.println(doc);
 249                 in.redrawLine();
 250                 in.flush();
 251             } else {
 252                 in.beep();
 253             }


 288         try {
 289             in.getTerminal().init();
 290         } catch (Exception ex) {
 291             throw new IllegalStateException(ex);
 292         }
 293     }
 294 
 295     public void beforeUserCode() {
 296         input.setState(State.BUFFER);
 297     }
 298 
 299     public void afterUserCode() {
 300         input.setState(State.WAIT);
 301     }
 302 
 303     @Override
 304     public void replaceLastHistoryEntry(String source) {
 305         history.fullHistoryReplace(source);
 306     }
 307 
 308     private void fixes(FixComputer computer) {
 309         String input = prefix + in.getCursorBuffer().toString();
 310         int cursor = prefix.length() + in.getCursorBuffer().cursor;
 311         FixResult candidates = computer.compute(repl, input, cursor);
 312 
 313         try {
 314             final boolean printError = candidates.error != null && !candidates.error.isEmpty();
 315             if (printError) {
 316                 in.println(candidates.error);
 317             }
 318             if (candidates.fixes.isEmpty()) {
 319                 in.beep();
 320                 if (printError) {
 321                     in.redrawLine();
 322                     in.flush();
 323                 }
 324             } else if (candidates.fixes.size() == 1 && !computer.showMenu) {
 325                 if (printError) {
 326                     in.redrawLine();
 327                     in.flush();
 328                 }
 329                 candidates.fixes.get(0).perform(in);
 330             } else {
 331                 List<Fix> fixes = new ArrayList<>(candidates.fixes);
 332                 fixes.add(0, new Fix() {
 333                     @Override
 334                     public String displayName() {
 335                         return "Do nothing";
 336                     }
 337 
 338                     @Override
 339                     public void perform(ConsoleReader in) throws IOException {
 340                         in.redrawLine();
 341                     }
 342                 });
 343 
 344                 Map<Character, Fix> char2Fix = new HashMap<>();
 345                 in.println();
 346                 for (int i = 0; i < fixes.size(); i++) {
 347                     Fix fix = fixes.get(i);
 348                     char2Fix.put((char) ('0' + i), fix);
 349                     in.println("" + i + ": " + fixes.get(i).displayName());
 350                 }
 351                 char2Fix.put((char) 3, fixes.get(0)); //Ctrl-C
 352                 in.print("Choice: ");
 353                 in.flush();
 354                 int read;
 355 
 356                 while (true) {
 357                     read = in.readCharacter();
 358 
 359                     Fix fix = char2Fix.get((char) read);
 360 
 361                     if (fix != null) {
 362                         in.println();
 363                         
 364                         fix.perform(in);
 365 
 366                         in.flush();
 367                         break;
 368                     }
 369                 }
 370             }
 371         } catch (IOException ex) {
 372             ex.printStackTrace();
 373         }
 374     }
 375 
 376     public interface Fix {
 377         public String displayName();
 378         public void perform(ConsoleReader in) throws IOException;
 379     }
 380 
 381     public abstract static class FixComputer {
 382         private final char shortcut;
 383         private final boolean showMenu;
 384 
 385         public FixComputer(char shortcut, boolean showMenu) {
 386             this.shortcut = shortcut;
 387             this.showMenu = showMenu;
 388         }
 389         
 390         public abstract FixResult compute(JShellTool repl, String code, int cursor);
 391     }
 392     
 393     public static class FixResult {
 394         public final List<Fix> fixes;
 395         public final String error;
 396 
 397         public FixResult(List<Fix> fixes, String error) {
 398             this.fixes = fixes;
 399             this.error = error;
 400         }
 401     }
 402 
 403     private static final FixComputer[] fixComputers = new FixComputer[] {
 404         new FixComputer('v', false) {
 405             @Override
 406             public FixResult compute(JShellTool repl, String code, int cursor) {
 407                 String type = repl.analysis.analyzeType(code, cursor);
 408                 if (type == null) {
 409                     return new FixResult(Collections.emptyList(), null);
 410                 }
 411                 return new FixResult(Collections.singletonList(new Fix() {
 412                     @Override
 413                     public String displayName() {
 414                         return "Create variable";
 415                     }
 416                     @Override
 417                     public void perform(ConsoleReader in) throws IOException {
 418                         in.redrawLine();
 419                         in.setCursorPosition(0);
 420                         in.putString(type + "  = ");
 421                         in.setCursorPosition(in.getCursorBuffer().cursor - 3);
 422                         in.flush();
 423                     }
 424                 }), null);
 425             }
 426         },
 427         new FixComputer('i', true) {
 428             @Override
 429             public FixResult compute(JShellTool repl, String code, int cursor) {
 430                 IndexResult res = repl.analysis.getDeclaredSymbols(code, cursor);
 431                 List<Fix> fixes = new ArrayList<>();
 432                 for (String fqn : res.getFqns()) {
 433                     fixes.add(new Fix() {
 434                         @Override
 435                         public String displayName() {
 436                             return "import: " + fqn;
 437                         }
 438                         @Override
 439                         public void perform(ConsoleReader in) throws IOException {
 440                             repl.state.eval("import " + fqn + ";");
 441                             in.println("Imported: " + fqn);
 442                             in.redrawLine();
 443                         }
 444                     });
 445                 }
 446                 if (res.isResolvable()) {
 447                     return new FixResult(Collections.emptyList(),
 448                                          "\nThe identifier is resolvable in this context.");
 449                 } else {
 450                     String error = "";
 451                     if (fixes.isEmpty()) {
 452                         error = "\nNo candidate FQNs found to import.";
 453                     }
 454                     if (!res.isUpToDate()) {
 455                         error += "\nResults may be incomplete; try again later for complete results.";
 456                     }
 457                     return new FixResult(fixes, error);
 458                 }
 459             }
 460         }
 461     };
 462 
 463     private static final class JShellUnixTerminal extends NoInterruptUnixTerminal {
 464 
 465         private final StopDetectingInputStream input;
 466 
 467         public JShellUnixTerminal(StopDetectingInputStream input) throws Exception {
 468             this.input = input;
 469         }
 470 
 471         public boolean isRaw() {
 472             try {
 473                 return getSettings().get("-a").contains("-icanon");
 474             } catch (IOException | InterruptedException ex) {
 475                 return false;
 476             }
 477         }
 478 
 479         @Override
 480         public InputStream wrapInIfNeeded(InputStream in) throws IOException {
 481             return input.setInputStream(super.wrapInIfNeeded(in));
 482         }


< prev index next >