KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > armedbear > j > Shell


1 /*
2  * Shell.java
3  *
4  * Copyright (C) 1998-2004 Peter Graves
5  * $Id: Shell.java,v 1.33 2004/09/19 14:13:59 piso Exp $
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */

21
22 package org.armedbear.j;
23
24 import gnu.regexp.REMatch;
25 import java.io.IOException JavaDoc;
26 import java.io.OutputStreamWriter JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.StringTokenizer JavaDoc;
29 import javax.swing.SwingUtilities JavaDoc;
30 import javax.swing.undo.CompoundEdit JavaDoc;
31
32 public class Shell extends CommandInterpreter implements Constants
33 {
34     protected static final String JavaDoc JPTY_NOT_FOUND =
35         "Unable to start shell process (jpty not found in PATH)";
36
37     private Process JavaDoc process;
38     private String JavaDoc command; // First token on command line.
39
private boolean promptIsStderr = true;
40     private File oldDir;
41     private File currentDir;
42     private File initialDir;
43     private boolean cygnify;
44
45     protected Shell()
46     {
47         type = TYPE_SHELL;
48         mode = Editor.getModeList().getMode(SHELL_MODE);
49         formatter = mode.getFormatter(this);
50         setInitialized(true);
51     }
52
53     protected Shell(String JavaDoc shellCommand)
54     {
55         this();
56         this.shellCommand = shellCommand;
57         if (shellCommand != null && shellCommand.indexOf("tcsh") >= 0)
58             promptIsStderr = false;
59     }
60
61     protected Shell(String JavaDoc shellCommand, Mode mode)
62     {
63         type = TYPE_SHELL;
64         this.shellCommand = shellCommand;
65         this.mode = mode;
66         formatter = mode.getFormatter(this);
67         setInitialized(true);
68     }
69
70     protected synchronized Process JavaDoc getProcess()
71     {
72         return process;
73     }
74
75     protected synchronized void setProcess(Process JavaDoc p)
76     {
77         process = p;
78     }
79
80     private static final String JavaDoc getDefaultShellCommand()
81     {
82         String JavaDoc s =
83             Editor.preferences().getStringProperty(Property.SHELL_FILE_NAME);
84         if (s != null && s.length() > 0)
85             return s;
86         return Platform.isPlatformWindows() ? "cmd.exe" : "bash -i";
87     }
88
89     protected void initializeHistory()
90     {
91         history = new History("shell.history", 30);
92     }
93
94     private static Shell createShell(String JavaDoc shellCommand)
95     {
96         if (shellCommand == null) {
97             Debug.bug();
98             return null;
99         }
100         Shell shell = new Shell(shellCommand);
101         shell.startProcess();
102         if (shell.getProcess() == null) {
103             Editor.getBufferList().remove(shell);
104             String JavaDoc message =
105                 "Unable to start shell process \"" + shell.shellCommand + "\"";
106             MessageDialog.showMessageDialog(message, "Error");
107             return null;
108         }
109         shell.needsRenumbering = true;
110         return shell;
111     }
112
113     protected void startProcess()
114     {
115         if (shellCommand == null) {
116             Debug.bug();
117             return;
118         }
119         if (Platform.isPlatformWindows())
120             if (shellCommand.toLowerCase().indexOf("cmd.exe") < 0)
121                 cygnify = true;
122         // Only set initialDir the first time we run, so that if we restart
123
// this shell, it will start up in the same directory each time.
124
if (initialDir == null) {
125             initialDir = Editor.currentEditor().getCurrentDirectory();
126             if (initialDir == null || initialDir.isRemote())
127                 initialDir = Directories.getUserHomeDirectory();
128         }
129         // Shell command may contain a space (e.g. "bash -i").
130
StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(shellCommand);
131         String JavaDoc[] cmdArray;
132         int i = 0;
133         if (Utilities.haveJpty()) {
134             cmdArray = new String JavaDoc[st.countTokens() + 1];
135             cmdArray[i++] = "jpty";
136         } else
137             cmdArray = new String JavaDoc[st.countTokens()];
138         while (st.hasMoreTokens())
139             cmdArray[i++] = st.nextToken();
140         Process JavaDoc p = null;
141         try {
142             p = Runtime.getRuntime().exec(cmdArray, null,
143                                           new java.io.File JavaDoc(initialDir.canonicalPath()));
144             setProcess(p);
145         }
146         catch (Throwable JavaDoc t) {
147             setProcess(null);
148             return;
149         }
150         currentDir = initialDir;
151         startWatcherThread();
152         // See if the process exits right away (meaning jpty couldn't launch
153
// the shell command).
154
try {
155             Thread.sleep(100);
156         }
157         catch (InterruptedException JavaDoc e) {
158             Log.error(e);
159         }
160         // When the process exits, the watcher thread calls setProcess(null),
161
// so check the value of getProcess() here.
162
if (getProcess() == null)
163             return; // Process exited.
164
setPromptRE(Editor.preferences().getStringProperty(
165             Property.SHELL_PROMPT_PATTERN));
166         try {
167             stdin = new OutputStreamWriter JavaDoc(p.getOutputStream());
168             stdoutThread = new StdoutThread(p.getInputStream());
169             stderrThread = new StderrThread(p.getErrorStream());
170             stdoutThread.start();
171             stderrThread.start();
172             readOnly = false;
173         }
174         catch (Throwable JavaDoc t) {
175             Log.error(t);
176         }
177     }
178
179     public void dispose()
180     {
181         if (!checkProcess()) {
182             Log.debug("checkProcess returned false");
183             return;
184         }
185         Thread JavaDoc thread = new Thread JavaDoc("shell dispose") {
186             public void run()
187             {
188                 try {
189                     stdin.write(3);
190                     stdin.flush();
191                     stdin.write("exit\n");
192                     stdin.flush();
193                     stdin.close();
194                     final Process JavaDoc p = getProcess();
195                     if (p != null) {
196                         p.destroy();
197                         p.waitFor();
198                     }
199                 }
200                 catch (IOException JavaDoc e) {
201                     Log.error(e);
202                 }
203                 catch (InterruptedException JavaDoc e) {
204                     Log.error(e);
205                 }
206             }
207         };
208         thread.setDaemon(true);
209         thread.start();
210     }
211
212     protected void enter(final String JavaDoc s)
213     {
214         super.enter(s);
215         // If it's a local shell (i.e. not telnet or ssh), keep track of the
216
// current directory.
217
if (type == TYPE_SHELL) {
218             ShellTokenizer st = new ShellTokenizer(s);
219             command = s.trim();
220             String JavaDoc arg = null;
221             if (st.hasMoreTokens()) {
222                 command = st.nextToken();
223                 if (st.hasMoreTokens())
224                     arg = st.nextToken();
225             }
226             if (command.equals("cd")) {
227                 if (arg == null)
228                     changeDirectory(Utilities.getUserHome());
229                 else if (arg.equals("-")) {
230                     if (oldDir != null)
231                         changeDirectory(oldDir.canonicalPath());
232                 } else
233                     changeDirectory(arg);
234             } else if (command.equals("pushd"))
235                 changeDirectory(arg);
236         }
237     }
238
239     protected boolean checkProcess()
240     {
241         Process JavaDoc p = getProcess();
242         if (p == null)
243             return false;
244         if (Utilities.isProcessAlive(p))
245             return true;
246         // Not alive.
247
setProcess(null);
248         readOnly = true;
249         resetUndo();
250         return false;
251     }
252
253     protected void startWatcherThread()
254     {
255         Thread JavaDoc thread = new Thread JavaDoc("shell watcher") {
256             public void run()
257             {
258                 try {
259                     Process JavaDoc p = getProcess();
260                     if (p != null)
261                         p.waitFor();
262                     setProcess(null);
263                     if (stdoutThread != null)
264                         stdoutThread.join();
265                     if (stderrThread != null)
266                         stderrThread.join();
267                 }
268                 catch (InterruptedException JavaDoc e) {
269                     Log.error(e);
270                 }
271                 Runnable JavaDoc processExitedRunnable = new Runnable JavaDoc() {
272                     public void run()
273                     {
274                         appendString("\nProcess exited\n");
275                         setBusy(false);
276                         updateDisplayInAllFrames();
277                     }
278                 };
279                 if (stderrThread != null)
280                     SwingUtilities.invokeLater(processExitedRunnable);
281             }
282         };
283         thread.setDaemon(true);
284         thread.start();
285     }
286
287     private void tab()
288     {
289         final Editor editor = Editor.currentEditor();
290         if (editor.getMark() != null)
291             return;
292         final Line dotLine = editor.getDotLine();
293         final String JavaDoc dotLineText = dotLine.getText();
294         final REMatch match = promptRE.getMatch(dotLineText);
295         if (match == null)
296             return; // Not at prompt.
297
final String JavaDoc prompt = match.toString();
298         final String JavaDoc userInput = dotLineText.substring(match.getEndIndex());
299         if (userInput.length() == 0)
300             return; // Nothing to complete.
301
final ShellTokenizer st = new ShellTokenizer(userInput);
302         final String JavaDoc prefix = st.lastToken();
303         if (prefix == null)
304             return; // Nothing to complete.
305
final String JavaDoc toBeCompleted = unescape(prefix);
306         final Completion completion =
307             new Completion(currentDir, toBeCompleted, shellCommand);
308         final String JavaDoc toBeInserted = completion.toString();
309         if (!toBeInserted.equals(prefix)) {
310             CompoundEdit JavaDoc compoundEdit = beginCompoundEdit();
311             editor.addUndo(SimpleEdit.MOVE);
312             editor.getDot().setOffset(dotLineText.lastIndexOf(prefix));
313             // Remove prefix from line.
314
final String JavaDoc head = dotLine.substring(0, editor.getDotOffset());
315             final String JavaDoc tail = dotLine.substring(editor.getDotOffset() + prefix.length());
316             editor.addUndo(SimpleEdit.LINE_EDIT);
317             dotLine.setText(head.concat(tail));
318             // Insert completion.
319
editor.addUndo(SimpleEdit.INSERT_STRING);
320             editor.insertStringInternal(toBeInserted);
321             // Move dot past inserted string.
322
editor.moveCaretToDotCol();
323             endCompoundEdit(compoundEdit);
324             Editor.updateInAllEditors(dotLine);
325         } else {
326             final List JavaDoc completions = completion.getCompletions();
327             final int size = completions.size();
328             if (size > 0) {
329                 dotLine.setFlags(STATE_INPUT);
330                 editor.insertLineSeparator();
331                 for (int i = 0; i < size; i++) {
332                     String JavaDoc s = (String JavaDoc) completions.get(i);
333                     s = unescape(s);
334                     int index = s.lastIndexOf('/', s.length()-2);
335                     if (index >= 0)
336                         s = s.substring(index+1);
337                     editor.insertStringInternal(s);
338                     editor.getDotLine().setFlags(STATE_OUTPUT);
339                     editor.insertLineSeparator();
340                 }
341                 if (prompt != null)
342                     editor.insertStringInternal(prompt);
343                 editor.insertStringInternal(userInput);
344                 editor.getDotLine().setFlags(STATE_INPUT);
345                 editor.eob();
346                 editor.getDisplay().setReframe(-2);
347                 resetUndo();
348             }
349         }
350     }
351
352     private void updateDirectory(String JavaDoc output)
353     {
354         if (command != null) {
355             String JavaDoc s = output;
356             int index = s.indexOf('\r');
357             if (index >= 0)
358                 s = s.substring(0, index);
359             index = s.indexOf('\n');
360             if (index >= 0)
361                 s = s.substring(0, index);
362             if (command.equals("pwd") || command.equals("cd")) {
363                 changeDirectory(s);
364             } else if (command.equals("popd")) {
365                 // BUG! Directory names with embedded spaces will not be
366
// handled correctly!
367
index = s.indexOf(' ');
368                 if (index >= 0)
369                     s = s.substring(0, index);
370                 changeDirectory(s);
371             }
372         }
373     }
374
375     private void changeDirectory(String JavaDoc s)
376     {
377         if (s != null) {
378             s = unescape(s).trim();
379             if (s.length() > 0) {
380                 char c = s.charAt(0);
381                 if (c == '\'' || c == '"') {
382                     s = s.substring(1);
383                     final int length = s.length();
384                     if (length > 0 && s.charAt(length-1) == c)
385                         s = s.substring(0, length-1);
386                 }
387             } else
388                 s = Utilities.getUserHome();
389             if (cygnify) {
390                 if (!s.startsWith(".."))
391                     s = Utilities.uncygnify(s);
392             }
393             File dir = File.getInstance(currentDir, s);
394             if (dir != null && dir.isDirectory()) {
395                 oldDir = currentDir;
396                 currentDir = dir;
397                 for (EditorIterator it = new EditorIterator(); it.hasNext();) {
398                     Editor ed = it.nextEditor();
399                     if (ed.getBuffer() == this)
400                         ed.updateLocation();
401                 }
402             }
403         }
404     }
405
406     private String JavaDoc unescape(String JavaDoc s)
407     {
408         // Is '\' an escape character?
409
boolean backslashIsEscape = Platform.isPlatformUnix() || cygnify;
410
411         FastStringBuffer sb = new FastStringBuffer(s.length());
412         char quoteChar = 0;
413         final int limit = s.length();
414         for (int i = 0; i < limit; i++) {
415             char c = s.charAt(i);
416             if (quoteChar != 0) {
417                 if (c == quoteChar)
418                     quoteChar = 0;
419                 else
420                     sb.append(c);
421             } else if (c == '\'' || c == '"') {
422                 quoteChar = c;
423             } else if (backslashIsEscape && c == '\\') {
424                 if (i < limit - 1)
425                     sb.append(s.charAt(++i));
426             } else
427                 sb.append(c);
428         }
429         return sb.toString();
430     }
431
432     public String JavaDoc getFileNameForDisplay()
433     {
434         return (currentDir != null) ? currentDir.canonicalPath() : "";
435     }
436
437     // For the buffer list.
438
public String JavaDoc toString()
439     {
440         return shellCommand != null ? shellCommand : "";
441     }
442
443     public File getCurrentDirectory()
444     {
445         return currentDir;
446     }
447
448     public File getCompletionDirectory()
449     {
450         return currentDir;
451     }
452
453     protected void appendString(String JavaDoc s)
454     {
455         if (s.indexOf(0x1b) >= 0) {
456             // Strip escape sequences used for ls colorization.
457
int limit = s.length();
458             FastStringBuffer sb = new FastStringBuffer(limit);
459             int i = 0;
460             while (i < limit) {
461                 char c = s.charAt(i++);
462                 if (c == 0x1b) {
463                     // Skip escaped chars through 'm'.
464
while (i < limit && s.charAt(i++) != 'm')
465                         ;
466                 } else
467                     sb.append(c);
468             }
469             s = sb.toString();
470         }
471         super.appendString(s);
472     }
473
474     protected void stdOutUpdate(final String JavaDoc s)
475     {
476         Runnable JavaDoc r = new Runnable JavaDoc() {
477             public void run()
478             {
479                 if (s.length() > 0) {
480                     updateDirectory(s);
481                     appendString(s);
482                 }
483                 updateLineFlags();
484                 updateDisplayInAllFrames();
485                 resetUndo();
486                 checkPasswordPrompt();
487             }
488         };
489         SwingUtilities.invokeLater(r);
490     }
491
492     protected void stdErrUpdate(final String JavaDoc s)
493     {
494         if (promptIsStderr) {
495             REMatch match = promptRE.getMatch(s);
496             if (match != null) {
497                 // This looks like the prompt.
498
// Give stdout a chance to finish.
499
try {
500                     Thread.sleep(100);
501                 }
502                 catch (InterruptedException JavaDoc e) {
503                     Log.error(e);
504                 }
505             }
506         }
507         Runnable JavaDoc r = new Runnable JavaDoc() {
508             public void run()
509             {
510                 appendString(s);
511                 updateLineFlags();
512                 updateDisplayInAllFrames();
513                 resetUndo();
514             }
515         };
516         SwingUtilities.invokeLater(r);
517     }
518
519     protected void updateLineFlags()
520     {
521         Debug.assertTrue(SwingUtilities.isEventDispatchThread());
522         Position endOfOutput = getEndOfOutput();
523         if (endOfOutput == null)
524             return;
525         final Line last = endOfOutput.getLine();
526         if (isPasswordPrompt(last)) {
527             last.setFlags(STATE_PASSWORD_PROMPT);
528             return;
529         }
530         if (last.flags() != STATE_INPUT)
531             last.setFlags(0);
532         // Look at the next-to-last line.
533
final Line nextToLast = last.previous();
534         // For now, this is a hard-coded test for Mikol's prompt. It should
535
// really use a configurable regexp.
536
if (nextToLast != null && nextToLast.getText().startsWith("| ")) {
537             // Next-to-last line looks like first line of 2-line prompt.
538
// See if the last line looks like the second line of the prompt.
539
REMatch match = promptRE.getMatch(last.getText());
540             if (match != null)
541                 nextToLast.setFlags(STATE_PROMPT);
542         }
543     }
544
545     private boolean isPasswordPrompt(Line line)
546     {
547         final String JavaDoc text = line.trim();
548         if (text.startsWith("Enter passphrase") && text.endsWith(":"))
549             return true;
550         if (text.toLowerCase().endsWith("password:"))
551             return true;
552         if (text.equals("Response:"))
553             return true;
554         return false;
555     }
556
557     protected void checkPasswordPrompt()
558     {
559         Position endOfOutput = getEndOfOutput();
560         if (endOfOutput != null && isPasswordPrompt(endOfOutput.getLine())) {
561             if (!Editor.getBufferList().contains(this))
562                 return;
563             String JavaDoc password =
564                 PasswordDialog.showPasswordDialog(Editor.currentEditor(),
565                                                   "Password:", "Password");
566             if (password != null) {
567                 try {
568                     stdin.write(password + "\n");
569                     stdin.flush();
570                     setEndOfOutput(new Position(getEnd()));
571                 }
572                 catch (IOException JavaDoc e) {
573                     Log.error(e);
574                 }
575             }
576         }
577     }
578
579     public static void shell()
580     {
581         shell(getDefaultShellCommand());
582     }
583
584     public static void shell(String JavaDoc shellCommand)
585     {
586         if (shellCommand == null) {
587             Debug.bug();
588             return;
589         }
590         if (!Editor.checkExperimental())
591             return;
592         if (Platform.isPlatformWindows()) {
593             if (!Platform.isPlatformWindows5())
594                 return;
595         } else {
596             // Unix.
597
if (!Utilities.haveJpty()) {
598                 MessageDialog.showMessageDialog(JPTY_NOT_FOUND, "Error");
599                 return;
600             }
601         }
602         // Look for existing shell buffer.
603
Buffer buf = null;
604         for (BufferIterator it = new BufferIterator(); it.hasNext();) {
605             Buffer b = it.nextBuffer();
606             if (b instanceof Shell) {
607                 if (shellCommand.equals(((Shell)b).shellCommand)) {
608                     buf = b;
609                     break;
610                 }
611             }
612         }
613         if (buf != null) {
614             Shell shell = (Shell) buf;
615             if (shell.getProcess() == null)
616                 shell.startProcess();
617         } else
618             buf = createShell(shellCommand);
619         if (buf != null) {
620             final Editor editor = Editor.currentEditor();
621             editor.makeNext(buf);
622             editor.switchToBuffer(buf);
623         }
624     }
625
626     public static void shellTab()
627     {
628         final Buffer buffer = Editor.currentEditor().getBuffer();
629         if (buffer instanceof Shell && !(buffer instanceof RemoteShell))
630             ((Shell)buffer).tab();
631     }
632
633     public static void shellInterrupt()
634     {
635         final Buffer buffer = Editor.currentEditor().getBuffer();
636         if (buffer instanceof Shell)
637             ((Shell)buffer).sendChar(3);
638     }
639 }
640
Popular Tags