001: /*
002: * CommmandInterpreter.java
003: *
004: * Copyright (C) 1998-2004 Peter Graves
005: * $Id: CommandInterpreter.java,v 1.26 2004/09/02 21:25:13 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.RE;
025: import gnu.regexp.REException;
026: import gnu.regexp.REMatch;
027: import gnu.regexp.UncheckedRE;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.OutputStreamWriter;
031: import javax.swing.Icon;
032: import javax.swing.SwingUtilities;
033: import javax.swing.undo.CompoundEdit;
034:
035: public class CommandInterpreter extends Buffer {
036: protected RE promptRE = new UncheckedRE(
037: DEFAULT_SHELL_PROMPT_PATTERN);
038: protected OutputStreamWriter stdin;
039: protected ReaderThread stdoutThread;
040: protected ReaderThread stderrThread;
041: protected History history;
042: protected String input;
043: protected boolean stripEcho;
044: protected String shellCommand;
045:
046: private Position posEndOfOutput;
047:
048: protected CommandInterpreter() {
049: super ();
050: initializeUndo();
051: initializeHistory();
052: }
053:
054: public final String getShellCommand() {
055: return shellCommand;
056: }
057:
058: public boolean isLisp() {
059: return false;
060: }
061:
062: public final boolean isModified() {
063: return false;
064: }
065:
066: protected void initializeHistory() {
067: history = new History(null, 30);
068: }
069:
070: public final RE getPromptRE() {
071: return promptRE;
072: }
073:
074: protected final void setPromptRE(String pattern) {
075: try {
076: promptRE = new RE(pattern);
077: } catch (REException e) {
078: Log.error(e);
079: }
080: }
081:
082: protected final synchronized Position getEndOfOutput() {
083: if (posEndOfOutput == null)
084: return null;
085: if (contains(posEndOfOutput.getLine())) {
086: if (posEndOfOutput.getOffset() > posEndOfOutput
087: .getLineLength())
088: posEndOfOutput
089: .setOffset(posEndOfOutput.getLineLength());
090: return posEndOfOutput;
091: }
092: // The original end-of-output line has been removed from the buffer.
093: RE promptRE = getPromptRE();
094: if (promptRE != null) {
095: Position eob = getEnd();
096: if (eob == null)
097: return null;
098: Line line = eob.getLine();
099: while (line != null) {
100: int flags = line.flags();
101: if (flags == 0 || flags == STATE_PROMPT
102: || flags == STATE_INPUT) {
103: final REMatch match = promptRE.getMatch(line
104: .getText());
105: if (match != null && match.getStartIndex() == 0) {
106: return new Position(line, match.getEndIndex());
107: }
108: }
109: line = line.previous();
110: }
111: }
112: return null;
113: }
114:
115: protected final synchronized void setEndOfOutput(Position pos) {
116: posEndOfOutput = pos;
117: }
118:
119: public Icon getIcon() {
120: return Utilities.getIconFromFile("jpty.png");
121: }
122:
123: public int load() {
124: try {
125: lockWrite();
126: } catch (InterruptedException e) {
127: Log.debug(e);
128: return LOAD_FAILED; // Shouldn't happen.
129: }
130: try {
131: appendLine("");
132: setLoaded(true);
133: } finally {
134: unlockWrite();
135: }
136: return LOAD_COMPLETED;
137: }
138:
139: // Returns true if underlying process is alive and well.
140: protected boolean checkProcess() {
141: return true;
142: }
143:
144: protected void enter() {
145: if (!checkProcess())
146: return;
147: final Editor editor = Editor.currentEditor();
148: final Line dotLine = editor.getDotLine();
149: Position endOfOutput = getEndOfOutput();
150: if (endOfOutput == null) {
151: // Ignore input before first prompt is displayed.
152: dotLine.setText("");
153: return;
154: }
155: if (endOfOutput.getLine() == dotLine) {
156: if (endOfOutput.getOffset() < dotLine.length())
157: input = dotLine.getText().substring(
158: endOfOutput.getOffset());
159: else
160: input = "";
161: } else {
162: // We're not at the end of the buffer.
163: input = stripPrompt(dotLine.getText());
164: }
165: if (input.length() != 0) {
166: history.append(input);
167: history.save();
168: }
169: enter(input);
170: }
171:
172: protected void enter(final String s) {
173: final Editor editor = Editor.currentEditor();
174: Line dotLine = editor.getDotLine();
175: if (dotLine.next() != null) {
176: // Go to end of buffer (if we're not already there) to append input.
177: editor.eob();
178: dotLine = editor.getDotLine();
179:
180: // Keep the prompt, but throw away anything after it.
181: final REMatch match = promptRE.getMatch(dotLine.getText());
182: if (match != null)
183: dotLine.setText(dotLine.substring(0, match
184: .getEndIndex()));
185:
186: // Append s.
187: dotLine.setText(dotLine.getText() + s);
188: }
189: int flags = dotLine.flags();
190: if (flags == 0)
191: dotLine.setFlags(STATE_INPUT);
192: editor.eol();
193: editor.insertLineSeparator();
194: if (needsRenumbering)
195: renumber();
196: editor.getDotLine().setFlags(0);
197: editor.moveCaretToDotCol();
198: editor.getDisplay().setReframe(-2);
199: resetUndo();
200: stripEcho = true;
201: send(s);
202: }
203:
204: protected void send(final String s) {
205: try {
206: stdin.write(s);
207: if (!s.endsWith("\n"))
208: stdin.write("\n");
209: stdin.flush();
210: } catch (IOException e) {
211: Log.error(e);
212: }
213: }
214:
215: protected String stripPrompt(String s) {
216: if (promptRE != null) {
217: REMatch match = promptRE.getMatch(s);
218: if (match != null)
219: return s.substring(match.getEndIndex());
220: }
221: // Look for login name or password prompt.
222: RE re = new UncheckedRE(".*: ?");
223: REMatch match = re.getMatch(s);
224: if (match != null)
225: return s.substring(match.getEndIndex());
226: return s;
227: }
228:
229: protected void escape() {
230: Editor editor = Editor.currentEditor();
231: Position endOfOutput = getEndOfOutput();
232: if (editor.getMark() != null || endOfOutput == null
233: || editor.getDot().isBefore(endOfOutput)) {
234: // There's a marked block, or we're not at the command line.
235: editor.escape();
236: return;
237: }
238: if (editor.escapeInternal())
239: return;
240: CompoundEdit compoundEdit = beginCompoundEdit();
241: editor.addUndo(SimpleEdit.MOVE);
242: editor.moveDotTo(endOfOutput);
243: editor.moveCaretToDotCol();
244: editor.setMark(getEnd());
245: editor.deleteRegion();
246: endCompoundEdit(compoundEdit);
247: // BUG! Undo/redo delete region doesn't preserve markers correctly!
248: resetUndo();
249: }
250:
251: protected void home() {
252: final Editor editor = Editor.currentEditor();
253: if (editor.getDotOffset() == 0)
254: return;
255: editor.addUndo(SimpleEdit.MOVE);
256: editor.beginningOfBlock();
257: int offset = 0;
258: if (promptRE != null) {
259: Line dotLine = editor.getDotLine();
260: if (dotLine.next() == null
261: || dotLine.flags() == STATE_INPUT) {
262: REMatch match = promptRE.getMatch(dotLine.getText());
263: if (match != null)
264: offset = match.getEndIndex();
265: }
266: }
267: // If we're already at the prompt or to the left of it, go to column 0.
268: if (editor.getDotOffset() <= offset)
269: offset = 0;
270: editor.getDot().setOffset(offset);
271: editor.getDisplay().moveCaretToDotCol();
272: }
273:
274: protected void backspace() {
275: Position endOfOutput = getEndOfOutput();
276: if (endOfOutput == null)
277: return;
278: boolean ok = true;
279: final Editor editor = Editor.currentEditor();
280: if (editor.getDotLine() == endOfOutput.getLine()) {
281: if (editor.getDotOffset() <= endOfOutput.getOffset())
282: ok = false;
283: } else {
284: String text = editor.getDotLine().getText();
285: if (promptRE != null) {
286: REMatch match = promptRE.getMatch(text);
287: if (match != null) {
288: if (editor.getDotOffset() <= match.getEndIndex())
289: ok = false;
290: }
291: }
292: }
293: if (ok)
294: editor.backspace();
295: }
296:
297: private void previousInput() {
298: getInputFromHistory(-1);
299: }
300:
301: private void nextInput() {
302: getInputFromHistory(1);
303: }
304:
305: private String currentInput;
306:
307: private void getInputFromHistory(int direction) {
308: if (getEndOfOutput() == null) {
309: // No prompt yet.
310: return;
311: }
312: final Editor editor = Editor.currentEditor();
313: final Line dotLine = editor.getDotLine();
314: if (dotLine.next() != null) {
315: editor.status("Not at command prompt");
316: return;
317: }
318: if (editor.getLastCommand() != COMMAND_HISTORY) {
319: history.reset();
320: Position begin = getEndOfOutput().copy();
321: Position end = getEnd();
322: Region r = new Region(editor.getBuffer(), begin, end);
323: currentInput = r.toString();
324: }
325: String s;
326: while (true) {
327: s = direction < 0 ? history.getPrevious() : history
328: .getNext();
329: if (s == null)
330: break;
331: s = s.trim();
332: if (s.equals(currentInput))
333: continue;
334: if (currentInput.length() == 0
335: || s.startsWith(currentInput))
336: break;
337: }
338: if (s != null) {
339: CompoundEdit compoundEdit = beginCompoundEdit();
340: editor.addUndo(SimpleEdit.MOVE);
341: editor.setDot(getEndOfOutput().copy());
342: editor.setMark(getEnd());
343: editor.deleteRegion();
344: editor.addUndo(SimpleEdit.INSERT_STRING);
345: editor.insertStringInternal(s);
346: editor.moveCaretToDotCol();
347: endCompoundEdit(compoundEdit);
348: // BUG! Undo/redo delete region doesn't preserve markers correctly!
349: resetUndo();
350: }
351: for (Line line = getEndOfOutput().getLine(); line != null; line = line
352: .next())
353: line.setFlags(STATE_INPUT);
354: editor.setCurrentCommand(COMMAND_HISTORY);
355: }
356:
357: protected void appendString(String s) {
358: try {
359: lockWrite();
360: } catch (InterruptedException e) {
361: Log.error(e);
362: return;
363: }
364: try {
365: Position pos = getEnd();
366: if (pos != null) {
367: insertString(pos, s);
368: if (needsRenumbering())
369: renumber();
370: enforceOutputLimit(Property.SHELL_OUTPUT_LIMIT);
371: setEndOfOutput(pos.copy());
372: } else {
373: setText(s);
374: setEndOfOutput(getEnd().copy());
375: }
376: } finally {
377: unlockWrite();
378: }
379: }
380:
381: protected void updateDisplayInAllFrames() {
382: for (EditorIterator it = new EditorIterator(); it.hasNext();) {
383: Editor ed = it.nextEditor();
384: if (ed.getBuffer() == this ) {
385: ed.eob();
386: ed.getDisplay().setReframe(-2);
387: ed.setUpdateFlag(REPAINT);
388: ed.updateDisplay();
389: }
390: }
391: }
392:
393: protected void sendChar(int c) {
394: final Editor editor = Editor.currentEditor();
395: final Line dotLine = editor.getDotLine();
396: if (dotLine.next() == null)
397: dotLine.setFlags(STATE_INPUT);
398: try {
399: stdin.write(c);
400: stdin.flush();
401: } catch (IOException e) {
402: Log.error(e);
403: }
404: }
405:
406: protected String stdOutFilter(String s) {
407: return removeEcho(s);
408: }
409:
410: protected void stdOutUpdate(final String s) {
411: Runnable r = new Runnable() {
412: public void run() {
413: appendString(s);
414: updateDisplayInAllFrames();
415: resetUndo();
416: }
417: };
418: SwingUtilities.invokeLater(r);
419: }
420:
421: protected String stdErrFilter(String s) {
422: return removeEcho(s);
423: }
424:
425: protected void stdErrUpdate(final String s) {
426: Runnable r = new Runnable() {
427: public void run() {
428: appendString(s);
429: updateDisplayInAllFrames();
430: resetUndo();
431: }
432: };
433: SwingUtilities.invokeLater(r);
434: }
435:
436: private String removeEcho(String s) {
437: if (stripEcho && input != null && s.startsWith(input)) {
438: int begin = input.length();
439: if (s.length() > begin && s.charAt(begin) == '\r')
440: ++begin;
441: if (s.length() > begin && s.charAt(begin) == '\n')
442: ++begin;
443: s = s.substring(begin);
444: // Strip echo only once per command line.
445: stripEcho = false;
446: }
447: return s;
448: }
449:
450: protected class StdoutThread extends ReaderThread {
451: public StdoutThread(InputStream stdout) {
452: super (stdout);
453: }
454:
455: public String filter(String s) {
456: return stdOutFilter(s);
457: }
458:
459: public void update(String s) {
460: stdOutUpdate(s);
461: }
462: }
463:
464: protected class StderrThread extends ReaderThread {
465: public StderrThread(InputStream stderr) {
466: super (stderr);
467: }
468:
469: public String filter(String s) {
470: return stdErrFilter(s);
471: }
472:
473: public void update(String s) {
474: stdErrUpdate(s);
475: }
476: }
477:
478: // Commands.
479: public static void shellEnter() {
480: final Buffer buffer = Editor.currentEditor().getBuffer();
481: if (buffer instanceof CommandInterpreter)
482: ((CommandInterpreter) buffer).enter();
483: }
484:
485: public static void shellEscape() {
486: final Buffer buffer = Editor.currentEditor().getBuffer();
487: if (buffer instanceof CommandInterpreter)
488: ((CommandInterpreter) buffer).escape();
489: }
490:
491: public static void shellHome() {
492: final Buffer buffer = Editor.currentEditor().getBuffer();
493: if (buffer instanceof CommandInterpreter)
494: ((CommandInterpreter) buffer).home();
495: }
496:
497: public static void shellBackspace() {
498: final Buffer buffer = Editor.currentEditor().getBuffer();
499: if (buffer instanceof CommandInterpreter)
500: ((CommandInterpreter) buffer).backspace();
501: }
502:
503: public static void shellPreviousInput() {
504: final Buffer buffer = Editor.currentEditor().getBuffer();
505: if (buffer instanceof CommandInterpreter)
506: ((CommandInterpreter) buffer).previousInput();
507: }
508:
509: public static void shellNextInput() {
510: final Buffer buffer = Editor.currentEditor().getBuffer();
511: if (buffer instanceof CommandInterpreter)
512: ((CommandInterpreter) buffer).nextInput();
513: }
514:
515: public static void shellPreviousPrompt() {
516: findPrompt(-1);
517: }
518:
519: public static void shellNextPrompt() {
520: findPrompt(1);
521: }
522:
523: private static final void findPrompt(int direction) {
524: final Editor editor = Editor.currentEditor();
525: final Buffer buffer = editor.getBuffer();
526: if (buffer instanceof CommandInterpreter) {
527: Position dot = editor.getDot();
528: if (dot != null) {
529: Line line = direction > 0 ? dot.getLine().next() : dot
530: .getLine().previous();
531: RE promptRE = ((CommandInterpreter) buffer)
532: .getPromptRE();
533: if (promptRE != null) {
534: while (line != null) {
535: int flags = line.flags();
536: if (flags == STATE_PROMPT
537: || flags == STATE_INPUT) {
538: final REMatch match = promptRE
539: .getMatch(line.getText());
540: if (match != null
541: && match.getStartIndex() == 0) {
542: Position pos = new Position(line, match
543: .getEndIndex());
544: editor.moveDotTo(pos);
545: return;
546: }
547: }
548: line = direction > 0 ? line.next() : line
549: .previous();
550: }
551: }
552: }
553: }
554: }
555: }
|