PageRenderTime 132ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/bundles/plugins-trunk/Console/console/SystemShell.java

#
Java | 1201 lines | 1061 code | 57 blank | 83 comment | 63 complexity | fa44e66869b28d39763d5da59cfd1747 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. package console;
  2. // {{{ Imports
  3. import java.io.*;
  4. import java.util.*;
  5. import java.util.regex.*;
  6. import org.gjt.sp.jedit.BeanShell;
  7. import org.gjt.sp.jedit.Buffer;
  8. import org.gjt.sp.jedit.MiscUtilities;
  9. import org.gjt.sp.jedit.OperatingSystem;
  10. import org.gjt.sp.jedit.View;
  11. import org.gjt.sp.jedit.jEdit;
  12. import org.gjt.sp.jedit.browser.VFSBrowser;
  13. import org.gjt.sp.util.Log;
  14. import org.gjt.sp.util.StringList;
  15. // }}}
  16. /**
  17. * A SystemShell is shared across all instances of Console.
  18. * It has own environment (variables), and executes system statements in
  19. * a shell which resembles in terms of user interface, something that is
  20. * a cross between the Windows "cmd.exe" and the Linux bash shell, so it
  21. * should be easy to use for both.
  22. *
  23. * It manages a mapping of Console to ConsoleState objects, where the ConsoleState
  24. * manages the actual ConsoleProcess and the state of that shell.
  25. *
  26. * When SystemShell executes something, the process itself is started indirectly by
  27. * ProcessRunner.exec().
  28. * @author 1999, 2005 Slava Pestov
  29. * @author 2006, 2009 Alan Ezust
  30. */
  31. // {{{ class SystemShell
  32. public class SystemShell extends Shell
  33. {
  34. // {{{ private members
  35. private String userHome;
  36. /** common shell variables shared across all instances of the System Shell. */
  37. Map<String, String> variables;
  38. /** The state of each console System Shell instance. */
  39. private Hashtable<Console, ConsoleState> consoleStateMap;
  40. static private final char dosSlash = 127;
  41. /** Map of aliases */
  42. private Hashtable<String, String> aliases;
  43. /** Built in commands */
  44. private Hashtable<String, SystemShellBuiltIn> commands;
  45. private boolean initialized;
  46. private byte[] lineSep;
  47. // }}}
  48. // {{{ SystemShell constructor
  49. public SystemShell()
  50. {
  51. super("System");
  52. lineSep = toBytes(System.getProperty("line.separator"));
  53. consoleStateMap = new Hashtable<Console, ConsoleState>();
  54. userHome = System.getProperty("user.home");
  55. if (File.separator.equals("\\"))
  56. {
  57. userHome = userHome.replace("\\", "\\\\");
  58. }
  59. } // }}}
  60. // {{{ public methods
  61. // {{{ openConsole() method
  62. /**
  63. * Called when a Console dockable first selects this shell.
  64. *
  65. * @since Console 4.0.2
  66. */
  67. public void openConsole(Console console)
  68. {
  69. ConsoleState cs = new ConsoleState();
  70. consoleStateMap.put(console, cs);
  71. if (jEdit.getBooleanProperty("console.rememberCWD" )) {
  72. String propName = "console.cwd." + console.getId();
  73. String val = jEdit.getProperty(propName, "null");
  74. if (!val.equals("null")) cs.currentDirectory = val;
  75. }
  76. } // }}}
  77. // {{{ closeConsole() method
  78. /**
  79. * Called when a Console dockable is closed.
  80. *
  81. * @since Console 4.0.2
  82. */
  83. public void closeConsole(Console console)
  84. {
  85. ConsoleProcess process = getConsoleState(console).process;
  86. if (process != null)
  87. process.stop();
  88. consoleStateMap.remove(console);
  89. } // }}}
  90. // {{{ printInfoMessage() method
  91. public void printInfoMessage(Output output)
  92. {
  93. if (jEdit.getBooleanProperty("console.shell.info.toggle"))
  94. output.print(null, jEdit.getProperty("console.shell.info"));
  95. } // }}}
  96. // {{{ printPrompt()
  97. /**
  98. * Prints a prompt to the specified console.
  99. *
  100. * @param output
  101. * The output
  102. */
  103. public void printPrompt(Console console, Output output)
  104. {
  105. ConsoleState cstate = getConsoleState(console);
  106. String currentDirectory;
  107. if (cstate == null)
  108. currentDirectory = System.getProperty("user.dir");
  109. else
  110. {
  111. currentDirectory = cstate.currentDirectory;
  112. }
  113. output.writeAttrs(ConsolePane.colorAttributes(console.getPlainColor()), jEdit
  114. .getProperty("console.shell.prompt",
  115. new String[] { MiscUtilities.abbreviate(currentDirectory)}) + " ");
  116. }
  117. // }}}
  118. // {{{ executeBuiltIn() method
  119. public void executeBuiltIn(Console console, Output output, Output error, String command,
  120. Vector<String> args)
  121. {
  122. SystemShellBuiltIn builtIn = (SystemShellBuiltIn) commands.get(command);
  123. if (builtIn == null)
  124. {
  125. String[] pp = { command };
  126. error.print(console.getErrorColor(), jEdit.getProperty(
  127. "console.shell.unknown-builtin", pp));
  128. }
  129. else
  130. {
  131. builtIn.execute(console, output, error, args);
  132. }
  133. } // }}}
  134. // {{{ executeInDir()
  135. public void executeInDir(final Console console, String input, final Output output, Output error,
  136. String command, String dir)
  137. {
  138. ConsoleState state = getConsoleState(console);
  139. String cwd = state.currentDirectory;
  140. state.setCurrentDirectory(console, dir);
  141. this.execute(console, input, output, error, command);
  142. state.setCurrentDirectory(console, cwd);
  143. } // }}}
  144. // {{{ execute()
  145. public void execute(final Console console, String input, final Output output, Output error,
  146. String command)
  147. {
  148. if (error == null)
  149. error = output;
  150. ConsoleState state = getConsoleState(console);
  151. // If a process is running under this shell and the pipe to its
  152. // stdin is open, treat this command line as a input line for
  153. // the process to make interactive processes usable as in
  154. // general shells.
  155. if (state.process != null)
  156. {
  157. PipedOutputStream out = state.process.getPipeOutput();
  158. if (out != null)
  159. {
  160. try
  161. {
  162. out.write(toBytes(command));
  163. out.write(lineSep);
  164. out.flush();
  165. }
  166. catch (IOException e)
  167. {
  168. Log.log (Log.ERROR, this, "execute()", e);
  169. }
  170. return;
  171. }
  172. }
  173. // comments, for possible future scripting support
  174. if (command.startsWith("#"))
  175. {
  176. output.commandDone();
  177. return;
  178. }
  179. // lazily initialize aliases and variables
  180. init();
  181. Vector<String> args = parse(command);
  182. // will be null if the command is an empty string
  183. if (args == null)
  184. {
  185. output.commandDone();
  186. return;
  187. }
  188. args = preprocess(console.getView(), console, args);
  189. String commandName = args.elementAt(0);
  190. // check for drive letter changedirs (windows only)
  191. if (OperatingSystem.isWindows()
  192. && commandName.endsWith(":")) {
  193. char driveLetter = commandName.charAt(0);
  194. args = state.changeDrive(driveLetter);
  195. commandName = args.elementAt(0);
  196. }
  197. if (commandName.charAt(0) == '%')
  198. {
  199. // a console built-in
  200. args.removeElementAt(0);
  201. executeBuiltIn(console, output, error, commandName, args);
  202. output.commandDone();
  203. return;
  204. }
  205. // if current working directory doesn't exist, print an error.
  206. String cwd = state.currentDirectory;
  207. if (!new File(cwd).exists())
  208. {
  209. output.print(console.getErrorColor(), jEdit.getProperty(
  210. "console.shell.error.working-dir", new String[] { cwd }));
  211. output.commandDone();
  212. // error.commandDone();
  213. return;
  214. }
  215. String fullPath = MiscUtilities.constructPath(cwd, commandName);
  216. // Java resolves this relative to user.dir, not
  217. // the directory we pass to exec()...
  218. if (commandName.startsWith("./") || commandName.startsWith("." + File.separator))
  219. {
  220. args.setElementAt(fullPath, 0);
  221. }
  222. if (new File(fullPath).isDirectory() && args.size() == 1)
  223. {
  224. args.setElementAt(fullPath, 0);
  225. executeBuiltIn(console, output, error, "%cd", args);
  226. output.commandDone();
  227. // error.commandDone();
  228. }
  229. else
  230. {
  231. boolean foreground;
  232. if (args.elementAt(args.size() - 1).equals("&"))
  233. {
  234. // run in background
  235. args.removeElementAt(args.size() - 1);
  236. foreground = false;
  237. output.commandDone();
  238. // error.commandDone();
  239. }
  240. else
  241. {
  242. // run in foreground
  243. foreground = true;
  244. }
  245. String[] _args = new String[args.size()];
  246. args.copyInto(_args);
  247. state.currentDirectory = cwd;
  248. final ConsoleProcess proc = new ConsoleProcess(console, output, _args,
  249. variables, state, foreground);
  250. /* If startup failed its no longer running */
  251. if (foreground && proc.isRunning())
  252. {
  253. console.getErrorSource().clear();
  254. state.process = proc;
  255. }
  256. /*
  257. * Check if we were doing a "run command with selection
  258. * as input"
  259. */
  260. if (input != null)
  261. {
  262. Log.log(Log.DEBUG, this, "sending input to system shell: "+input);
  263. OutputStream out = proc.getPipeOutput();
  264. if (out != null)
  265. {
  266. try
  267. {
  268. out.write(toBytes(input));
  269. out.close();
  270. }
  271. catch (IOException e)
  272. {
  273. Log.log (Log.ERROR, this, "execute.pipeout", e);
  274. }
  275. }
  276. }
  277. }
  278. } // }}}
  279. // {{{ stop() method
  280. public void stop(Console console)
  281. {
  282. ConsoleState consoleState = getConsoleState(console);
  283. if (consoleState == null)
  284. return;
  285. ConsoleProcess process = consoleState.process;
  286. if (process != null)
  287. process.stop();
  288. else
  289. {
  290. console.getOutput().print(console.getErrorColor(),
  291. jEdit.getProperty("console.shell.noproc"));
  292. }
  293. } // }}}
  294. // {{{ waitFor() method
  295. /**
  296. * Waits for currently running Console processes to finish execution.
  297. *
  298. * @return true if all was successful (i.e. the error status code was 0)
  299. */
  300. public boolean waitFor(Console console)
  301. {
  302. ConsoleState consoleState = getConsoleState(console);
  303. if (consoleState == null)
  304. return true;
  305. ConsoleProcess process = consoleState.process;
  306. if (process != null)
  307. {
  308. try
  309. {
  310. return (process.waitFor() == 0);
  311. }
  312. catch (InterruptedException e)
  313. {
  314. }
  315. return process.getExitStatus() == 0;
  316. }
  317. else
  318. return true;
  319. } // }}}
  320. // {{{ endOfFile() method
  321. /**
  322. * Sends an end of file.
  323. *
  324. * @param console
  325. * The console
  326. */
  327. public void endOfFile(Console console)
  328. {
  329. ConsoleState state = getConsoleState(console);
  330. if (state.process != null)
  331. {
  332. console.getOutput().writeAttrs(
  333. ConsolePane.colorAttributes(console.getInfoColor()), "^D\n");
  334. PipedOutputStream out = state.process.getPipeOutput();
  335. if (out != null)
  336. {
  337. try
  338. {
  339. out.close();
  340. }
  341. catch (IOException e)
  342. {
  343. }
  344. }
  345. }
  346. } // }}}
  347. // {{{ detach() method
  348. /**
  349. * Detaches the currently running process.
  350. *
  351. * @param console
  352. * The console
  353. */
  354. public void detach(Console console)
  355. {
  356. SystemShell.ConsoleState state = getConsoleState(console);
  357. ConsoleProcess process = state.process;
  358. if (process == null)
  359. {
  360. console.getOutput().print(console.getErrorColor(),
  361. jEdit.getProperty("console.shell.noproc"));
  362. return;
  363. }
  364. process.detach();
  365. } // }}}
  366. // {{{ getCompletions() method
  367. /**
  368. * Returns possible completions for the specified command.
  369. *
  370. * @param console
  371. * The console instance
  372. * @param command
  373. * The command
  374. * @since Console 3.6
  375. */
  376. public CompletionInfo getCompletions(Console console, String command)
  377. {
  378. // lazily initialize aliases and variables
  379. init();
  380. View view = console.getView();
  381. String currentDirectory = (console == null ? System.getProperty("user.dir")
  382. : getConsoleState(console).currentDirectory);
  383. final String fileDelimiters = "=\'\" \\" + File.pathSeparator;
  384. String lastArg;
  385. CompletionInfo completionInfo = new CompletionInfo();
  386. completionInfo.offset = 0;
  387. if (File.separatorChar == '\\')
  388. {
  389. // Escaping impossible
  390. String lastArgEscaped = (String) parse(command).lastElement();
  391. // We want to allow completion on the forward slash too
  392. lastArg = lastArgEscaped.replace('/', File.separatorChar);
  393. if (lastArg.startsWith("\\")) {
  394. ConsoleState state = getConsoleState(console);
  395. char drive = state.currentDirectory.charAt(0);
  396. lastArg = drive + ":\\" + lastArg.substring(1);
  397. completionInfo.offset = 2;
  398. }
  399. }
  400. else
  401. {
  402. // Escaping possible
  403. /* I use findLastArgument and then unescape instead of
  404. (String)parse(command).lastElement() because there's
  405. no way to get parse(String) to also return the original
  406. length of the unescaped argument, which we need to calculate
  407. the completion offset. */
  408. String lastArgEscaped = findLastArgument(command, fileDelimiters);
  409. lastArg = unescape(lastArgEscaped, fileDelimiters);
  410. }
  411. completionInfo.offset += command.length() - lastArg.length();
  412. Matcher m = homeDir.matcher(lastArg);
  413. lastArg = m.replaceFirst(System.getProperty("user.home"));
  414. if (completionInfo.offset == 0)
  415. {
  416. completionInfo.completions = (String[]) getCommandCompletions(view,
  417. currentDirectory, lastArg).toArray(new String[0]);
  418. }
  419. else
  420. {
  421. completionInfo.completions = (String[]) getFileCompletions(view,
  422. currentDirectory, lastArg, false).toArray(new String[0]);
  423. }
  424. // On systems where the file separator is the same as the escape
  425. // character (Windows), it's impossible to do escaping properly,
  426. // so we just assume that escaping is not needed (which is true
  427. // for windows).
  428. if (File.separatorChar != '\\')
  429. {
  430. for (int i = 0; i < completionInfo.completions.length; i++)
  431. {
  432. completionInfo.completions[i] = escape(
  433. completionInfo.completions[i], fileDelimiters);
  434. }
  435. }
  436. // We add a double quote at the beginning of any completion with
  437. // special characters because the current argument parsing
  438. // (done in parse()) uses StreamTokenizer, which only handles
  439. // escaping if it's done within a string. The purpose here is
  440. // dual - to get parse() to unescape the characters and to get
  441. // it to recognize the string as a single argument.
  442. // On systems where we don't support escaping, we do this
  443. // only for the 2nd purpose.
  444. boolean isDoubleQuoted = (completionInfo.offset > 0)
  445. && (command.charAt(completionInfo.offset - 1) == '\"');
  446. if (!isDoubleQuoted)
  447. {
  448. final String specialCharacters = (File.separatorChar == '\\') ? " "
  449. : fileDelimiters;
  450. for (int i = 0; i < completionInfo.completions.length; i++)
  451. {
  452. String result = completionInfo.completions[i];
  453. if (containsCharacters(result, specialCharacters))
  454. result = "\"" + result;
  455. completionInfo.completions[i] = result;
  456. }
  457. }
  458. return completionInfo;
  459. }
  460. // }}}
  461. // {{{ expandVariables()
  462. static final String varPatternString = "([$%])([a-zA-Z0-9_]+)(\\1?)";
  463. static final String varPatternString2 = "([$%])\\{([^}]+)\\}";
  464. static final Pattern varPattern = Pattern.compile(varPatternString);
  465. static final Pattern varPattern2 = Pattern.compile(varPatternString2);
  466. static final Pattern homeDir = Pattern.compile("^~");
  467. /**
  468. * returns a string after it's been processed by jedit's internal
  469. * command processor
  470. *
  471. * @param view
  472. * A view corresponding to this console's state.
  473. * @param arg
  474. * A single argument on the command line
  475. * @return A string after it's been processed, with variables replaced
  476. * with their values.
  477. */
  478. String expandVariables(View view, String arg)
  479. {
  480. // StringBuffer buf = new StringBuffer();
  481. String varName = null;
  482. // Expand homedir
  483. Matcher m = homeDir.matcher(arg);
  484. if (m.find())
  485. {
  486. arg = m.replaceFirst(userHome);
  487. }
  488. m = varPattern.matcher(arg);
  489. if (!m.find())
  490. {
  491. m = varPattern2.matcher(arg);
  492. if (!m.find())
  493. return arg;
  494. }
  495. varName = m.group(2);
  496. String expansion = getVariableValue(view, varName);
  497. if (expansion == null)
  498. {
  499. varName = varName.toUpperCase();
  500. expansion = getVariableValue(view, varName);
  501. }
  502. if (expansion != null)
  503. {
  504. expansion = expansion.replace("\\", "\\\\");
  505. return m.replaceFirst(expansion);
  506. }
  507. return arg;
  508. }
  509. // }}}
  510. // {{{ getVariableValue() method
  511. public String getVariableValue(View view, String varName)
  512. {
  513. init();
  514. if (view == null)
  515. return variables.get(varName);
  516. // ick, I don't like this at all... multiple consoles/view?
  517. ConsoleState cs = getConsoleState(ConsolePlugin.getConsole(view));
  518. String expansion;
  519. // Expand some special variables
  520. Buffer buffer = view.getBuffer();
  521. // What is this for?
  522. if (varName.equals("$") || varName.equals("%"))
  523. expansion = varName;
  524. else if (varName.equals("d"))
  525. {
  526. expansion = MiscUtilities.getParentOfPath(buffer.getPath());
  527. if (expansion.endsWith("/") || expansion.endsWith(File.separator))
  528. {
  529. expansion = expansion.substring(0, expansion.length() - 1);
  530. }
  531. }
  532. else if (varName.equals("l"))
  533. {
  534. int line = view.getTextArea().getCaretLine()+1;
  535. expansion = new Integer(line).toString();
  536. }
  537. else if (varName.equals("u"))
  538. {
  539. expansion = buffer.getPath();
  540. if (!MiscUtilities.isURL(expansion))
  541. {
  542. // FIXME: This is totally incorrect! make a proper URL.
  543. expansion = "file:/" + expansion.replace(File.separatorChar, '/');
  544. }
  545. }
  546. else if (varName.equals("f"))
  547. expansion = buffer.getPath();
  548. else if (varName.equalsIgnoreCase("pwd")) {
  549. expansion = cs.currentDirectory;
  550. }
  551. else if (varName.equals("n"))
  552. expansion = buffer.getName();
  553. else if (varName.equals("c"))
  554. expansion = ConsolePlugin.getClassName(buffer);
  555. else if (varName.equals("PKG"))
  556. {
  557. expansion = ConsolePlugin.getPackageName(buffer);
  558. if (expansion == null)
  559. expansion = "";
  560. }
  561. else if (varName.equals("ROOT"))
  562. expansion = ConsolePlugin.getPackageRoot(buffer);
  563. else if (varName.equals("p"))
  564. expansion = BeanShell.eval(view, BeanShell.getNameSpace(),
  565. "getProjectRoot(view, buffer)").toString();
  566. else if (varName.equals("BROWSER_DIR"))
  567. {
  568. VFSBrowser browser = (VFSBrowser) view.getDockableWindowManager()
  569. .getDockable("vfs.browser");
  570. if (browser == null)
  571. expansion = null;
  572. else
  573. expansion = browser.getDirectory();
  574. }
  575. else
  576. expansion = (String) variables.get(varName);
  577. return expansion;
  578. } // }}}
  579. // {{{ getAliases() method
  580. public Hashtable<String, String> getAliases()
  581. {
  582. init();
  583. return aliases;
  584. } // }}}
  585. // }}}
  586. // {{{ methods
  587. // {{{ getConsoleState() method
  588. ConsoleState getConsoleState(Console console)
  589. {
  590. ConsoleState retval = (ConsoleState) consoleStateMap.get(console);
  591. if (retval == null)
  592. openConsole(console);
  593. return (ConsoleState) consoleStateMap.get(console);
  594. } // }}}
  595. // {{{ getVariables() method
  596. Map<String, String> getVariables()
  597. {
  598. init();
  599. return variables;
  600. } // }}}
  601. // {{{ propertiesChanged() method
  602. static void propertiesChanged()
  603. {
  604. // aliases = null;
  605. // variables = null;
  606. // next time execute() is called, init() will reload everything
  607. } // }}}
  608. // {{{ toBytes() method
  609. private static byte[] toBytes(String str)
  610. {
  611. try
  612. {
  613. return str.getBytes(jEdit.getProperty("console.encoding"));
  614. }
  615. catch (UnsupportedEncodingException e)
  616. {
  617. Log.log (Log.ERROR, SystemShell.class, "toBytes()", e);
  618. return null;
  619. }
  620. } // }}}
  621. // {{{ init() method
  622. private void init()
  623. {
  624. if (initialized)
  625. return;
  626. initialized = true;
  627. initCommands();
  628. initAliases();
  629. initVariables();
  630. } // }}}
  631. // {{{ initCommands() method
  632. private void initCommands()
  633. {
  634. commands = new Hashtable<String, SystemShellBuiltIn>();
  635. commands.put("%alias", new SystemShellBuiltIn.alias());
  636. commands.put("%aliases", new SystemShellBuiltIn.aliases());
  637. commands.put("%browse", new SystemShellBuiltIn.browse());
  638. commands.put("%cd", new SystemShellBuiltIn.cd());
  639. commands.put("%clear", new SystemShellBuiltIn.clear());
  640. commands.put("%dirstack", new SystemShellBuiltIn.dirstack());
  641. commands.put("%echo", new SystemShellBuiltIn.echo());
  642. commands.put("%edit", new SystemShellBuiltIn.edit());
  643. commands.put("%env", new SystemShellBuiltIn.env());
  644. commands.put("%help", new SystemShellBuiltIn.help());
  645. commands.put("%kill", new SystemShellBuiltIn.kill());
  646. commands.put("%popd", new SystemShellBuiltIn.popd());
  647. commands.put("%pushd", new SystemShellBuiltIn.pushd());
  648. commands.put("%printwd", new SystemShellBuiltIn.pwd());
  649. commands.put("%run", new SystemShellBuiltIn.run());
  650. commands.put("%set", new SystemShellBuiltIn.set());
  651. commands.put("%unalias", new SystemShellBuiltIn.unalias());
  652. commands.put("%unset", new SystemShellBuiltIn.unset());
  653. commands.put("%version", new SystemShellBuiltIn.version());
  654. } // }}}
  655. // {{{ initAliases() method
  656. private void initAliases()
  657. {
  658. aliases = new Hashtable<String,String>();
  659. ProcessRunner pr = ProcessRunner.getProcessRunner();
  660. pr.setUpDefaultAliases(aliases);
  661. // some built-ins can be invoked without the % prefix
  662. aliases.put("cd", "%cd");
  663. aliases.put("pushd", "%pushd");
  664. aliases.put("popd", "%popd");
  665. aliases.put("pwd", "%printwd");
  666. aliases.put("aliases", "%aliases");
  667. aliases.put("alias", "%alias");
  668. aliases.put("ver", "%version");
  669. aliases.put("-", "%cd -");
  670. /* run ant without adornments to make error parsing easier */
  671. aliases.put("ant", "ant -emacs");
  672. // load aliases from properties
  673. String alias;
  674. int i = 0;
  675. while ((alias = jEdit.getProperty("console.shell.alias." + i)) != null)
  676. {
  677. aliases.put(alias, jEdit.getProperty("console.shell.alias." + i
  678. + ".expansion"));
  679. i++;
  680. }
  681. } // }}}
  682. // {{{ initVariables() method
  683. private void initVariables()
  684. {
  685. variables = new HashMap<String, String>();
  686. variables.putAll(System.getenv());
  687. if (File.separator.equals("\\"))
  688. {
  689. Map<String, String> upcased = new HashMap<String, String>();
  690. for (String key : variables.keySet())
  691. {
  692. upcased.put(key.toUpperCase(), variables.get(key));
  693. }
  694. variables = upcased;
  695. }
  696. if (jEdit.getJEditHome() != null)
  697. variables.put("JEDIT_HOME", jEdit.getJEditHome());
  698. if (jEdit.getSettingsDirectory() != null)
  699. variables.put("JEDIT_SETTINGS", jEdit.getSettingsDirectory());
  700. // for the sake of Unix programs that try to be smart
  701. variables.put("TERM", "dumb");
  702. // load variables from properties
  703. String varname;
  704. int i = 0;
  705. while((varname = jEdit.getProperty("console.shell.variable." + i)) != null) {
  706. variables.put(varname, jEdit.getProperty("console.shell.variable." + i + ".value")); i++;
  707. }
  708. } // }}}
  709. // {{{ parse() method
  710. /**
  711. * Convert a command into a vector of arguments.
  712. */
  713. private Vector<String> parse(String command)
  714. {
  715. Vector<String> args = new Vector<String>();
  716. // We replace \ with a non-printable char because
  717. // StreamTokenizer handles \ specially, which causes
  718. // problems on Windows as \ is the file separator
  719. // there.
  720. // After parsing is done, the non printable char is
  721. // changed to \ once again.
  722. // StreamTokenizer needs a way to disable backslash
  723. // handling...
  724. if (File.separatorChar == '\\')
  725. command = command.replace('\\', dosSlash);
  726. StreamTokenizer st = new StreamTokenizer(new StringReader(command));
  727. st.resetSyntax();
  728. st.wordChars('!', 255);
  729. st.whitespaceChars(0, ' ');
  730. st.quoteChar('"');
  731. st.quoteChar('\'');
  732. try
  733. {
  734. loop: for (;;)
  735. {
  736. switch (st.nextToken())
  737. {
  738. case StreamTokenizer.TT_EOF:
  739. break loop;
  740. case StreamTokenizer.TT_WORD:
  741. case '"':
  742. case '\'':
  743. if (File.separatorChar == '\\')
  744. args.addElement(st.sval.replace(dosSlash, '\\'));
  745. else
  746. args.addElement(st.sval);
  747. break;
  748. }
  749. }
  750. }
  751. catch (IOException io)
  752. {
  753. // won't happen
  754. }
  755. if (args.size() == 0)
  756. return null;
  757. else
  758. return args;
  759. } // }}}
  760. // {{{ preprocess() method
  761. /**
  762. * Expand aliases, variables and globs.
  763. *
  764. * @return a new vector of arguments after vars have been substituted.
  765. */
  766. private Vector<String> preprocess(View view, Console console, Vector<String> args)
  767. {
  768. Vector<String> newArgs = new Vector<String>();
  769. // expand aliases
  770. String commandName = args.elementAt(0);
  771. String expansion = aliases.get(commandName);
  772. if (expansion != null)
  773. {
  774. Vector<String> expansionArgs = parse(expansion);
  775. for (int i = 0; i < expansionArgs.size(); i++)
  776. {
  777. expandGlobs(view, newArgs, (String) expansionArgs.elementAt(i));
  778. }
  779. }
  780. else
  781. expandGlobs(view, newArgs, commandName);
  782. // add remaining arguments
  783. for (int i = 1; i < args.size(); i++)
  784. expandGlobs(view, newArgs, (String) args.elementAt(i));
  785. return newArgs;
  786. } // }}}
  787. // {{{ expandGlobs() method
  788. /**
  789. * @param arg -
  790. * a single input argument
  791. * @param args -
  792. * an output variable where we place resulting expanded
  793. * args
  794. *
  795. */
  796. private void expandGlobs(View view, Vector<String> args, String arg)
  797. {
  798. // XXX: to do
  799. args.addElement(expandVariables(view, arg));
  800. } // }}}
  801. // {{{ getFileCompletions() method
  802. private List<String> getFileCompletions(View view, String currentDirName, String typedFilename,
  803. boolean directoriesOnly)
  804. {
  805. String expandedTypedFilename = expandVariables(view, typedFilename);
  806. int lastSeparatorIndex = expandedTypedFilename.lastIndexOf(File.separator);
  807. // The directory part of what the user typed, including the file
  808. // separator.
  809. String typedDirName = lastSeparatorIndex == -1 ? "" : expandedTypedFilename
  810. .substring(0, lastSeparatorIndex + 1);
  811. // The file typed by the user.
  812. File typedFile = new File(expandedTypedFilename).isAbsolute() ? new File(
  813. expandedTypedFilename) : new File(currentDirName, expandedTypedFilename);
  814. boolean directory = expandedTypedFilename.endsWith(File.separator)
  815. || expandedTypedFilename.length() == 0;
  816. // The parent directory of the file typed by the user (or itself
  817. // if it's already a directory).
  818. File dir = directory ? typedFile : typedFile.getParentFile();
  819. // The filename part of the file typed by the user, or "" if
  820. // it's a directory.
  821. String fileName = directory ? "" : typedFile.getName();
  822. // The list of files we're going to try to match
  823. String[] filenames = dir.list();
  824. if ((filenames == null) || (filenames.length == 0))
  825. return null;
  826. boolean isOSCaseSensitive = ProcessRunner.getProcessRunner().isCaseSensitive();
  827. StringList matchingFilenames = new StringList();
  828. // int matchingFilenamesCount = 0;
  829. String matchedString = isOSCaseSensitive ? fileName : fileName.toLowerCase();
  830. for (int i = 0; i < filenames.length; i++)
  831. {
  832. String matchedAgainst = isOSCaseSensitive ? filenames[i] : filenames[i]
  833. .toLowerCase();
  834. if (matchedAgainst.startsWith(matchedString))
  835. {
  836. StringBuffer match = new StringBuffer();
  837. File matchFile = new File(dir, filenames[i]);
  838. if (directoriesOnly && !matchFile.isDirectory())
  839. continue;
  840. match.append(typedDirName + filenames[i]);
  841. // Add a separator at the end if it's a
  842. // directory
  843. if (matchFile.isDirectory() && match.charAt(match.length()-1) != File.separatorChar)
  844. match.append(File.separator);
  845. matchingFilenames.add(match.toString());
  846. }
  847. }
  848. return matchingFilenames;
  849. } // }}}
  850. // {{{ getCommandCompletions() method
  851. private List<String> getCommandCompletions(View view, String currentDirName, String command)
  852. {
  853. StringList list = new StringList();
  854. Iterator<String> iter = commands.keySet().iterator();
  855. while (iter.hasNext())
  856. {
  857. String cmd = (String) iter.next();
  858. if (cmd.startsWith(command))
  859. list.add(cmd);
  860. }
  861. iter = aliases.keySet().iterator();
  862. while (iter.hasNext())
  863. {
  864. String cmd = (String) iter.next();
  865. if (cmd.startsWith(command))
  866. list.add(cmd);
  867. }
  868. list.addAll(getFileCompletions(view, currentDirName, command, false));
  869. return list;
  870. } // }}}
  871. // {{{ findLastArgument() method
  872. /**
  873. * Returns the last argument in the given command by using the given
  874. * delimiters. The delimiters can be escaped.
  875. */
  876. private static String findLastArgument(String command, String delimiters)
  877. {
  878. int i = command.length() - 1;
  879. while (i >= 0)
  880. {
  881. char c = command.charAt(i);
  882. if (delimiters.indexOf(c) != -1)
  883. {
  884. if ((i == 0) || (command.charAt(i - 1) != '\\'))
  885. break;
  886. else
  887. i--;
  888. }
  889. i--;
  890. }
  891. return command.substring(i + 1);
  892. } // }}}
  893. // {{{ unescape() method
  894. /**
  895. * Unescapes the given delimiters in the given string.
  896. */
  897. private static String unescape(String s, String delimiters)
  898. {
  899. StringBuffer buf = new StringBuffer(s.length());
  900. int i = s.length() - 1;
  901. while (i >= 0)
  902. {
  903. char c = s.charAt(i);
  904. buf.append(c);
  905. if (delimiters.indexOf(c) != -1)
  906. {
  907. if (s.charAt(i - 1) == '\\')
  908. i--;
  909. }
  910. i--;
  911. }
  912. return buf.reverse().toString();
  913. } // }}}
  914. // {{{ escape() method
  915. /**
  916. * Escapes the given delimiters in the given string.
  917. */
  918. private static String escape(String s, String delimiters)
  919. {
  920. StringBuffer buf = new StringBuffer();
  921. int length = s.length();
  922. for (int i = 0; i < length; i++)
  923. {
  924. char c = s.charAt(i);
  925. if (delimiters.indexOf(c) != -1)
  926. buf.append('\\');
  927. buf.append(c);
  928. }
  929. return buf.toString();
  930. } // }}}
  931. // {{{ containsWhitespace() method
  932. /**
  933. * Returns <code>true</code> if the first string contains any of the
  934. * characters in the second string. Returns <code>false</code>
  935. * otherwise.
  936. */
  937. private static boolean containsCharacters(String s, String characters)
  938. {
  939. int stringLength = s.length();
  940. for (int i = 0; i < stringLength; i++)
  941. if (characters.indexOf(s.charAt(i)) != -1)
  942. return true;
  943. return false;
  944. }
  945. // }}}
  946. // }}}
  947. // {{{ ConsoleState inner class
  948. /**
  949. * A SystemShell is a singleton instance - one per plugin. There are a
  950. * number of ConsoleStates, one for each Console instance.
  951. *
  952. * The ConsoleState contains information that the SystemShell needs to
  953. * know "where it is". This includes: ConsoleProcess process, String
  954. * currentDirectory, String lastDirectory, Stack<String>
  955. * directoryStack.
  956. */
  957. static class ConsoleState
  958. {
  959. // {{{ ConsoleState members
  960. private ConsoleProcess process;
  961. private ConsoleProcess lastProcess;
  962. /* used only for windows, to keep track of current directories
  963. * for each drive letter
  964. */
  965. private HashMap<Character, String> driveDirectories = null;
  966. String currentDirectory = System.getProperty("user.dir");
  967. String lastDirectory = System.getProperty("user.dir");
  968. Stack<String> directoryStack = new Stack<String>();
  969. // }}}
  970. // {{{ setProcess method
  971. void setProcess(ConsoleProcess cp)
  972. {
  973. if (process != null)
  974. lastProcess = process;
  975. process = cp;
  976. } // }}}
  977. // {{{ getProcess
  978. ConsoleProcess getProcess()
  979. {
  980. return process;
  981. } // }}}
  982. // {{{ getLastProcess method
  983. ConsoleProcess getLastProcess()
  984. {
  985. if (process != null)
  986. return process;
  987. return lastProcess;
  988. } // }}}
  989. // {{{ gotoLastDirectory method
  990. void gotoLastDirectory(Console console)
  991. {
  992. setCurrentDirectory(console, lastDirectory);
  993. } // }}}
  994. // {{{ changeDrive()
  995. Vector<String> changeDrive(char driveLetter) {
  996. driveLetter = Character.toUpperCase(driveLetter);
  997. Vector<String> retval = new Vector<String>();
  998. retval.add("%cd");
  999. char curDrive = Character.toUpperCase(currentDirectory.charAt(0));
  1000. if (driveDirectories == null) driveDirectories = new HashMap<Character, String>();
  1001. driveDirectories.put(Character.valueOf(curDrive), currentDirectory);
  1002. String path = driveLetter + ":" + File.separator;
  1003. if (driveDirectories.containsKey(Character.valueOf(driveLetter))) {
  1004. path = driveDirectories.get(Character.valueOf(driveLetter));
  1005. }
  1006. retval.add(path);
  1007. return retval;
  1008. } // }}}
  1009. // {{{ setCurrentDirectory()
  1010. void setCurrentDirectory(Console console, String newDir)
  1011. {
  1012. String[] pp = { newDir };
  1013. File file = new File(newDir);
  1014. if (!file.isAbsolute())
  1015. file = new File(currentDirectory, newDir);
  1016. if (!file.exists())
  1017. {
  1018. console.getOutput().print(console.getErrorColor(),
  1019. jEdit.getProperty("console.shell.cd.error", pp));
  1020. }
  1021. else if (!file.isDirectory())
  1022. {
  1023. console.getOutput().print(console.getErrorColor(),
  1024. jEdit.getProperty("console.shell.cd.file", pp));
  1025. }
  1026. else
  1027. {
  1028. lastDirectory = currentDirectory;
  1029. try
  1030. {
  1031. currentDirectory = file.getCanonicalPath();
  1032. if (jEdit.getBooleanProperty("console.rememberCWD"))
  1033. {
  1034. String propName = "console.cwd." + console.getId();
  1035. jEdit.setProperty(propName, currentDirectory);
  1036. }
  1037. }
  1038. catch (IOException ioe)
  1039. {
  1040. Log.log (Log.ERROR, this, "setCurrentDirectory()", ioe);
  1041. }
  1042. }
  1043. } // }}}
  1044. } // }}}
  1045. } // }}}