├── .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 |
--------------------------------------------------------------------------------