1 21 22 package org.armedbear.j; 23 24 import gnu.regexp.REMatch; 25 import java.io.IOException ; 26 import java.io.OutputStreamWriter ; 27 import java.util.List ; 28 import java.util.StringTokenizer ; 29 import javax.swing.SwingUtilities ; 30 import javax.swing.undo.CompoundEdit ; 31 32 public class Shell extends CommandInterpreter implements Constants 33 { 34 protected static final String JPTY_NOT_FOUND = 35 "Unable to start shell process (jpty not found in PATH)"; 36 37 private Process process; 38 private String command; 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 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 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 getProcess() 71 { 72 return process; 73 } 74 75 protected synchronized void setProcess(Process p) 76 { 77 process = p; 78 } 79 80 private static final String getDefaultShellCommand() 81 { 82 String 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 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 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 if (initialDir == null) { 125 initialDir = Editor.currentEditor().getCurrentDirectory(); 126 if (initialDir == null || initialDir.isRemote()) 127 initialDir = Directories.getUserHomeDirectory(); 128 } 129 StringTokenizer st = new StringTokenizer (shellCommand); 131 String [] cmdArray; 132 int i = 0; 133 if (Utilities.haveJpty()) { 134 cmdArray = new String [st.countTokens() + 1]; 135 cmdArray[i++] = "jpty"; 136 } else 137 cmdArray = new String [st.countTokens()]; 138 while (st.hasMoreTokens()) 139 cmdArray[i++] = st.nextToken(); 140 Process p = null; 141 try { 142 p = Runtime.getRuntime().exec(cmdArray, null, 143 new java.io.File (initialDir.canonicalPath())); 144 setProcess(p); 145 } 146 catch (Throwable t) { 147 setProcess(null); 148 return; 149 } 150 currentDir = initialDir; 151 startWatcherThread(); 152 try { 155 Thread.sleep(100); 156 } 157 catch (InterruptedException e) { 158 Log.error(e); 159 } 160 if (getProcess() == null) 163 return; setPromptRE(Editor.preferences().getStringProperty( 165 Property.SHELL_PROMPT_PATTERN)); 166 try { 167 stdin = new OutputStreamWriter (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 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 thread = new Thread ("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 p = getProcess(); 195 if (p != null) { 196 p.destroy(); 197 p.waitFor(); 198 } 199 } 200 catch (IOException e) { 201 Log.error(e); 202 } 203 catch (InterruptedException e) { 204 Log.error(e); 205 } 206 } 207 }; 208 thread.setDaemon(true); 209 thread.start(); 210 } 211 212 protected void enter(final String s) 213 { 214 super.enter(s); 215 if (type == TYPE_SHELL) { 218 ShellTokenizer st = new ShellTokenizer(s); 219 command = s.trim(); 220 String 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 p = getProcess(); 242 if (p == null) 243 return false; 244 if (Utilities.isProcessAlive(p)) 245 return true; 246 setProcess(null); 248 readOnly = true; 249 resetUndo(); 250 return false; 251 } 252 253 protected void startWatcherThread() 254 { 255 Thread thread = new Thread ("shell watcher") { 256 public void run() 257 { 258 try { 259 Process 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 e) { 269 Log.error(e); 270 } 271 Runnable processExitedRunnable = new Runnable () { 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 dotLineText = dotLine.getText(); 294 final REMatch match = promptRE.getMatch(dotLineText); 295 if (match == null) 296 return; final String prompt = match.toString(); 298 final String userInput = dotLineText.substring(match.getEndIndex()); 299 if (userInput.length() == 0) 300 return; final ShellTokenizer st = new ShellTokenizer(userInput); 302 final String prefix = st.lastToken(); 303 if (prefix == null) 304 return; final String toBeCompleted = unescape(prefix); 306 final Completion completion = 307 new Completion(currentDir, toBeCompleted, shellCommand); 308 final String toBeInserted = completion.toString(); 309 if (!toBeInserted.equals(prefix)) { 310 CompoundEdit compoundEdit = beginCompoundEdit(); 311 editor.addUndo(SimpleEdit.MOVE); 312 editor.getDot().setOffset(dotLineText.lastIndexOf(prefix)); 313 final String head = dotLine.substring(0, editor.getDotOffset()); 315 final String tail = dotLine.substring(editor.getDotOffset() + prefix.length()); 316 editor.addUndo(SimpleEdit.LINE_EDIT); 317 dotLine.setText(head.concat(tail)); 318 editor.addUndo(SimpleEdit.INSERT_STRING); 320 editor.insertStringInternal(toBeInserted); 321 editor.moveCaretToDotCol(); 323 endCompoundEdit(compoundEdit); 324 Editor.updateInAllEditors(dotLine); 325 } else { 326 final List 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 s = (String ) 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 output) 353 { 354 if (command != null) { 355 String 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 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 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 unescape(String s) 407 { 408 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 getFileNameForDisplay() 433 { 434 return (currentDir != null) ? currentDir.canonicalPath() : ""; 435 } 436 437 public String 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 s) 454 { 455 if (s.indexOf(0x1b) >= 0) { 456 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 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 s) 475 { 476 Runnable r = new Runnable () { 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 s) 493 { 494 if (promptIsStderr) { 495 REMatch match = promptRE.getMatch(s); 496 if (match != null) { 497 try { 500 Thread.sleep(100); 501 } 502 catch (InterruptedException e) { 503 Log.error(e); 504 } 505 } 506 } 507 Runnable r = new Runnable () { 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 final Line nextToLast = last.previous(); 534 if (nextToLast != null && nextToLast.getText().startsWith("| ")) { 537 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 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 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 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 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 if (!Utilities.haveJpty()) { 598 MessageDialog.showMessageDialog(JPTY_NOT_FOUND, "Error"); 599 return; 600 } 601 } 602 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 |