1 | /* |
---|
2 | * ConsoleDocument.java |
---|
3 | * |
---|
4 | * Copyright (C) 2008-2009 Alessio Stalla |
---|
5 | * |
---|
6 | * $Id: REPLConsole.java 13145 2011-01-13 23:20:19Z ehuelsmann $ |
---|
7 | * |
---|
8 | * This program is free software; you can redistribute it and/or |
---|
9 | * modify it under the terms of the GNU General Public License |
---|
10 | * as published by the Free Software Foundation; either version 2 |
---|
11 | * of the License, or (at your option) any later version. |
---|
12 | * |
---|
13 | * This program is distributed in the hope that it will be useful, |
---|
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
16 | * GNU General Public License for more details. |
---|
17 | * |
---|
18 | * You should have received a copy of the GNU General Public License |
---|
19 | * along with this program; if not, write to the Free Software |
---|
20 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
---|
21 | * |
---|
22 | * As a special exception, the copyright holders of this library give you |
---|
23 | * permission to link this library with independent modules to produce an |
---|
24 | * executable, regardless of the license terms of these independent |
---|
25 | * modules, and to copy and distribute the resulting executable under |
---|
26 | * terms of your choice, provided that you also meet, for each linked |
---|
27 | * independent module, the terms and conditions of the license of that |
---|
28 | * module. An independent module is a module which is not derived from |
---|
29 | * or based on this library. If you modify this library, you may extend |
---|
30 | * this exception to your version of the library, but you are not |
---|
31 | * obligated to do so. If you do not wish to do so, delete this |
---|
32 | * exception statement from your version. |
---|
33 | */ |
---|
34 | |
---|
35 | package org.armedbear.lisp.java.swing; |
---|
36 | |
---|
37 | import java.awt.Window; |
---|
38 | import java.awt.event.WindowAdapter; |
---|
39 | import java.awt.event.WindowEvent; |
---|
40 | import java.io.BufferedReader; |
---|
41 | import java.io.BufferedWriter; |
---|
42 | import java.lang.RuntimeException; |
---|
43 | import java.io.Reader; |
---|
44 | import java.io.Writer; |
---|
45 | |
---|
46 | import javax.swing.JFrame; |
---|
47 | import javax.swing.JScrollPane; |
---|
48 | import javax.swing.JTextArea; |
---|
49 | import javax.swing.SwingUtilities; |
---|
50 | import javax.swing.event.DocumentEvent; |
---|
51 | import javax.swing.event.DocumentListener; |
---|
52 | import javax.swing.text.AttributeSet; |
---|
53 | import javax.swing.text.BadLocationException; |
---|
54 | import javax.swing.text.DefaultStyledDocument; |
---|
55 | import javax.swing.text.JTextComponent; |
---|
56 | import org.armedbear.lisp.Function; |
---|
57 | import org.armedbear.lisp.Interpreter; |
---|
58 | import org.armedbear.lisp.LispObject; |
---|
59 | import org.armedbear.lisp.LispThread; |
---|
60 | import org.armedbear.lisp.SpecialBindingsMark; |
---|
61 | import org.armedbear.lisp.Stream; |
---|
62 | import org.armedbear.lisp.Symbol; |
---|
63 | import org.armedbear.lisp.TwoWayStream; |
---|
64 | |
---|
65 | import static org.armedbear.lisp.Lisp.*; |
---|
66 | |
---|
67 | public class REPLConsole extends DefaultStyledDocument { |
---|
68 | |
---|
69 | private StringBuffer inputBuffer = new StringBuffer(); |
---|
70 | |
---|
71 | private Reader reader = new Reader() { |
---|
72 | |
---|
73 | @Override |
---|
74 | public void close() throws RuntimeException {} |
---|
75 | |
---|
76 | @Override |
---|
77 | public synchronized int read(char[] cbuf, int off, int len) throws RuntimeException { |
---|
78 | try { |
---|
79 | int length = Math.min(inputBuffer.length(), len); |
---|
80 | while(length <= 0) { |
---|
81 | wait(); |
---|
82 | length = Math.min(inputBuffer.length(), len); |
---|
83 | } |
---|
84 | inputBuffer.getChars(0, length, cbuf, off); |
---|
85 | inputBuffer.delete(0, length); |
---|
86 | return length; |
---|
87 | } catch (InterruptedException e) { |
---|
88 | throw new RuntimeException(e); |
---|
89 | } |
---|
90 | } |
---|
91 | }; |
---|
92 | |
---|
93 | private Writer writer = new Writer() { |
---|
94 | |
---|
95 | @Override |
---|
96 | public void close() throws RuntimeException {} |
---|
97 | |
---|
98 | @Override |
---|
99 | public void flush() throws RuntimeException {} |
---|
100 | |
---|
101 | @Override |
---|
102 | public void write(final char[] cbuf, final int off, final int len) throws RuntimeException { |
---|
103 | try { |
---|
104 | final int insertOffs; |
---|
105 | synchronized(reader) { |
---|
106 | if(inputBuffer.toString().matches("^\\s*$")) { |
---|
107 | int length = inputBuffer.length(); |
---|
108 | inputBuffer.delete(0, length); |
---|
109 | } |
---|
110 | insertOffs = getLength() - inputBuffer.length(); |
---|
111 | reader.notifyAll(); |
---|
112 | } |
---|
113 | Runnable r = new Runnable() { |
---|
114 | public void run() { |
---|
115 | synchronized(reader) { |
---|
116 | try { |
---|
117 | superInsertString(insertOffs, |
---|
118 | new String(cbuf, off, len), |
---|
119 | null); |
---|
120 | } catch(Exception e) { |
---|
121 | assert(false); //BadLocationException should not happen here |
---|
122 | } |
---|
123 | } |
---|
124 | } |
---|
125 | }; |
---|
126 | SwingUtilities.invokeAndWait(r); |
---|
127 | } catch (Exception e) { |
---|
128 | throw new RuntimeException(e); |
---|
129 | } |
---|
130 | } |
---|
131 | }; |
---|
132 | |
---|
133 | private boolean disposed = false; |
---|
134 | private final Thread replThread; |
---|
135 | |
---|
136 | public REPLConsole(LispObject replFunction) { |
---|
137 | final LispObject replWrapper = makeReplWrapper(new Stream(Symbol.SYSTEM_STREAM, new BufferedReader(reader)), |
---|
138 | new Stream(Symbol.SYSTEM_STREAM, new BufferedWriter(writer)), |
---|
139 | replFunction); |
---|
140 | replThread = new Thread("REPL-thread-" + System.identityHashCode(this)) { |
---|
141 | public void run() { |
---|
142 | while(true) { |
---|
143 | replWrapper.execute(); |
---|
144 | yield(); |
---|
145 | } |
---|
146 | } |
---|
147 | }; |
---|
148 | replThread.start(); |
---|
149 | } |
---|
150 | |
---|
151 | @Override |
---|
152 | public void insertString(int offs, String str, AttributeSet a) |
---|
153 | throws BadLocationException { |
---|
154 | synchronized(reader) { |
---|
155 | int bufferStart = getLength() - inputBuffer.length(); |
---|
156 | if(offs < bufferStart) { |
---|
157 | throw new BadLocationException("Can only insert after " + bufferStart, offs); |
---|
158 | } |
---|
159 | superInsertString(offs, str, a); |
---|
160 | inputBuffer.insert(offs - bufferStart, str); |
---|
161 | if(processInputP(inputBuffer, str)) { |
---|
162 | reader.notifyAll(); |
---|
163 | } |
---|
164 | } |
---|
165 | } |
---|
166 | |
---|
167 | protected void superInsertString(int offs, String str, AttributeSet a) |
---|
168 | throws BadLocationException { |
---|
169 | super.insertString(offs, str, a); |
---|
170 | } |
---|
171 | |
---|
172 | /** |
---|
173 | * Guaranteed to run with exclusive access to the buffer. |
---|
174 | * @param sb NB sb MUST NOT be destructively modified!! |
---|
175 | * @return |
---|
176 | */ |
---|
177 | protected boolean processInputP(StringBuffer sb, String str) { |
---|
178 | if(str.indexOf("\n") == -1) { |
---|
179 | return false; |
---|
180 | } |
---|
181 | int parenCount = 0; |
---|
182 | int len = sb.length(); |
---|
183 | for(int i = 0; i < len; i++) { |
---|
184 | char c = sb.charAt(i); |
---|
185 | if(c == '(') { |
---|
186 | parenCount++; |
---|
187 | } else if(c == ')') { |
---|
188 | parenCount--; |
---|
189 | if(parenCount == 0) { |
---|
190 | return true; |
---|
191 | } |
---|
192 | } |
---|
193 | } |
---|
194 | return parenCount <= 0; |
---|
195 | } |
---|
196 | |
---|
197 | @Override |
---|
198 | public void remove(int offs, int len) throws BadLocationException { |
---|
199 | synchronized(reader) { |
---|
200 | int bufferStart = getLength() - inputBuffer.length(); |
---|
201 | if(offs < bufferStart) { |
---|
202 | throw new BadLocationException("Can only remove after " + bufferStart, offs); |
---|
203 | } |
---|
204 | super.remove(offs, len); |
---|
205 | inputBuffer.delete(offs - bufferStart, offs - bufferStart + len); |
---|
206 | } |
---|
207 | } |
---|
208 | |
---|
209 | public Reader getReader() { |
---|
210 | return reader; |
---|
211 | } |
---|
212 | |
---|
213 | public Writer getWriter() { |
---|
214 | return writer; |
---|
215 | } |
---|
216 | |
---|
217 | public void setupTextComponent(final JTextComponent txt) { |
---|
218 | addDocumentListener(new DocumentListener() { |
---|
219 | |
---|
220 | // @Override |
---|
221 | public void changedUpdate(DocumentEvent e) { |
---|
222 | } |
---|
223 | |
---|
224 | // @Override |
---|
225 | public void insertUpdate(DocumentEvent e) { |
---|
226 | int len = getLength(); |
---|
227 | if(len - e.getLength() == e.getOffset()) { //The insert was at the end of the document |
---|
228 | txt.setCaretPosition(getLength()); |
---|
229 | } |
---|
230 | } |
---|
231 | |
---|
232 | // @Override |
---|
233 | public void removeUpdate(DocumentEvent e) { |
---|
234 | } |
---|
235 | }); |
---|
236 | txt.setCaretPosition(getLength()); |
---|
237 | } |
---|
238 | |
---|
239 | public void dispose() { |
---|
240 | disposed = true; |
---|
241 | for(DocumentListener listener : getDocumentListeners()) { |
---|
242 | removeDocumentListener(listener); |
---|
243 | } |
---|
244 | try { |
---|
245 | reader.close(); |
---|
246 | writer.close(); |
---|
247 | } catch (Exception e) { |
---|
248 | throw new RuntimeException(e); |
---|
249 | } |
---|
250 | replThread.interrupt(); //really? |
---|
251 | } |
---|
252 | |
---|
253 | private final LispObject debuggerHook = new Function() { |
---|
254 | |
---|
255 | @Override |
---|
256 | public LispObject execute(LispObject condition, LispObject debuggerHook) { |
---|
257 | if(disposed) { |
---|
258 | return PACKAGE_SYS.findSymbol("%DEBUGGER-HOOK-FUNCTION").execute(condition, debuggerHook); |
---|
259 | } else { |
---|
260 | return NIL; |
---|
261 | } |
---|
262 | } |
---|
263 | |
---|
264 | }; |
---|
265 | |
---|
266 | public LispObject makeReplWrapper(final Stream in, final Stream out, final LispObject fn) { |
---|
267 | return new Function() { |
---|
268 | @Override |
---|
269 | public LispObject execute() { |
---|
270 | SpecialBindingsMark lastSpecialBinding = LispThread.currentThread().markSpecialBindings(); |
---|
271 | try { |
---|
272 | TwoWayStream ioStream = new TwoWayStream(in, out); |
---|
273 | LispThread.currentThread().bindSpecial(Symbol.DEBUGGER_HOOK, debuggerHook); |
---|
274 | LispThread.currentThread().bindSpecial(Symbol.STANDARD_INPUT, in); |
---|
275 | LispThread.currentThread().bindSpecial(Symbol.STANDARD_OUTPUT, out); |
---|
276 | LispThread.currentThread().bindSpecial(Symbol.ERROR_OUTPUT, out); |
---|
277 | LispThread.currentThread().bindSpecial(Symbol.TERMINAL_IO, ioStream); |
---|
278 | LispThread.currentThread().bindSpecial(Symbol.DEBUG_IO, ioStream); |
---|
279 | LispThread.currentThread().bindSpecial(Symbol.QUERY_IO, ioStream); |
---|
280 | return fn.execute(); |
---|
281 | } finally { |
---|
282 | LispThread.currentThread().resetSpecialBindings(lastSpecialBinding); |
---|
283 | } |
---|
284 | } |
---|
285 | |
---|
286 | }; |
---|
287 | } |
---|
288 | |
---|
289 | public void disposeOnClose(final Window parent) { |
---|
290 | parent.addWindowListener(new WindowAdapter() { |
---|
291 | @Override |
---|
292 | public void windowClosing(WindowEvent e) { |
---|
293 | dispose(); |
---|
294 | parent.removeWindowListener(this); |
---|
295 | } |
---|
296 | }); |
---|
297 | } |
---|
298 | |
---|
299 | public static void main(String[] args) { |
---|
300 | LispObject repl = null; |
---|
301 | try { |
---|
302 | repl = Interpreter.createInstance().eval("#'top-level::top-level-loop"); |
---|
303 | } catch (Throwable e) { |
---|
304 | e.printStackTrace(); |
---|
305 | System.exit(1); // Ok. We haven't done anything useful yet. |
---|
306 | } |
---|
307 | final REPLConsole d = new REPLConsole(repl); |
---|
308 | final JTextComponent txt = new JTextArea(d); |
---|
309 | d.setupTextComponent(txt); |
---|
310 | JFrame f = new JFrame(); |
---|
311 | f.add(new JScrollPane(txt)); |
---|
312 | d.disposeOnClose(f); |
---|
313 | f.setDefaultCloseOperation(f.EXIT_ON_CLOSE); |
---|
314 | f.pack(); |
---|
315 | f.setVisible(true); |
---|
316 | } |
---|
317 | |
---|
318 | } |
---|