001: /*
002: * LispShell.java
003: *
004: * Copyright (C) 2002-2004 Peter Graves
005: * $Id: LispShell.java,v 1.78 2004/09/21 16:10:32 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j;
023:
024: import gnu.regexp.REMatch;
025: import java.io.IOException;
026: import java.io.OutputStreamWriter;
027: import java.util.List;
028: import javax.swing.SwingUtilities;
029: import org.armedbear.lisp.Site;
030:
031: public class LispShell extends Shell {
032: private static final String DEFAULT_PROMPT_PATTERN = "^[^:>\\*\\]]*[:>\\*\\]] *";
033:
034: private static final String ALLEGRO_PROMPT_PATTERN = "^(\\[[0-9+][ci]?\\] )?[^ ]+\\([0-9]+\\): ";
035:
036: private static final String ARMEDBEAR_PROMPT_PATTERN = ALLEGRO_PROMPT_PATTERN;
037:
038: private static final String CLISP_PROMPT_PATTERN = "^[^>\\*\\]]*\\[[0-9]+\\]> ";
039:
040: private static final String CMUCL_PROMPT_PATTERN = "^\\* |^[0-9]+\\] ";
041:
042: private static final String SBCL_PROMPT_PATTERN = CMUCL_PROMPT_PATTERN
043: + "|" + ALLEGRO_PROMPT_PATTERN;
044:
045: private final boolean slime;
046:
047: private String resetCommand = null;
048: private String exitCommand = "(exit)";
049:
050: private Position posBeforeLastPrompt;
051: private Position posEndOfInput;
052:
053: private File currentDirectory;
054:
055: // For JLisp.java.
056: protected LispShell() {
057: setPromptRE(ARMEDBEAR_PROMPT_PATTERN);
058: setResetCommand(":reset");
059: slime = false;
060: }
061:
062: private LispShell(String shellCommand, String title) {
063: super (shellCommand, LispShellMode.getMode());
064: this .title = title;
065: formatter = mode.getFormatter(this );
066: slime = title.startsWith("slime ");
067: }
068:
069: public final boolean isLisp() {
070: return true;
071: }
072:
073: private void setResetCommand(String s) {
074: resetCommand = s;
075: }
076:
077: private void setExitCommand(String s) {
078: exitCommand = s;
079: }
080:
081: private static Shell createLispShell(String shellCommand,
082: String title, boolean startSlime) {
083: if (startSlime) {
084: if (shellCommand.indexOf("sbcl") >= 0) {
085: File lispHome = File.getInstance(Site.getLispHome());
086: if (lispHome == null)
087: return null; // FIXME Error message?
088: File swankLoader = File.getInstance(lispHome,
089: "swank-loader.lisp");
090: if (swankLoader == null)
091: return null; // FIXME Error message?
092: shellCommand = shellCommand + " --load "
093: + swankLoader.canonicalPath();
094: } else if (shellCommand.indexOf("abcl") >= 0
095: || shellCommand.indexOf("org.armedbear.lisp") >= 0) {
096: shellCommand = shellCommand
097: .concat(" --load-system-file swank-loader.lisp");
098: }
099: }
100: LispShell lisp = new LispShell(shellCommand, title);
101: lisp.startProcess();
102: if (lisp.getProcess() == null) {
103: Editor.getBufferList().remove(lisp);
104: String message;
105: if (Utilities.haveJpty())
106: message = "Unable to start process \"" + shellCommand
107: + "\"";
108: else
109: message = JPTY_NOT_FOUND;
110: MessageDialog.showMessageDialog(message, "Error");
111: return null;
112: }
113: if (shellCommand.equals("alisp")
114: || shellCommand.equals("/usr/bin/alisp")) {
115: lisp.setPromptRE(ALLEGRO_PROMPT_PATTERN);
116: lisp.setResetCommand(":reset");
117: } else if (shellCommand.indexOf("clisp") >= 0) {
118: // clisp -I
119: lisp.setPromptRE(CLISP_PROMPT_PATTERN);
120: lisp.setResetCommand("(sys::debug-unwind)");
121: } else if (shellCommand.equals("/usr/bin/lisp")) {
122: lisp.setPromptRE(CMUCL_PROMPT_PATTERN);
123: lisp.setResetCommand(":q");
124: lisp.setExitCommand("(quit)");
125: } else if (shellCommand.indexOf("sbcl") >= 0) {
126: lisp.setPromptRE(SBCL_PROMPT_PATTERN);
127: lisp.setResetCommand(":abort");
128: lisp.setExitCommand("(quit)");
129: } else if (shellCommand.indexOf("org.armedbear.lisp") >= 0
130: || shellCommand.indexOf("abcl") >= 0) {
131: lisp.setPromptRE(ARMEDBEAR_PROMPT_PATTERN);
132: lisp.setResetCommand(":reset");
133: } else {
134: lisp.setPromptRE(DEFAULT_PROMPT_PATTERN);
135: if (shellCommand.equals("rep")
136: || shellCommand.equals("/usr/bin/rep"))
137: lisp.setExitCommand(",quit");
138: }
139: lisp.needsRenumbering(true);
140: if (Editor.isLispInitialized())
141: LispAPI.invokeLispShellStartupHook(lisp, shellCommand);
142: return lisp;
143: }
144:
145: protected void startProcess() {
146: if (shellCommand == null) {
147: Debug.bug();
148: return;
149: }
150: File initialDirectory = Editor.currentEditor()
151: .getCurrentDirectory();
152: if (initialDirectory == null || initialDirectory.isRemote())
153: initialDirectory = Directories.getUserHomeDirectory();
154: List tokens = Utilities.tokenize(shellCommand);
155: final int tokenCount = tokens.size();
156: String[] cmdArray;
157: int i = 0;
158: if (Utilities.haveJpty()) {
159: cmdArray = new String[tokenCount + 1];
160: cmdArray[i++] = "jpty";
161: } else
162: cmdArray = new String[tokenCount];
163: for (int j = 0; j < tokenCount; j++)
164: cmdArray[i++] = (String) tokens.get(j);
165: Process p = null;
166: try {
167: p = Runtime.getRuntime().exec(cmdArray, null,
168: new java.io.File(initialDirectory.canonicalPath()));
169: setProcess(p);
170: } catch (Throwable t) {
171: setProcess(null);
172: return;
173: }
174: currentDirectory = initialDirectory;
175: startWatcherThread();
176: // See if the process exits right away (meaning jpty couldn't launch
177: // the shell command).
178: try {
179: Thread.sleep(100);
180: } catch (InterruptedException e) {
181: Log.error(e);
182: }
183: // When the process exits, the watcher thread calls setProcess(null),
184: // so check the value of getProcess() here.
185: if (getProcess() == null)
186: return; // Process exited.
187: try {
188: stdin = new OutputStreamWriter(p.getOutputStream());
189: stdoutThread = new StdoutThread(p.getInputStream());
190: stderrThread = new StderrThread(p.getErrorStream());
191: stdoutThread.start();
192: stderrThread.start();
193: readOnly = false;
194: } catch (Throwable t) {
195: Log.error(t);
196: }
197: }
198:
199: protected void initializeHistory() {
200: history = new History("lisp.history", 30);
201: }
202:
203: public void enter() {
204: if (!checkProcess())
205: return;
206: final Editor editor = Editor.currentEditor();
207: Position dot = editor.getDotCopy();
208: if (dot == null)
209: return;
210: if (needsRenumbering)
211: renumber();
212: final Line dotLine = dot.getLine();
213: final Position endOfOutput = getEndOfOutput();
214: if (endOfOutput == null) {
215: // Ignore input before first prompt is displayed.
216: dotLine.setText("");
217: return;
218: }
219: if (dot.isBefore(endOfOutput)) {
220: editor.newlineAndIndent();
221: return; // For now.
222: }
223: final Line promptLine = endOfOutput.getLine();
224: Annotation a = new Annotation(endOfOutput.getOffset());
225: promptLine.setAnnotation(a);
226: promptLine.setFlags(STATE_PROMPT);
227: Position end = getEnd();
228: Position pos = LispMode.findContainingSexp(end);
229: boolean isComplete = (pos == null || pos.isBefore(endOfOutput));
230: if (isComplete) {
231: // Complete sexp.
232: editor.eob();
233: editor.insertLineSeparator();
234: editor.getDotLine().setFlags(0);
235: } else {
236: // Not complete; multiline input.
237: editor.newline();
238: editor.getDotLine().setFlags(STATE_INPUT);
239: }
240: if (needsRenumbering)
241: renumber();
242: editor.moveCaretToDotCol();
243: editor.getDisplay().setReframe(-2);
244: resetUndo();
245: stripEcho = true;
246: if (isComplete) {
247: // No containing sexp. Send input to lisp process.
248: Position begin = endOfOutput;
249: end = editor.getDotCopy();
250: end.setOffset(end.getLineLength());
251: setEndOfOutput(end);
252: Line lineBeforeLastPrompt = promptLine.previous();
253: if (lineBeforeLastPrompt != null) {
254: posBeforeLastPrompt = new Position(
255: lineBeforeLastPrompt, lineBeforeLastPrompt
256: .length());
257: }
258: posEndOfInput = end.copy();
259: String s = new Region(this , begin, end).toString();
260: sendInputToLisp(s);
261: } else
262: indentLineAtDot(editor);
263: }
264:
265: public void resetLisp() {
266: if (resetCommand != null) {
267: Position pos = getEnd();
268: insertString(pos, resetCommand.concat("\n"));
269: if (needsRenumbering())
270: renumber();
271: enforceOutputLimit(Property.SHELL_OUTPUT_LIMIT);
272: posEndOfInput = pos.copy();
273: send(resetCommand);
274: }
275: }
276:
277: protected void stdOutUpdate(final String s) {
278: String prompt;
279: int index = s.lastIndexOf('\n');
280: if (index >= 0)
281: prompt = s.substring(index + 1);
282: else
283: prompt = s;
284: final REMatch match = promptRE.getMatch(prompt);
285: if (match != null) {
286: // Last line of output looks like a prompt.
287: String m = match.toString();
288: if (prompt.startsWith(m)) {
289: if (prompt.substring(m.length()).startsWith(m)) {
290: // Double prompt. Remove one of them.
291: prompt = prompt.substring(m.length());
292: }
293: }
294: }
295: final String output;
296: if (index >= 0)
297: output = s.substring(0, index + 1) + prompt;
298: else
299: output = prompt;
300: Runnable r = new Runnable() {
301: public void run() {
302: Position pos = getEnd();
303: if (pos != null)
304: pos.getLine().setFlags(0); // This value will propagate.
305: if (output.length() > 0) {
306: appendString(output);
307: if (match != null) {
308: Line lineBeforeLastPrompt = getEnd().getLine()
309: .previous();
310: if (lineBeforeLastPrompt != null) {
311: posBeforeLastPrompt = new Position(
312: lineBeforeLastPrompt,
313: lineBeforeLastPrompt.length());
314: }
315: if (isBusy())
316: setBusy(false);
317: }
318: }
319: updateDisplayInAllFrames();
320: resetUndo();
321: }
322: };
323: SwingUtilities.invokeLater(r);
324: }
325:
326: protected void stdErrUpdate(final String s) {
327: Runnable r = new Runnable() {
328: public void run() {
329: appendString(s);
330: updateDisplayInAllFrames();
331: resetUndo();
332: }
333: };
334: SwingUtilities.invokeLater(r);
335: }
336:
337: protected void appendString(String s) {
338: try {
339: lockWrite();
340: } catch (InterruptedException e) {
341: Log.error(e);
342: return;
343: }
344: try {
345: if (slime) {
346: // Slime.
347: if (posEndOfInput == null)
348: posEndOfInput = new Position(getFirstLine(), 0);
349: final Position pos;
350: if (posBeforeLastPrompt != null
351: && posEndOfInput != null) {
352: Position posLastPrompt = new Position(
353: posBeforeLastPrompt.getNextLine(), 0);
354: if (posEndOfInput.isAfter(posLastPrompt)) {
355: // There has been user input since the last prompt.
356: pos = getEnd();
357: } else {
358: pos = posBeforeLastPrompt;
359: }
360: } else
361: pos = getEnd();
362: if (pos != null) {
363: if (pos == posBeforeLastPrompt) {
364: if (s.length() > 0) {
365: if (s.charAt(s.length() - 1) == '\n')
366: s = s.substring(0, s.length() - 1);
367: }
368: if (s.length() > 0 && s.charAt(0) != '\n')
369: insertLineSeparator(pos);
370: }
371: insertString(pos, s);
372: if (needsRenumbering())
373: renumber();
374: enforceOutputLimit(Property.SHELL_OUTPUT_LIMIT);
375: if (pos != posBeforeLastPrompt)
376: setEndOfOutput(pos.copy());
377: } else {
378: // Empty buffer.
379: setText(s);
380: setEndOfOutput(getEnd().copy());
381: }
382: } else {
383: // No slime.
384: Position pos = getEnd();
385: if (pos != null) {
386: insertString(pos, s);
387: if (needsRenumbering())
388: renumber();
389: enforceOutputLimit(Property.SHELL_OUTPUT_LIMIT);
390: setEndOfOutput(pos.copy());
391: } else {
392: setText(s);
393: setEndOfOutput(getEnd().copy());
394: }
395: }
396: } finally {
397: unlockWrite();
398: }
399: }
400:
401: private void indentLineAtDot(Editor editor) {
402: final Line dotLine = editor.getDotLine();
403: if (dotLine.length() > 0)
404: return;
405: try {
406: lockWrite();
407: } catch (InterruptedException e) {
408: Log.error(e);
409: return;
410: }
411: try {
412: getFormatter().parseBuffer();
413: int indent = mode.getCorrectIndentation(dotLine, this );
414: if (indent != getIndentation(dotLine)) {
415: editor.addUndo(SimpleEdit.LINE_EDIT);
416: setIndentation(dotLine, indent);
417: dotLine.setFlags(STATE_INPUT);
418: modified();
419: }
420: if (dotLine.length() > 0) {
421: editor.moveDotToIndentation();
422: editor.moveCaretToDotCol();
423: } else {
424: final Display display = editor.getDisplay();
425: display.setCaretCol(indent - display.getShift());
426: if (getBooleanProperty(Property.RESTRICT_CARET))
427: editor.fillToCaret();
428: }
429: resetUndo(); // Why?
430: } finally {
431: unlockWrite();
432: }
433: }
434:
435: private void sendInputToLisp(String input) {
436: // Save history unless input is very short (e.g. ":q"). Ignore
437: // whitespace at end of line.
438: String trim = input.trim();
439: if (trim.length() > 2) {
440: history.append(trim);
441: history.save();
442: }
443: send(input);
444: }
445:
446: public void dispose() {
447: if (!checkProcess()) {
448: Log.debug("checkProcess returned false");
449: return;
450: }
451: Thread t = new Thread("LispShell dispose") {
452: public void run() {
453: try {
454: stdin.write(3);
455: stdin.flush();
456: stdin.write(exitCommand);
457: stdin.write("\n");
458: stdin.flush();
459: stdin.close();
460: final Process p = getProcess();
461: if (p != null) {
462: p.destroy();
463: p.waitFor();
464: }
465: } catch (IOException e) {
466: Log.error(e);
467: } catch (InterruptedException e) {
468: Log.error(e);
469: }
470: }
471: };
472: t.setPriority(Thread.MIN_PRIORITY);
473: t.setDaemon(true);
474: t.start();
475: }
476:
477: public File getCurrentDirectory() {
478: return currentDirectory;
479: }
480:
481: public File getCompletionDirectory() {
482: return currentDirectory;
483: }
484:
485: public String getFileNameForDisplay() {
486: return title;
487: }
488:
489: public String toString() {
490: return title;
491: }
492:
493: public static void slime() {
494: _slime(getDefaultLispShellCommand(), "slime abcl", false);
495: }
496:
497: public static void slime(String shellCommand) {
498: _slime(shellCommand, "slime ".concat(shellCommand),
499: // Require jpty on Unix platforms.
500: Platform.isPlatformUnix());
501: }
502:
503: private static final void _slime(String shellCommand, String title,
504: boolean requireJpty) {
505: Buffer buffer = findSlime();
506: if (buffer != null) {
507: final Editor editor = Editor.currentEditor();
508: editor.makeNext(buffer);
509: Buffer b = editor.getBuffer();
510: if (b != null && b.isPaired())
511: editor.switchToBuffer(buffer);
512: else
513: editor.activate(buffer);
514: return;
515: }
516: lisp(shellCommand, title, requireJpty, true);
517: }
518:
519: public static void lisp() {
520: lisp(getDefaultLispShellCommand(), "abcl", false, false);
521: }
522:
523: public static void lisp(String shellCommand) {
524: // Require jpty on Unix platforms.
525: lisp(shellCommand, shellCommand, Platform.isPlatformUnix(),
526: false);
527: }
528:
529: private static void lisp(String shellCommand, String title,
530: boolean requireJpty, boolean startSlime) {
531: if (requireJpty && !Utilities.haveJpty()) {
532: MessageDialog.showMessageDialog(JPTY_NOT_FOUND, "Error");
533: return;
534: }
535: if (Platform.isPlatformWindows())
536: if (!Platform.isPlatformWindows5())
537: return;
538: final Editor editor = Editor.currentEditor();
539: // Look for an existing LispShell buffer with the same shell command.
540: Buffer buf = findLisp(title);
541: if (buf == null) {
542: editor.setWaitCursor();
543: buf = createLispShell(shellCommand, title, startSlime);
544: if (buf != null)
545: buf.setBusy(true);
546: editor.setDefaultCursor();
547: } else
548: startSlime = false; // Already started.
549: if (buf != null) {
550: editor.makeNext(buf);
551: Buffer b = editor.getBuffer();
552: if (b != null && b.isPaired())
553: editor.switchToBuffer(buf);
554: else
555: editor.activate(buf);
556: if (startSlime)
557: startSlime(buf);
558: }
559: }
560:
561: private static void startSlime(final Buffer buffer) {
562: Runnable r = new Runnable() {
563: public void run() {
564: try {
565: JLisp
566: .runLispCommand("(sys:load-system-file \"slime-loader.lisp\")");
567: JLisp
568: .runLispCommand("(setq slime::*repl-buffer-name* \""
569: + buffer.getTitle() + "\")");
570: JLisp.runLispCommand("(slime:slime)");
571: } catch (Throwable t) {
572: Log.debug(t);
573: }
574: }
575: };
576: new Thread(r).start();
577: }
578:
579: private static String getDefaultLispShellCommand() {
580: File java = null;
581: File javaHome = File.getInstance(System
582: .getProperty("java.home"));
583: if (javaHome != null && javaHome.isDirectory()) {
584: java = File
585: .getInstance(
586: javaHome,
587: Platform.isPlatformWindows() ? "bin\\java.exe"
588: : "bin/java");
589: if (java != null && !java.isFile())
590: java = null;
591: }
592: // If j was invoked via "java -jar j.jar", use the canonical path
593: // of j.jar.
594: String classPath = System.getProperty("java.class.path");
595: if (classPath.equals("j.jar:.")) // IBM 1.4.0 on Linux
596: classPath = "j.jar";
597: if (classPath.indexOf(LocalFile.getPathSeparatorChar()) < 0) {
598: // Only one component in classpath.
599: String path = classPath;
600: if (Platform.isPlatformWindows())
601: path = path.toLowerCase();
602: if (path.equals("j.jar") || path.endsWith("/j.jar")
603: || path.endsWith("\\j.jar")) {
604: File dir = File.getInstance(System
605: .getProperty("user.dir"));
606: File file = File.getInstance(dir, path);
607: if (file != null && file.isFile())
608: classPath = file.canonicalPath();
609: }
610: }
611: FastStringBuffer sb = new FastStringBuffer();
612: if (java != null) {
613: sb.append('"');
614: sb.append(java.canonicalPath());
615: sb.append('"');
616: String vendor = System.getProperty("java.vendor");
617: if (vendor != null) {
618: if (vendor.indexOf("Sun") >= 0
619: || vendor.indexOf("Blackdown") >= 0) {
620: String vm = System.getProperty("java.vm.name");
621: if (vm != null
622: && vm.toLowerCase().indexOf("server") >= 0)
623: sb.append(" -server");
624: sb.append(" -Xmx128M");
625: if (Platform.isPlatformUnix()) {
626: String lispHome = org.armedbear.lisp.Site
627: .getLispHome();
628: if (lispHome != null) {
629: sb.append(" -Xrs -Djava.library.path=");
630: sb.append(lispHome);
631: sb.append(":/usr/local/lib/abcl");
632: }
633: }
634: } else if (vendor.indexOf("IBM") >= 0) {
635: sb.append(" -Xss512K");
636: sb.append(" -Xmx128M");
637: }
638: }
639: } else
640: sb.append("java");
641: sb.append(" -cp ");
642: sb.append('"');
643: sb.append(classPath);
644: sb.append('"');
645: sb.append(" org.armedbear.lisp.Main");
646: return sb.toString();
647: }
648:
649: public static CommandInterpreter findLisp(String title) {
650: for (BufferIterator it = new BufferIterator(); it.hasNext();) {
651: Buffer b = it.nextBuffer();
652: if (b instanceof CommandInterpreter) {
653: CommandInterpreter comint = (CommandInterpreter) b;
654: if (comint.isLisp()) {
655: if (title == null
656: || title.equals(comint.getTitle()))
657: return comint;
658: }
659: }
660: }
661: return null;
662: }
663:
664: private static final CommandInterpreter findSlime() {
665: for (BufferIterator it = new BufferIterator(); it.hasNext();) {
666: Buffer b = it.nextBuffer();
667: if (b instanceof CommandInterpreter) {
668: CommandInterpreter comint = (CommandInterpreter) b;
669: if (comint.isLisp()) {
670: String title = comint.getTitle();
671: if (title != null && title.startsWith("slime"))
672: return comint;
673: }
674: }
675: }
676: return null;
677: }
678: }
|