├── .gitignore ├── README.md ├── project.clj └── src ├── clj └── org │ └── dipert │ └── swingrepl │ └── main.clj └── jvm └── bsh ├── ConsoleInterface.java └── util ├── GUIConsoleInterface.java ├── JConsole.java └── NameCompletion.java /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | lib 4 | classes 5 | *.class 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | clj-swingrepl 2 | ============= 3 | 4 | Swing Clojure REPL that uses BeanShell's JConsole component. 5 | 6 | Build 7 | ----- 8 | 9 | * Install [Leiningen](http://github.com/technomancy/leiningen) 10 | * `lein deps` 11 | * `lein javac` 12 | 13 | Run 14 | --- 15 | 16 | You can run `lein swank` and connect with SLIME via Emacs, or you can build a distributable jar with `lein uberjar` 17 | 18 | To run, use something like `java -jar swingrepl-standalone.jar` 19 | 20 | Todo 21 | ---- 22 | 23 | * Completions for things available in the current namespace: JConsole has its own completions mechanism that might be hooked into 24 | * Bracket, parentheses, quote completion/matching/highlighting 25 | * Better as-library behavior: provide configurable automatic imports 26 | 27 | Notes 28 | ----- 29 | 30 | * A Clojure implementation of something like JConsole might be nice 31 | 32 | Thanks 33 | ------ 34 | 35 | Many props to the BeanShell dude for making such a cool REPL. 36 | 37 | Copyright 2012 Alan Dipert 38 | Distributed under the Eclipse Public License, the same as Clojure. 39 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject swingrepl "1.4.1-SNAPSHOT" 2 | :source-paths ["src/clj"] 3 | :java-source-paths ["src/jvm"] 4 | :javac-options ["-target" "1.6" "-source" "1.6"] 5 | :description "A Swing Clojure REPL using BeanShell's JConsole" 6 | :dependencies [[org.clojure/clojure "1.5.0"]] 7 | :main org.dipert.swingrepl.main) 8 | -------------------------------------------------------------------------------- /src/clj/org/dipert/swingrepl/main.clj: -------------------------------------------------------------------------------- 1 | (ns org.dipert.swingrepl.main 2 | "Swing Clojure REPL using BeanShell's JConsole" 3 | (:require clojure.main clojure.repl) 4 | (:import (javax.swing JFrame) 5 | (java.awt.event WindowEvent) 6 | (java.awt Font) 7 | (bsh.util JConsole)) 8 | (:gen-class)) 9 | 10 | (def ^{:doc "Formatted Clojure version string" 11 | :private true} 12 | clj-version 13 | (apply str (interpose \. (map *clojure-version* [:major :minor :incremental])))) 14 | 15 | (def ^{:doc "Default REPL options" 16 | :private false} 17 | default-opts 18 | {:width 972 19 | :height 400 20 | :font (Font. "Monospaced" Font/PLAIN 14) 21 | :title (str "Clojure " clj-version " REPL") 22 | :prompt #(printf "%s=> " (ns-name *ns*)) 23 | :init #() 24 | :eval eval 25 | :on-close JFrame/DISPOSE_ON_CLOSE}) 26 | 27 | (def ^{:doc "Default debug REPL options" 28 | :private false} 29 | default-dbg-opts 30 | {:title (str "Clojure " clj-version " Debug REPL") 31 | :prompt #(print "dr => ") 32 | :eval (comment "See make-dbg-repl-jframe")}) 33 | 34 | (defn- make-repl-thread [console & repl-args] 35 | (binding [*out* (.getOut console) 36 | *in* (clojure.lang.LineNumberingPushbackReader. (.getIn console)) 37 | *err* (.getOut console)] 38 | (Thread. (bound-fn [] 39 | (apply clojure.main/repl repl-args))))) 40 | 41 | (defn- window-closing-dispatcher [window] 42 | #(.dispatchEvent window (WindowEvent. window WindowEvent/WINDOW_CLOSING))) 43 | 44 | 45 | (defn make-repl-jconsole 46 | "Returns a JConsole component" 47 | [options] 48 | (let [{:keys [font prompt init eval eof]} options 49 | console (bsh.util.JConsole. font) 50 | thread (make-repl-thread console :prompt prompt :init init :eval eval) 51 | stopper (clojure.repl/thread-stopper thread)] 52 | (doto console 53 | (.setInterruptFunction (fn [reason] (stopper reason))) 54 | (.setEOFFunction eof)) 55 | (.start thread) 56 | console)) 57 | 58 | 59 | (defn make-repl-jframe 60 | "Displays a JFrame with JConsole and attached REPL." 61 | ([] (make-repl-jframe {})) 62 | ([optmap] 63 | (let [options (merge default-opts optmap) 64 | {:keys [title width height font on-close prompt init eval]} options 65 | jframe (doto (JFrame. title) 66 | (.setSize width height) 67 | (.setDefaultCloseOperation on-close) 68 | (.setLocationRelativeTo nil)) 69 | eof (window-closing-dispatcher jframe)] 70 | (let [console (make-repl-jconsole 71 | (merge 72 | {:eof eof} 73 | options))] 74 | (doto (.getContentPane jframe) 75 | (.setLayout (java.awt.BorderLayout.)) 76 | (.add console)) 77 | (doto jframe 78 | (.pack) 79 | (.setSize width height)) 80 | (.requestFocus console) 81 | (.setVisible jframe true))))) 82 | 83 | ;; local-bindings and eval-with-locals are from http://gist.github.com/252421 84 | ;; Inspired by George Jahad's version: http://georgejahad.com/clojure/debug-repl.html 85 | (defmacro local-bindings 86 | "Produces a map of the names of local bindings to their values." 87 | [] 88 | (let [symbols (map key @clojure.lang.Compiler/LOCAL_ENV)] 89 | (zipmap (map (fn [sym] `(quote ~sym)) symbols) symbols))) 90 | 91 | (declare ^:dynamic *locals*) 92 | (defn eval-with-locals 93 | "Evals a form with given locals. The locals should be a map of symbols to 94 | values." 95 | [locals form] 96 | (binding [*locals* locals] 97 | (eval 98 | `(let ~(vec (mapcat #(list % `(*locals* '~%)) (keys locals))) 99 | ~form)))) 100 | 101 | (defmacro make-dbg-repl-jframe 102 | "Displays a JFrame with JConsole and attached REPL. The frame has the context 103 | from wherever it has been called, effectively creating a debugging REPL. 104 | 105 | Usage: 106 | 107 | (use 'org.dipert.swingrepl.main) 108 | (defn foo [a] (+ a 5) (make-dbg-repl-jframe {}) (+ a 2)) 109 | (foo 3) 110 | 111 | This will pop up the debugging REPL, you should be able to access the var 'a' 112 | from the REPL." 113 | ([] `(make-dbg-repl-jframe {})) 114 | ([optmap] 115 | `(make-repl-jframe (merge 116 | default-opts 117 | default-dbg-opts 118 | {:eval (partial eval-with-locals (local-bindings))} 119 | ~optmap)))) 120 | 121 | (defn -main 122 | [& args] 123 | (make-repl-jframe {:on-close JFrame/EXIT_ON_CLOSE})) 124 | -------------------------------------------------------------------------------- /src/jvm/bsh/ConsoleInterface.java: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * * 3 | * This file is part of the BeanShell Java Scripting distribution. * 4 | * Documentation and updates may be found at http://www.beanshell.org/ * 5 | * * 6 | * Sun Public License Notice: * 7 | * * 8 | * The contents of this file are subject to the Sun Public License Version * 9 | * 1.0 (the "License"); you may not use this file except in compliance with * 10 | * the License. A copy of the License is available at http://www.sun.com * 11 | * * 12 | * The Original Code is BeanShell. The Initial Developer of the Original * 13 | * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright * 14 | * (C) 2000. All Rights Reserved. * 15 | * * 16 | * GNU Public License Notice: * 17 | * * 18 | * Alternatively, the contents of this file may be used under the terms of * 19 | * the GNU Lesser General Public License (the "LGPL"), in which case the * 20 | * provisions of LGPL are applicable instead of those above. If you wish to * 21 | * allow use of your version of this file only under the terms of the LGPL * 22 | * and not to allow others to use your version of this file under the SPL, * 23 | * indicate your decision by deleting the provisions above and replace * 24 | * them with the notice and other provisions required by the LGPL. If you * 25 | * do not delete the provisions above, a recipient may use your version of * 26 | * this file under either the SPL or the LGPL. * 27 | * * 28 | * Patrick Niemeyer (pat@pat.net) * 29 | * Author of Learning Java, O'Reilly & Associates * 30 | * http://www.pat.net/~pat/ * 31 | * * 32 | *****************************************************************************/ 33 | 34 | 35 | package bsh; 36 | 37 | import java.io.*; 38 | 39 | /** 40 | * The capabilities of a minimal console for BeanShell. 41 | * Stream I/O and optimized print for output. 42 | *

43 | * A simple console may ignore some of these or map them to trivial 44 | * implementations. e.g. print() with color can be mapped to plain text. 45 | * 46 | * @see bsh.util.GUIConsoleInterface 47 | */ 48 | public interface ConsoleInterface { 49 | public Reader getIn(); 50 | 51 | public PrintWriter getOut(); 52 | 53 | public PrintWriter getErr(); 54 | 55 | public void println(Object o); 56 | 57 | public void print(Object o); 58 | 59 | public void error(Object o); 60 | } 61 | -------------------------------------------------------------------------------- /src/jvm/bsh/util/GUIConsoleInterface.java: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * * 3 | * This file is part of the BeanShell Java Scripting distribution. * 4 | * Documentation and updates may be found at http://www.beanshell.org/ * 5 | * * 6 | * Sun Public License Notice: * 7 | * * 8 | * The contents of this file are subject to the Sun Public License Version * 9 | * 1.0 (the "License"); you may not use this file except in compliance with * 10 | * the License. A copy of the License is available at http://www.sun.com * 11 | * * 12 | * The Original Code is BeanShell. The Initial Developer of the Original * 13 | * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright * 14 | * (C) 2000. All Rights Reserved. * 15 | * * 16 | * GNU Public License Notice: * 17 | * * 18 | * Alternatively, the contents of this file may be used under the terms of * 19 | * the GNU Lesser General Public License (the "LGPL"), in which case the * 20 | * provisions of LGPL are applicable instead of those above. If you wish to * 21 | * allow use of your version of this file only under the terms of the LGPL * 22 | * and not to allow others to use your version of this file under the SPL, * 23 | * indicate your decision by deleting the provisions above and replace * 24 | * them with the notice and other provisions required by the LGPL. If you * 25 | * do not delete the provisions above, a recipient may use your version of * 26 | * this file under either the SPL or the LGPL. * 27 | * * 28 | * Patrick Niemeyer (pat@pat.net) * 29 | * Author of Learning Java, O'Reilly & Associates * 30 | * http://www.pat.net/~pat/ * 31 | * * 32 | *****************************************************************************/ 33 | 34 | package bsh.util; 35 | 36 | import bsh.ConsoleInterface; 37 | 38 | import java.awt.Color; 39 | 40 | /** 41 | * Additional capabilities of an interactive console for BeanShell. 42 | * Althought this is called "GUIConsoleInterface" it might just as well be 43 | * used by a more sophisticated text-only command line. 44 | *

45 | * Note: we may want to express the command line history, editing, 46 | * and cut & paste functionality here as well at some point. 47 | */ 48 | public interface GUIConsoleInterface extends ConsoleInterface { 49 | public void print(Object o, Color color); 50 | 51 | public void setNameCompletion(NameCompletion nc); 52 | 53 | /** 54 | * e.g. the wait cursor 55 | */ 56 | public void setWaitFeedback(boolean on); 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/jvm/bsh/util/JConsole.java: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * * 3 | * This file is part of the BeanShell Java Scripting distribution. * 4 | * Documentation and updates may be found at http://www.beanshell.org/ * 5 | * * 6 | * Sun Public License Notice: * 7 | * * 8 | * The contents of this file are subject to the Sun Public License Version * 9 | * 1.0 (the "License"); you may not use this file except in compliance with * 10 | * the License. A copy of the License is available at http://www.sun.com * 11 | * * 12 | * The Original Code is BeanShell. The Initial Developer of the Original * 13 | * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright * 14 | * (C) 2000. All Rights Reserved. * 15 | * * 16 | * GNU Public License Notice: * 17 | * * 18 | * Alternatively, the contents of this file may be used under the terms of * 19 | * the GNU Lesser General Public License (the "LGPL"), in which case the * 20 | * provisions of LGPL are applicable instead of those above. If you wish to * 21 | * allow use of your version of this file only under the terms of the LGPL * 22 | * and not to allow others to use your version of this file under the SPL, * 23 | * indicate your decision by deleting the provisions above and replace * 24 | * them with the notice and other provisions required by the LGPL. If you * 25 | * do not delete the provisions above, a recipient may use your version of * 26 | * this file under either the SPL or the LGPL. * 27 | * * 28 | * Patrick Niemeyer (pat@pat.net) * 29 | * Author of Learning Java, O'Reilly & Associates * 30 | * http://www.pat.net/~pat/ * 31 | * * 32 | *****************************************************************************/ 33 | 34 | package bsh.util; 35 | 36 | import java.awt.Component; 37 | import java.awt.Font; 38 | import java.awt.Color; 39 | import java.awt.Insets; 40 | import java.awt.event.*; 41 | import java.beans.PropertyChangeEvent; 42 | import java.beans.PropertyChangeListener; 43 | import java.io.*; 44 | import java.util.Vector; 45 | import java.awt.Cursor; 46 | import javax.swing.text.*; 47 | import javax.swing.*; 48 | import clojure.lang.IFn; 49 | 50 | // Things that are not in the core packages 51 | 52 | import bsh.util.NameCompletion; 53 | 54 | /** 55 | * A JFC/Swing based console for the BeanShell desktop. 56 | * This is a descendant of the old AWTConsole. 57 | *

58 | * Improvements by: Mark Donszelmann 59 | * including Cut & Paste 60 | *

61 | * Improvements by: Daniel Leuck 62 | * including Color and Image support, key press bug workaround 63 | */ 64 | public class JConsole extends JScrollPane 65 | implements GUIConsoleInterface, Runnable, KeyListener, 66 | MouseListener, ActionListener, PropertyChangeListener { 67 | private final static String CUT = "Cut"; 68 | private final static String COPY = "Copy"; 69 | private final static String PASTE = "Paste"; 70 | 71 | private IFn interruptFunction; 72 | private IFn eofFunction; 73 | private Writer outPipe; 74 | private Reader inPipe; 75 | private Reader in; 76 | private PrintWriter out; 77 | 78 | public Reader getIn() { 79 | return in; 80 | } 81 | 82 | public PrintWriter getOut() { 83 | return out; 84 | } 85 | 86 | public PrintWriter getErr() { 87 | return out; 88 | } 89 | 90 | private int cmdStart = 0; 91 | private Vector history = new Vector(); 92 | private String startedLine; 93 | private int histLine = 0; 94 | 95 | private JPopupMenu menu; 96 | private JTextPane text; 97 | private DefaultStyledDocument doc; 98 | 99 | NameCompletion nameCompletion; 100 | final int SHOW_AMBIG_MAX = 10; 101 | 102 | // hack to prevent key repeat for some reason? 103 | private boolean gotUp = true; 104 | 105 | public JConsole() { 106 | this(null, null, new Font("Monospaced", Font.PLAIN, 14)); 107 | } 108 | 109 | public JConsole(Font font) { 110 | this(null, null, font); 111 | } 112 | 113 | public JConsole(Reader cin, Writer cout, Font font) { 114 | super(); 115 | 116 | // Special TextPane which catches for cut and paste, both L&F keys and 117 | // programmatic behaviour 118 | text = new JTextPane(doc = new DefaultStyledDocument()) { 119 | public void cut() { 120 | if (text.getCaretPosition() < cmdStart) { 121 | super.copy(); 122 | } else { 123 | super.cut(); 124 | } 125 | } 126 | 127 | public void paste() { 128 | forceCaretMoveToEnd(); 129 | super.paste(); 130 | } 131 | }; 132 | 133 | text.setFont(font); 134 | text.setText(""); 135 | text.setMargin(new Insets(7, 5, 7, 5)); 136 | text.addKeyListener(this); 137 | setViewportView(text); 138 | 139 | // create popup menu 140 | menu = new JPopupMenu("JConsole Menu"); 141 | menu.add(new JMenuItem(CUT)).addActionListener(this); 142 | menu.add(new JMenuItem(COPY)).addActionListener(this); 143 | menu.add(new JMenuItem(PASTE)).addActionListener(this); 144 | 145 | text.addMouseListener(this); 146 | 147 | // make sure popup menu follows Look & Feel 148 | UIManager.addPropertyChangeListener(this); 149 | 150 | outPipe = cout; 151 | if (outPipe == null) { 152 | outPipe = new PipedWriter(); 153 | try { 154 | in = new PipedReader((PipedWriter) outPipe); 155 | } catch (IOException e) { 156 | print("Console internal error (1)...", Color.red); 157 | } 158 | } 159 | 160 | inPipe = cin; 161 | if (inPipe == null) { 162 | PipedWriter pout = new PipedWriter(); 163 | out = new PrintWriter(pout); 164 | try { 165 | inPipe = new PipedReader(pout); 166 | } catch (IOException e) { 167 | print("Console internal error: " + e); 168 | } 169 | } 170 | // Start the inpipe watcher 171 | new Thread(this).start(); 172 | 173 | 174 | requestFocus(); 175 | } 176 | 177 | public void setInterruptFunction(IFn interruptFunction) { 178 | this.interruptFunction = interruptFunction; 179 | } 180 | 181 | public void setEOFFunction(IFn eofFunction) { 182 | this.eofFunction = eofFunction; 183 | } 184 | 185 | public void requestFocus() { 186 | super.requestFocus(); 187 | text.requestFocus(); 188 | } 189 | 190 | public void keyPressed(KeyEvent e) { 191 | type(e); 192 | gotUp = false; 193 | } 194 | 195 | public void keyTyped(KeyEvent e) { 196 | type(e); 197 | } 198 | 199 | public void keyReleased(KeyEvent e) { 200 | gotUp = true; 201 | type(e); 202 | } 203 | 204 | private synchronized void type(KeyEvent e) { 205 | switch (e.getKeyCode()) { 206 | case (KeyEvent.VK_ENTER): 207 | if (e.getID() == KeyEvent.KEY_PRESSED) { 208 | enter(); 209 | resetCommandStart(); 210 | text.setCaretPosition(cmdStart); 211 | } 212 | e.consume(); 213 | text.repaint(); 214 | break; 215 | 216 | case (KeyEvent.VK_UP): 217 | if (e.getID() == KeyEvent.KEY_PRESSED) { 218 | historyUp(); 219 | } 220 | e.consume(); 221 | break; 222 | 223 | case (KeyEvent.VK_DOWN): 224 | if (e.getID() == KeyEvent.KEY_PRESSED) { 225 | historyDown(); 226 | } 227 | e.consume(); 228 | break; 229 | 230 | case (KeyEvent.VK_LEFT): 231 | if (text.getCaretPosition() <= cmdStart) { 232 | e.consume(); 233 | } 234 | break; 235 | 236 | case (KeyEvent.VK_BACK_SPACE): 237 | if (text.getSelectedText() == null) { 238 | if(text.getCaretPosition() <= cmdStart) { 239 | e.consume(); 240 | } 241 | } else { 242 | if(text.getCaretPosition() < cmdStart) { 243 | e.consume(); 244 | } 245 | // TODO: prevent deletion when the caret is at 246 | // the end of the user=> marker 247 | } 248 | // See also default: case for backspace workaround 249 | break; 250 | 251 | case (KeyEvent.VK_DELETE): 252 | if (text.getCaretPosition() < cmdStart) { 253 | e.consume(); 254 | } 255 | // TODO: prevent deletion when the caret is at 256 | // the end of the user=> marker 257 | break; 258 | 259 | case (KeyEvent.VK_RIGHT): 260 | forceCaretMoveToStart(); 261 | break; 262 | 263 | case (KeyEvent.VK_HOME): 264 | if ((e.getModifiers() & InputEvent.SHIFT_MASK) > 0) { 265 | text.moveCaretPosition(cmdStart); 266 | } else { 267 | text.setCaretPosition(cmdStart); 268 | } 269 | e.consume(); 270 | break; 271 | 272 | case (KeyEvent.VK_U): // clear line 273 | if ((e.getModifiers() & InputEvent.CTRL_MASK) > 0) { 274 | replaceRange("", cmdStart, textLength()); 275 | histLine = 0; 276 | e.consume(); 277 | } 278 | break; 279 | 280 | case (KeyEvent.VK_D): // "end of input" 281 | if ((e.getModifiers() & InputEvent.CTRL_MASK) > 0) { 282 | e.consume(); 283 | if(this.eofFunction != null) { 284 | this.eofFunction.invoke(); 285 | } 286 | } 287 | break; 288 | 289 | case (KeyEvent.VK_ALT): 290 | case (KeyEvent.VK_CAPS_LOCK): 291 | case (KeyEvent.VK_CONTROL): 292 | case (KeyEvent.VK_META): 293 | case (KeyEvent.VK_SHIFT): 294 | case (KeyEvent.VK_PRINTSCREEN): 295 | case (KeyEvent.VK_SCROLL_LOCK): 296 | case (KeyEvent.VK_PAUSE): 297 | case (KeyEvent.VK_INSERT): 298 | case (KeyEvent.VK_F1): 299 | case (KeyEvent.VK_F2): 300 | case (KeyEvent.VK_F3): 301 | case (KeyEvent.VK_F4): 302 | case (KeyEvent.VK_F5): 303 | case (KeyEvent.VK_F6): 304 | case (KeyEvent.VK_F7): 305 | case (KeyEvent.VK_F8): 306 | case (KeyEvent.VK_F9): 307 | case (KeyEvent.VK_F10): 308 | case (KeyEvent.VK_F11): 309 | case (KeyEvent.VK_F12): 310 | case (KeyEvent.VK_ESCAPE): 311 | 312 | // only modifier pressed 313 | break; 314 | 315 | // Control-C 316 | case (KeyEvent.VK_C): 317 | if (text.getSelectedText() == null) { // Ctrl-C also copies text 318 | if (((e.getModifiers() & InputEvent.CTRL_MASK) > 0) 319 | && (e.getID() == KeyEvent.KEY_PRESSED)) { 320 | //append("^C"); 321 | if(interruptFunction != null) { 322 | interruptFunction.invoke("User pressed Ctrl-C"); 323 | } 324 | } 325 | e.consume(); 326 | } 327 | break; 328 | 329 | case (KeyEvent.VK_TAB): 330 | if (e.getID() == KeyEvent.KEY_RELEASED) { 331 | String part = text.getText().substring(cmdStart); 332 | doCommandCompletion(part); 333 | } 334 | e.consume(); 335 | break; 336 | 337 | default: 338 | if ( 339 | (e.getModifiers() & 340 | (InputEvent.CTRL_MASK 341 | | InputEvent.ALT_MASK | InputEvent.META_MASK)) == 0) { 342 | // plain character 343 | forceCaretMoveToEnd(); 344 | } 345 | 346 | /* 347 | The getKeyCode function always returns VK_UNDEFINED for 348 | keyTyped events, so backspace is not fully consumed. 349 | */ 350 | if (e.paramString().indexOf("Backspace") != -1) { 351 | if (text.getCaretPosition() <= cmdStart) { 352 | e.consume(); 353 | break; 354 | } 355 | } 356 | 357 | break; 358 | } 359 | } 360 | 361 | private void doCommandCompletion(String part) { 362 | if (nameCompletion == null) { 363 | return; 364 | } 365 | 366 | int i = part.length() - 1; 367 | 368 | // Character.isJavaIdentifierPart() How convenient for us!! 369 | while ( 370 | i >= 0 && 371 | (Character.isJavaIdentifierPart(part.charAt(i)) 372 | || part.charAt(i) == '.') 373 | ) { 374 | i--; 375 | } 376 | 377 | part = part.substring(i + 1); 378 | 379 | if (part.length() < 2) // reasonable completion length 380 | { 381 | return; 382 | } 383 | 384 | //System.out.println("completing part: "+part); 385 | 386 | // no completion 387 | String[] complete = nameCompletion.completeName(part); 388 | if (complete.length == 0) { 389 | java.awt.Toolkit.getDefaultToolkit().beep(); 390 | return; 391 | } 392 | 393 | // Found one completion (possibly what we already have) 394 | if (complete.length == 1 && !complete.equals(part)) { 395 | String append = complete[0].substring(part.length()); 396 | append(append); 397 | return; 398 | } 399 | 400 | // Found ambiguous, show (some of) them 401 | 402 | String line = text.getText(); 403 | String command = line.substring(cmdStart); 404 | // Find prompt 405 | for (i = cmdStart; line.charAt(i) != '\n' && i > 0; i--) { 406 | ; 407 | } 408 | String prompt = line.substring(i + 1, cmdStart); 409 | 410 | // Show ambiguous 411 | StringBuffer sb = new StringBuffer("\n"); 412 | for (i = 0; i < complete.length && i < SHOW_AMBIG_MAX; i++) { 413 | sb.append(complete[i] + "\n"); 414 | } 415 | if (i == SHOW_AMBIG_MAX) { 416 | sb.append("...\n"); 417 | } 418 | 419 | print(sb, Color.gray); 420 | print(prompt); // print resets command start 421 | append(command); // append does not reset command start 422 | } 423 | 424 | private void resetCommandStart() { 425 | cmdStart = textLength(); 426 | } 427 | 428 | private void append(String string) { 429 | int slen = textLength(); 430 | text.select(slen, slen); 431 | text.replaceSelection(string); 432 | } 433 | 434 | private String replaceRange(Object s, int start, int end) { 435 | String st = s.toString(); 436 | text.select(start, end); 437 | text.replaceSelection(st); 438 | //text.repaint(); 439 | return st; 440 | } 441 | 442 | private void forceCaretMoveToEnd() { 443 | if (text.getCaretPosition() < cmdStart) { 444 | // move caret first! 445 | text.setCaretPosition(textLength()); 446 | } 447 | text.repaint(); 448 | } 449 | 450 | private void forceCaretMoveToStart() { 451 | if (text.getCaretPosition() < cmdStart) { 452 | // move caret first! 453 | } 454 | text.repaint(); 455 | } 456 | 457 | 458 | private void enter() { 459 | String s = getCmd(); 460 | 461 | if (s.length() == 0) // special hack for empty return! 462 | { 463 | s = ";\n"; 464 | } else { 465 | history.addElement(s); 466 | s = s + "\n"; 467 | } 468 | 469 | append("\n"); 470 | histLine = 0; 471 | acceptLine(s); 472 | text.repaint(); 473 | } 474 | 475 | private String getCmd() { 476 | String s = ""; 477 | try { 478 | s = text.getText(cmdStart, textLength() - cmdStart); 479 | } catch (BadLocationException e) { 480 | // should not happen 481 | System.out.println("Internal JConsole Error: " + e); 482 | } 483 | return s; 484 | } 485 | 486 | private void historyUp() { 487 | if (history.size() == 0) { 488 | return; 489 | } 490 | if (histLine == 0) // save current line 491 | { 492 | startedLine = getCmd(); 493 | } 494 | if (histLine < history.size()) { 495 | histLine++; 496 | showHistoryLine(); 497 | } 498 | } 499 | 500 | private void historyDown() { 501 | if (histLine == 0) { 502 | return; 503 | } 504 | 505 | histLine--; 506 | showHistoryLine(); 507 | } 508 | 509 | private void showHistoryLine() { 510 | String showline; 511 | if (histLine == 0) { 512 | showline = startedLine; 513 | } else { 514 | showline = (String) history.elementAt(history.size() - histLine); 515 | } 516 | 517 | replaceRange(showline, cmdStart, textLength()); 518 | text.setCaretPosition(textLength()); 519 | text.repaint(); 520 | } 521 | 522 | String ZEROS = "000"; 523 | 524 | private void acceptLine(String line) { 525 | //FIXME: what did this do? 526 | //line = buf.toString(); 527 | 528 | if (outPipe == null) { 529 | print("Console internal error: cannot output ...", Color.red); 530 | } else { 531 | try { 532 | outPipe.write(line); 533 | outPipe.flush(); 534 | } catch (IOException e) { 535 | outPipe = null; 536 | throw new RuntimeException("Console pipe broken..."); 537 | } 538 | } 539 | //text.repaint(); 540 | } 541 | 542 | public void println(Object o) { 543 | print(String.valueOf(o) + "\n"); 544 | text.repaint(); 545 | } 546 | 547 | public void print(final Object o) { 548 | invokeAndWait(new Runnable() { 549 | public void run() { 550 | append(String.valueOf(o)); 551 | resetCommandStart(); 552 | text.setCaretPosition(cmdStart); 553 | } 554 | }); 555 | } 556 | 557 | /** 558 | * Prints "\\n" (i.e. newline) 559 | */ 560 | public void println() { 561 | print("\n"); 562 | text.repaint(); 563 | } 564 | 565 | public void error(Object o) { 566 | print(o, Color.red); 567 | } 568 | 569 | public void println(Icon icon) { 570 | print(icon); 571 | println(); 572 | text.repaint(); 573 | } 574 | 575 | public void print(final Icon icon) { 576 | if (icon == null) { 577 | return; 578 | } 579 | 580 | invokeAndWait(new Runnable() { 581 | public void run() { 582 | text.insertIcon(icon); 583 | resetCommandStart(); 584 | text.setCaretPosition(cmdStart); 585 | } 586 | }); 587 | } 588 | 589 | public void print(Object s, Font font) { 590 | print(s, font, null); 591 | } 592 | 593 | public void print(Object s, Color color) { 594 | print(s, null, color); 595 | } 596 | 597 | public void print(final Object o, final Font font, final Color color) { 598 | invokeAndWait(new Runnable() { 599 | public void run() { 600 | AttributeSet old = getStyle(); 601 | setStyle(font, color); 602 | append(String.valueOf(o)); 603 | resetCommandStart(); 604 | text.setCaretPosition(cmdStart); 605 | setStyle(old, true); 606 | } 607 | }); 608 | } 609 | 610 | public void print( 611 | Object s, 612 | String fontFamilyName, 613 | int size, 614 | Color color 615 | ) { 616 | 617 | print(s, fontFamilyName, size, color, false, false, false); 618 | } 619 | 620 | public void print( 621 | final Object o, 622 | final String fontFamilyName, 623 | final int size, 624 | final Color color, 625 | final boolean bold, 626 | final boolean italic, 627 | final boolean underline 628 | ) { 629 | invokeAndWait(new Runnable() { 630 | public void run() { 631 | AttributeSet old = getStyle(); 632 | setStyle(fontFamilyName, size, color, bold, italic, underline); 633 | append(String.valueOf(o)); 634 | resetCommandStart(); 635 | text.setCaretPosition(cmdStart); 636 | setStyle(old, true); 637 | } 638 | }); 639 | } 640 | 641 | private AttributeSet setStyle(Font font) { 642 | return setStyle(font, null); 643 | } 644 | 645 | private AttributeSet setStyle(Color color) { 646 | return setStyle(null, color); 647 | } 648 | 649 | private AttributeSet setStyle(Font font, Color color) { 650 | if (font != null) { 651 | return setStyle(font.getFamily(), font.getSize(), color, 652 | font.isBold(), font.isItalic(), 653 | StyleConstants.isUnderline(getStyle())); 654 | } else { 655 | return setStyle(null, -1, color); 656 | } 657 | } 658 | 659 | private AttributeSet setStyle( 660 | String fontFamilyName, int size, Color color) { 661 | MutableAttributeSet attr = new SimpleAttributeSet(); 662 | if (color != null) { 663 | StyleConstants.setForeground(attr, color); 664 | } 665 | if (fontFamilyName != null) { 666 | StyleConstants.setFontFamily(attr, fontFamilyName); 667 | } 668 | if (size != -1) { 669 | StyleConstants.setFontSize(attr, size); 670 | } 671 | 672 | setStyle(attr); 673 | 674 | return getStyle(); 675 | } 676 | 677 | private AttributeSet setStyle( 678 | String fontFamilyName, 679 | int size, 680 | Color color, 681 | boolean bold, 682 | boolean italic, 683 | boolean underline 684 | ) { 685 | MutableAttributeSet attr = new SimpleAttributeSet(); 686 | if (color != null) { 687 | StyleConstants.setForeground(attr, color); 688 | } 689 | if (fontFamilyName != null) { 690 | StyleConstants.setFontFamily(attr, fontFamilyName); 691 | } 692 | if (size != -1) { 693 | StyleConstants.setFontSize(attr, size); 694 | } 695 | StyleConstants.setBold(attr, bold); 696 | StyleConstants.setItalic(attr, italic); 697 | StyleConstants.setUnderline(attr, underline); 698 | 699 | setStyle(attr); 700 | 701 | return getStyle(); 702 | } 703 | 704 | private void setStyle(AttributeSet attributes) { 705 | setStyle(attributes, false); 706 | } 707 | 708 | private void setStyle(AttributeSet attributes, boolean overWrite) { 709 | text.setCharacterAttributes(attributes, overWrite); 710 | } 711 | 712 | private AttributeSet getStyle() { 713 | return text.getCharacterAttributes(); 714 | } 715 | 716 | public void setFont(Font font) { 717 | super.setFont(font); 718 | 719 | if (text != null) { 720 | text.setFont(font); 721 | } 722 | } 723 | 724 | private void inPipeWatcher() throws IOException { 725 | char[] ca = new char[256]; // arbitrary blocking factor 726 | int read; 727 | while ((read = inPipe.read(ca)) != -1) { 728 | print(new String(ca, 0, read)); 729 | //text.repaint(); 730 | } 731 | 732 | println("Console: Input closed..."); 733 | } 734 | 735 | public void run() { 736 | try { 737 | inPipeWatcher(); 738 | } catch (IOException e) { 739 | print("Console: I/O Error: " + e + "\n", Color.red); 740 | } 741 | } 742 | 743 | public String toString() { 744 | return "BeanShell console"; 745 | } 746 | 747 | // MouseListener Interface 748 | public void mouseClicked(MouseEvent event) { 749 | } 750 | 751 | public void mousePressed(MouseEvent event) { 752 | if (event.isPopupTrigger()) { 753 | menu.show( 754 | (Component) event.getSource(), event.getX(), event.getY()); 755 | } 756 | } 757 | 758 | public void mouseReleased(MouseEvent event) { 759 | if (event.isPopupTrigger()) { 760 | menu.show((Component) event.getSource(), event.getX(), 761 | event.getY()); 762 | } 763 | text.repaint(); 764 | } 765 | 766 | public void mouseEntered(MouseEvent event) { 767 | } 768 | 769 | public void mouseExited(MouseEvent event) { 770 | } 771 | 772 | // property change 773 | public void propertyChange(PropertyChangeEvent event) { 774 | if (event.getPropertyName().equals("lookAndFeel")) { 775 | SwingUtilities.updateComponentTreeUI(menu); 776 | } 777 | } 778 | 779 | // handle cut, copy and paste 780 | public void actionPerformed(ActionEvent event) { 781 | String cmd = event.getActionCommand(); 782 | if (cmd.equals(CUT)) { 783 | text.cut(); 784 | } else if (cmd.equals(COPY)) { 785 | text.copy(); 786 | } else if (cmd.equals(PASTE)) { 787 | text.paste(); 788 | } 789 | } 790 | 791 | /** 792 | * If not in the event thread run via SwingUtilities.invokeAndWait() 793 | */ 794 | private void invokeAndWait(Runnable run) { 795 | if (!SwingUtilities.isEventDispatchThread()) { 796 | try { 797 | SwingUtilities.invokeAndWait(run); 798 | } catch (Exception e) { 799 | // shouldn't happen 800 | e.printStackTrace(); 801 | } 802 | } else { 803 | run.run(); 804 | } 805 | } 806 | 807 | /** 808 | * I don't think this is necessary anymore. 809 | *

810 | * The overridden read method in this class will not throw "Broken pipe" 811 | * IOExceptions; It will simply wait for new writers and data. 812 | * This is used by the JConsole internal read thread to allow writers 813 | * in different (and in particular ephemeral) threads to write to the pipe. 814 | * It also checks a little more frequently than the original read(). 815 | * Warning: read() will not even error on a read to an explicitly closed 816 | * pipe (override closed to for that). 817 | *

818 | * public static class BlockingPipedReader extends PipedReader { 819 | * boolean closed; 820 | * public BlockingPipedReader( PipedWriter pout ) 821 | * throws IOException 822 | * { 823 | * super(pout); 824 | * } 825 | * // FIXME 826 | * public synchronized int read() throws IOException { 827 | * if ( closed ) 828 | * throw new IOException("stream closed"); 829 | *

830 | * while (super.in < 0) { // While no data 831 | * notifyAll(); // Notify any writers to wake up 832 | * try { 833 | * wait(750); 834 | * } catch ( InterruptedException e ) { 835 | * throw new InterruptedIOException(); 836 | * } 837 | * } 838 | * // This is what the superclass does. 839 | * int ret = buffer[super.out++] & 0xFF; 840 | * if (super.out >= buffer.length) 841 | * super.out = 0; 842 | * if (super.in == super.out) 843 | * super.in = -1; // now empty 844 | * return ret; 845 | * } 846 | * public void close() throws IOException { 847 | * closed = true; 848 | * super.close(); 849 | * } 850 | *

851 | * } 852 | */ 853 | 854 | 855 | public void setNameCompletion(NameCompletion nc) { 856 | this.nameCompletion = nc; 857 | } 858 | 859 | public void setWaitFeedback(boolean on) { 860 | if (on) { 861 | setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); 862 | } else { 863 | setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 864 | } 865 | } 866 | 867 | private int textLength() { 868 | return text.getDocument().getLength(); 869 | } 870 | } 871 | -------------------------------------------------------------------------------- /src/jvm/bsh/util/NameCompletion.java: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * * 3 | * This file is part of the BeanShell Java Scripting distribution. * 4 | * Documentation and updates may be found at http://www.beanshell.org/ * 5 | * * 6 | * Sun Public License Notice: * 7 | * * 8 | * The contents of this file are subject to the Sun Public License Version * 9 | * 1.0 (the "License"); you may not use this file except in compliance with * 10 | * the License. A copy of the License is available at http://www.sun.com * 11 | * * 12 | * The Original Code is BeanShell. The Initial Developer of the Original * 13 | * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright * 14 | * (C) 2000. All Rights Reserved. * 15 | * * 16 | * GNU Public License Notice: * 17 | * * 18 | * Alternatively, the contents of this file may be used under the terms of * 19 | * the GNU Lesser General Public License (the "LGPL"), in which case the * 20 | * provisions of LGPL are applicable instead of those above. If you wish to * 21 | * allow use of your version of this file only under the terms of the LGPL * 22 | * and not to allow others to use your version of this file under the SPL, * 23 | * indicate your decision by deleting the provisions above and replace * 24 | * them with the notice and other provisions required by the LGPL. If you * 25 | * do not delete the provisions above, a recipient may use your version of * 26 | * this file under either the SPL or the LGPL. * 27 | * * 28 | * Patrick Niemeyer (pat@pat.net) * 29 | * Author of Learning Java, O'Reilly & Associates * 30 | * http://www.pat.net/~pat/ * 31 | * * 32 | *****************************************************************************/ 33 | 34 | package bsh.util; 35 | 36 | import java.util.*; 37 | 38 | /** 39 | * The interface for name completion. 40 | */ 41 | public interface NameCompletion { 42 | /** 43 | * Return an array containing a string element of the maximum 44 | * unambiguous namespace completion or, if there is no common prefix, 45 | * return the list of ambiguous names. 46 | * e.g. 47 | * input: "java.l" 48 | * output: [ "java.lang." ] 49 | * input: "java.lang." 50 | * output: [ "java.lang.Thread", "java.lang.Integer", ... ] 51 | *

52 | * Note: Alternatively, make a NameCompletionResult object someday... 53 | */ 54 | public String[] completeName(String part); 55 | 56 | } 57 | --------------------------------------------------------------------------------