/plugins/Console/tags/release-4-2-5-2/console/SystemShell.java

# · Java · 1046 lines · 677 code · 144 blank · 225 comment · 142 complexity · 42fb0088b8505970918b33048b1c161f MD5 · raw file

  1. /*
  2. * SystemShell.java - Executes OS commands
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 1999, 2005 Slava Pestov
  7. *
  8. *
  9. * This program is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU General Public License
  11. * as published by the Free Software Foundation; either version 2
  12. * of the License, or any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program; if not, write to the Free Software
  21. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  22. */
  23. package console;
  24. // {{{ Imports
  25. import java.io.File;
  26. import java.io.IOException;
  27. import java.io.OutputStream;
  28. import java.io.PipedOutputStream;
  29. import java.io.StreamTokenizer;
  30. import java.io.StringReader;
  31. import java.io.UnsupportedEncodingException;
  32. import java.util.ArrayList;
  33. import java.util.Hashtable;
  34. import java.util.Iterator;
  35. import java.util.List;
  36. import java.util.Map;
  37. import java.util.Stack;
  38. import java.util.Vector;
  39. import java.util.regex.Matcher;
  40. import java.util.regex.Pattern;
  41. import org.gjt.sp.jedit.Buffer;
  42. import org.gjt.sp.jedit.MiscUtilities;
  43. import org.gjt.sp.jedit.View;
  44. import org.gjt.sp.jedit.jEdit;
  45. import org.gjt.sp.jedit.browser.VFSBrowser;
  46. // }}}
  47. /**
  48. * A SystemShell belongs to each Console. It creates a ProcessBuilder. When it
  49. * is time to execute something, it creates a ConsoleProcess, passing the
  50. * ProcessBuilder down. The process itself is started indirectly by
  51. * ProcessRunner.exec().
  52. */
  53. public class SystemShell extends Shell
  54. {
  55. // {{{ SystemShell constructor
  56. public SystemShell()
  57. {
  58. super("System");
  59. lineSep = toBytes(System.getProperty("line.separator"));
  60. processBuilder = new ProcessBuilder();
  61. consoleStateMap = new Hashtable<Console, ConsoleState>();
  62. } // }}}
  63. // {{{ openConsole() method
  64. /**
  65. * Called when a Console dockable first selects this shell.
  66. *
  67. * @since Console 4.0.2
  68. */
  69. public void openConsole(Console console)
  70. {
  71. consoleStateMap.put(console, new ConsoleState());
  72. } // }}}
  73. // {{{ closeConsole() method
  74. /**
  75. * Called when a Console dockable is closed.
  76. *
  77. * @since Console 4.0.2
  78. */
  79. public void closeConsole(Console console)
  80. {
  81. ConsoleProcess process = getConsoleState(console).process;
  82. if (process != null)
  83. process.stop();
  84. consoleStateMap.remove(console);
  85. } // }}}
  86. // {{{ printInfoMessage() method
  87. public void printInfoMessage(Output output)
  88. {
  89. output.print(null, jEdit.getProperty("console.shell.info"));
  90. } // }}}
  91. // {{{ printPrompt()
  92. /**
  93. * Prints a prompt to the specified console.
  94. *
  95. * @param output
  96. * The output
  97. */
  98. public void printPrompt(Console console, Output output)
  99. {
  100. String currentDirectory;
  101. if (getConsoleState(console) == null)
  102. currentDirectory = System.getProperty("user.dir");
  103. else
  104. {
  105. currentDirectory = getConsoleState(console).currentDirectory;
  106. }
  107. output.writeAttrs(ConsolePane.colorAttributes(console.getPlainColor()), jEdit
  108. .getProperty("console.shell.prompt", new String[] { currentDirectory }));
  109. output.writeAttrs(null, " ");
  110. }
  111. // }}}
  112. // {{{ execute()
  113. public void execute(final Console console, String input, final Output output, Output error,
  114. String command)
  115. {
  116. if (error == null)
  117. error = output;
  118. ConsoleState state = getConsoleState(console);
  119. if (state.process != null)
  120. {
  121. PipedOutputStream out = state.process.getPipeOutput();
  122. if (out != null)
  123. {
  124. try
  125. {
  126. out.write(toBytes(command));
  127. out.write(lineSep);
  128. out.flush();
  129. }
  130. catch (IOException e)
  131. {
  132. throw new RuntimeException(e);
  133. }
  134. }
  135. return;
  136. }
  137. // comments, for possible future scripting support
  138. if (command.startsWith("#"))
  139. {
  140. output.commandDone();
  141. return;
  142. }
  143. // lazily initialize aliases and variables
  144. init();
  145. Vector<String> args = parse(command);
  146. // will be null if the command is an empty string
  147. if (args == null)
  148. {
  149. output.commandDone();
  150. return;
  151. }
  152. args = preprocess(console.getView(), console, args);
  153. String commandName = (String) args.elementAt(0);
  154. if (commandName.charAt(0) == '%')
  155. {
  156. // a console built-in
  157. args.removeElementAt(0);
  158. executeBuiltIn(console, output, error, commandName, args);
  159. output.commandDone();
  160. return;
  161. }
  162. // if current working directory doesn't exist, print an error.
  163. String cwd = state.currentDirectory;
  164. if (!new File(cwd).exists())
  165. {
  166. output.print(console.getErrorColor(), jEdit.getProperty(
  167. "console.shell.error.working-dir", new String[] { cwd }));
  168. output.commandDone();
  169. // error.commandDone();
  170. return;
  171. }
  172. String fullPath = MiscUtilities.constructPath(cwd, commandName);
  173. // Java resolves this relative to user.dir, not
  174. // the directory we pass to exec()...
  175. if (commandName.startsWith("./") || commandName.startsWith("." + File.separator))
  176. {
  177. args.setElementAt(fullPath, 0);
  178. }
  179. if (new File(fullPath).isDirectory() && args.size() == 1)
  180. {
  181. args.setElementAt(fullPath, 0);
  182. executeBuiltIn(console, output, error, "%cd", args);
  183. output.commandDone();
  184. // error.commandDone();
  185. }
  186. else
  187. {
  188. boolean foreground;
  189. if (args.elementAt(args.size() - 1).equals("&"))
  190. {
  191. // run in background
  192. args.removeElementAt(args.size() - 1);
  193. foreground = false;
  194. output.commandDone();
  195. // error.commandDone();
  196. }
  197. else
  198. {
  199. // run in foreground
  200. foreground = true;
  201. }
  202. String[] _args = new String[args.size()];
  203. args.copyInto(_args);
  204. state.currentDirectory = cwd;
  205. final ConsoleProcess proc = new ConsoleProcess(console, output, _args,
  206. processBuilder, state, foreground);
  207. /* If startup failed its no longer running */
  208. if (foreground && proc.isRunning())
  209. {
  210. console.getErrorSource().clear();
  211. state.process = proc;
  212. }
  213. if (input != null)
  214. {
  215. try
  216. {
  217. OutputStream out = proc.getPipeOutput();
  218. out.write(toBytes(input));
  219. out.close();
  220. }
  221. catch (IOException e)
  222. {
  223. throw new RuntimeException(e);
  224. }
  225. }
  226. }
  227. }
  228. // }}}
  229. // {{{ stop() method
  230. public void stop(Console console)
  231. {
  232. ConsoleState consoleState = getConsoleState(console);
  233. if (consoleState == null)
  234. return;
  235. ConsoleProcess process = consoleState.process;
  236. if (process != null)
  237. process.stop();
  238. else
  239. {
  240. console.getOutput().print(console.getErrorColor(),
  241. jEdit.getProperty("console.shell.noproc"));
  242. }
  243. } // }}}
  244. // {{{ waitFor() method
  245. public boolean waitFor(Console console)
  246. {
  247. ConsoleState consoleState = getConsoleState(console);
  248. if (consoleState == null)
  249. return true;
  250. ConsoleProcess process = consoleState.process;
  251. if (process != null)
  252. {
  253. try
  254. {
  255. synchronized (process)
  256. {
  257. process.wait();
  258. }
  259. }
  260. catch (InterruptedException e)
  261. {
  262. }
  263. return process.getExitStatus();
  264. }
  265. else
  266. return true;
  267. } // }}}
  268. // {{{ endOfFile() method
  269. /**
  270. * Sends an end of file.
  271. *
  272. * @param console
  273. * The console
  274. */
  275. public void endOfFile(Console console)
  276. {
  277. ConsoleState state = getConsoleState(console);
  278. if (state.process != null)
  279. {
  280. console.getOutput().writeAttrs(
  281. ConsolePane.colorAttributes(console.getInfoColor()), "^D\n");
  282. PipedOutputStream out = state.process.getPipeOutput();
  283. try
  284. {
  285. out.close();
  286. }
  287. catch (IOException e)
  288. {
  289. }
  290. }
  291. } // }}}
  292. // {{{ detach() method
  293. /**
  294. * Detaches the currently running process.
  295. *
  296. * @param console
  297. * The console
  298. */
  299. public void detach(Console console)
  300. {
  301. SystemShell.ConsoleState state = getConsoleState(console);
  302. ConsoleProcess process = state.process;
  303. if (process == null)
  304. {
  305. console.getOutput().print(console.getErrorColor(),
  306. jEdit.getProperty("console.shell.noproc"));
  307. return;
  308. }
  309. process.detach();
  310. } // }}}
  311. // {{{ getCompletions() method
  312. /**
  313. * Returns possible completions for the specified command.
  314. *
  315. * @param console
  316. * The console instance
  317. * @param command
  318. * The command
  319. * @since Console 3.6
  320. */
  321. public CompletionInfo getCompletions(Console console, String command)
  322. {
  323. // lazily initialize aliases and variables
  324. init();
  325. View view = console.getView();
  326. String currentDirectory = (console == null ? System.getProperty("user.dir")
  327. : getConsoleState(console).currentDirectory);
  328. final String fileDelimiters = "=\'\" \\" + File.pathSeparator;
  329. String lastArgEscaped, lastArg;
  330. if (File.separatorChar == '\\')
  331. {
  332. // Escaping impossible
  333. lastArgEscaped = (String) parse(command).lastElement();
  334. lastArg = lastArgEscaped;
  335. }
  336. else
  337. {
  338. // Escaping possible
  339. // I use findLastArgument and then unescape instead of
  340. // (String)parse(command).lastElement() because there's
  341. // no way
  342. // to get parse(String) to also return the original
  343. // length
  344. // of the unescaped argument, which we need to calculate
  345. // the
  346. // completion offset.
  347. lastArgEscaped = findLastArgument(command, fileDelimiters);
  348. lastArg = unescape(lastArgEscaped, fileDelimiters);
  349. }
  350. CompletionInfo completionInfo = new CompletionInfo();
  351. completionInfo.offset = command.length() - lastArg.length();
  352. if (completionInfo.offset == 0)
  353. {
  354. completionInfo.completions = (String[]) getCommandCompletions(view,
  355. currentDirectory, lastArg).toArray(new String[0]);
  356. }
  357. else
  358. {
  359. completionInfo.completions = (String[]) getFileCompletions(view,
  360. currentDirectory, lastArg, false).toArray(new String[0]);
  361. }
  362. // On systems where the file separator is the same as the escape
  363. // character (Windows), it's impossible to do escaping properly,
  364. // so we just assume that escaping is not needed (which is true
  365. // for windows).
  366. if (File.separatorChar != '\\')
  367. {
  368. for (int i = 0; i < completionInfo.completions.length; i++)
  369. {
  370. completionInfo.completions[i] = escape(
  371. completionInfo.completions[i], fileDelimiters);
  372. }
  373. }
  374. // We add a double quote at the beginning of any completion with
  375. // special characters because the current argument parsing
  376. // (done in parse()) uses StreamTokenizer, which only handles
  377. // escaping if it's done within a string. The purpose here is
  378. // dual - to get parse() to unescape the characters and to get
  379. // it to recognize the string as a single argument.
  380. // On systems where we don't support escaping, we do this
  381. // only for the 2nd purpose.
  382. boolean isDoubleQuoted = (completionInfo.offset > 0)
  383. && (command.charAt(completionInfo.offset - 1) == '\"');
  384. if (!isDoubleQuoted)
  385. {
  386. final String specialCharacters = (File.separatorChar == '\\') ? " "
  387. : fileDelimiters;
  388. for (int i = 0; i < completionInfo.completions.length; i++)
  389. {
  390. String result = completionInfo.completions[i];
  391. if (containsCharacters(result, specialCharacters))
  392. result = "\"" + result;
  393. completionInfo.completions[i] = result;
  394. }
  395. }
  396. return completionInfo;
  397. }
  398. // }}}
  399. // {{{ expandVariables()
  400. static final String varPatternString = "([$%])([a-zA-Z0-9_]+)(\\1?)";
  401. static final String varPatternString2 = "([$%])\\{([^}]+)\\}";
  402. static final Pattern varPattern = Pattern.compile(varPatternString);
  403. static final Pattern varPattern2 = Pattern.compile(varPatternString2);
  404. /**
  405. * returns a string after it's been processed by jedit's internal
  406. * command processor
  407. *
  408. * @param view
  409. * A view corresponding to this console's state.
  410. * @param arg
  411. * A single argument on the command line
  412. * @return A string after it's been processed, with variables replaced
  413. * with their values.
  414. */
  415. public String expandVariables(View view, String arg)
  416. {
  417. StringBuffer buf = new StringBuffer();
  418. String varName = null;
  419. arg = arg.replace("^~", System.getProperty("user.home"));
  420. Matcher m = varPattern.matcher(arg);
  421. if (!m.find())
  422. {
  423. m = varPattern2.matcher(arg);
  424. if (!m.find())
  425. return arg;
  426. }
  427. else
  428. {
  429. varName = m.group(2);
  430. String expansion = getVariableValue(view, varName);
  431. if (expansion != null)
  432. {
  433. expansion = expansion.replace("\\", "\\\\");
  434. return m.replaceFirst(expansion);
  435. }
  436. }
  437. return arg;
  438. }
  439. // }}}
  440. // {{{ getVariableValue() method
  441. public String getVariableValue(View view, String varName)
  442. {
  443. init();
  444. Map<String, String> variables = processBuilder.environment();
  445. if (view == null)
  446. return variables.get(varName);
  447. String expansion;
  448. // Expand some special variables
  449. Buffer buffer = view.getBuffer();
  450. // What is this for?
  451. if (varName.equals("$") || varName.equals("%"))
  452. expansion = varName;
  453. else if (varName.equals("d"))
  454. {
  455. expansion = MiscUtilities.getParentOfPath(buffer.getPath());
  456. if (expansion.endsWith("/") || expansion.endsWith(File.separator))
  457. {
  458. expansion = expansion.substring(0, expansion.length() - 1);
  459. }
  460. }
  461. else if (varName.equals("u"))
  462. {
  463. expansion = buffer.getPath();
  464. if (!MiscUtilities.isURL(expansion))
  465. {
  466. expansion = "file:/" + expansion.replace(File.separatorChar, '/');
  467. }
  468. }
  469. else if (varName.equals("f"))
  470. expansion = buffer.getPath();
  471. else if (varName.equals("n"))
  472. expansion = buffer.getName();
  473. else if (varName.equals("c"))
  474. expansion = ConsolePlugin.getClassName(buffer);
  475. else if (varName.equals("PKG"))
  476. {
  477. expansion = ConsolePlugin.getPackageName(buffer);
  478. if (expansion == null)
  479. expansion = "";
  480. }
  481. else if (varName.equals("ROOT"))
  482. expansion = ConsolePlugin.getPackageRoot(buffer);
  483. else if (varName.equals("BROWSER_DIR"))
  484. {
  485. VFSBrowser browser = (VFSBrowser) view.getDockableWindowManager()
  486. .getDockable("vfs.browser");
  487. if (browser == null)
  488. expansion = null;
  489. else
  490. expansion = browser.getDirectory();
  491. }
  492. else
  493. expansion = (String) variables.get(varName);
  494. return expansion;
  495. } // }}}
  496. // {{{ getAliases() method
  497. public Hashtable getAliases()
  498. {
  499. init();
  500. return aliases;
  501. } // }}}
  502. // {{{ getConsoleState() method
  503. ConsoleState getConsoleState(Console console)
  504. {
  505. ConsoleState retval = (ConsoleState) consoleStateMap.get(console);
  506. if (retval == null)
  507. openConsole(console);
  508. return (ConsoleState) consoleStateMap.get(console);
  509. } // }}}
  510. // {{{ getVariables() method
  511. Map getVariables()
  512. {
  513. return processBuilder.environment();
  514. } // }}}
  515. // {{{ propertiesChanged() method
  516. static void propertiesChanged()
  517. {
  518. // aliases = null;
  519. // variables = null;
  520. // next time execute() is called, init() will reload everything
  521. } // }}}
  522. // }}}
  523. // {{{ toBytes() method
  524. private static byte[] toBytes(String str)
  525. {
  526. try
  527. {
  528. return str.getBytes(jEdit.getProperty("console.encoding"));
  529. }
  530. catch (UnsupportedEncodingException e)
  531. {
  532. throw new RuntimeException(e);
  533. }
  534. } // }}}
  535. // {{{ init() method
  536. private void init()
  537. {
  538. if (initialized)
  539. return;
  540. initialized = true;
  541. initCommands();
  542. initAliases();
  543. } // }}}
  544. // {{{ initCommands() method
  545. private void initCommands()
  546. {
  547. commands = new Hashtable<String, SystemShellBuiltIn>();
  548. commands.put("%alias", new SystemShellBuiltIn.alias());
  549. commands.put("%aliases", new SystemShellBuiltIn.aliases());
  550. commands.put("%browse", new SystemShellBuiltIn.browse());
  551. commands.put("%cd", new SystemShellBuiltIn.cd());
  552. commands.put("%clear", new SystemShellBuiltIn.clear());
  553. commands.put("%dirstack", new SystemShellBuiltIn.dirstack());
  554. commands.put("%echo", new SystemShellBuiltIn.echo());
  555. commands.put("%edit", new SystemShellBuiltIn.edit());
  556. commands.put("%env", new SystemShellBuiltIn.env());
  557. commands.put("%help", new SystemShellBuiltIn.help());
  558. commands.put("%kill", new SystemShellBuiltIn.kill());
  559. commands.put("%popd", new SystemShellBuiltIn.popd());
  560. commands.put("%pushd", new SystemShellBuiltIn.pushd());
  561. commands.put("%pwd", new SystemShellBuiltIn.pwd());
  562. commands.put("%run", new SystemShellBuiltIn.run());
  563. commands.put("%set", new SystemShellBuiltIn.set());
  564. commands.put("%unalias", new SystemShellBuiltIn.unalias());
  565. commands.put("%unset", new SystemShellBuiltIn.unset());
  566. commands.put("%version", new SystemShellBuiltIn.version());
  567. } // }}}
  568. // {{{ initAliases() method
  569. private void initAliases()
  570. {
  571. aliases = new Hashtable();
  572. ProcessRunner pr = ProcessRunner.getProcessRunner();
  573. pr.setUpDefaultAliases(aliases);
  574. // some built-ins can be invoked without the % prefix
  575. aliases.put("cd", "%cd");
  576. // aliases.put("pwd","%pwd");
  577. aliases.put("-", "%cd -");
  578. // load aliases from properties
  579. String alias;
  580. int i = 0;
  581. while ((alias = jEdit.getProperty("console.shell.alias." + i)) != null)
  582. {
  583. aliases.put(alias, jEdit.getProperty("console.shell.alias." + i
  584. + ".expansion"));
  585. i++;
  586. }
  587. } // }}}
  588. // {{{ initVariables() method
  589. private void initVariables()
  590. {
  591. Map<String, String> variables = processBuilder.environment();
  592. if (jEdit.getJEditHome() != null)
  593. variables.put("JEDIT_HOME", jEdit.getJEditHome());
  594. if (jEdit.getSettingsDirectory() != null)
  595. variables.put("JEDIT_SETTINGS", jEdit.getSettingsDirectory());
  596. // for the sake of Unix programs that try to be smart
  597. variables.put("TERM", "dumb");
  598. // load variables from properties
  599. /*
  600. * String varname; i = 0; while((varname =
  601. * jEdit.getProperty("console.shell.variable." + i)) != null) {
  602. * variables.put(varname,jEdit.getProperty("console.shell.variable." +
  603. * i + ".value")); i++; }
  604. */
  605. } // }}}
  606. // {{{ parse() method
  607. /**
  608. * Convert a command into a vector of arguments.
  609. */
  610. private Vector<String> parse(String command)
  611. {
  612. Vector<String> args = new Vector<String>();
  613. // We replace \ with a non-printable char because
  614. // StreamTokenizer handles \ specially, which causes
  615. // problems on Windows as \ is the file separator
  616. // there.
  617. // After parsing is done, the non printable char is
  618. // changed to \ once again.
  619. // StreamTokenizer needs a way to disable backslash
  620. // handling...
  621. if (File.separatorChar == '\\')
  622. command = command.replace('\\', dosSlash);
  623. StreamTokenizer st = new StreamTokenizer(new StringReader(command));
  624. st.resetSyntax();
  625. st.wordChars('!', 255);
  626. st.whitespaceChars(0, ' ');
  627. st.quoteChar('"');
  628. st.quoteChar('\'');
  629. try
  630. {
  631. loop: for (;;)
  632. {
  633. switch (st.nextToken())
  634. {
  635. case StreamTokenizer.TT_EOF:
  636. break loop;
  637. case StreamTokenizer.TT_WORD:
  638. case '"':
  639. case '\'':
  640. if (File.separatorChar == '\\')
  641. args.addElement(st.sval.replace(dosSlash, '\\'));
  642. else
  643. args.addElement(st.sval);
  644. break;
  645. }
  646. }
  647. }
  648. catch (IOException io)
  649. {
  650. // won't happen
  651. }
  652. if (args.size() == 0)
  653. return null;
  654. else
  655. return args;
  656. } // }}}
  657. // {{{ preprocess() method
  658. /**
  659. * Expand aliases, variables and globs.
  660. *
  661. * @return a new vector of arguments after vars have been substituted.
  662. */
  663. private Vector<String> preprocess(View view, Console console, Vector<String> args)
  664. {
  665. Vector<String> newArgs = new Vector<String>();
  666. // expand aliases
  667. String commandName = args.elementAt(0);
  668. String expansion = aliases.get(commandName);
  669. if (expansion != null)
  670. {
  671. Vector<String> expansionArgs = parse(expansion);
  672. for (int i = 0; i < expansionArgs.size(); i++)
  673. {
  674. expandGlobs(view, newArgs, (String) expansionArgs.elementAt(i));
  675. }
  676. }
  677. else
  678. expandGlobs(view, newArgs, commandName);
  679. // add remaining arguments
  680. for (int i = 1; i < args.size(); i++)
  681. expandGlobs(view, newArgs, (String) args.elementAt(i));
  682. return newArgs;
  683. } // }}}
  684. // {{{ expandGlobs() method
  685. /**
  686. * @param arg -
  687. * a single input argument
  688. * @param args -
  689. * an output variable where we place resulting expanded
  690. * args
  691. *
  692. */
  693. private void expandGlobs(View view, Vector<String> args, String arg)
  694. {
  695. // XXX: to do
  696. args.addElement(expandVariables(view, arg));
  697. } // }}}
  698. // {{{ executeBuiltIn() method
  699. public void executeBuiltIn(Console console, Output output, Output error, String command,
  700. Vector args)
  701. {
  702. SystemShellBuiltIn builtIn = (SystemShellBuiltIn) commands.get(command);
  703. if (builtIn == null)
  704. {
  705. String[] pp = { command };
  706. error.print(console.getErrorColor(), jEdit.getProperty(
  707. "console.shell.unknown-builtin", pp));
  708. }
  709. else
  710. {
  711. builtIn.execute(console, output, error, args);
  712. }
  713. } // }}}
  714. // {{{ getFileCompletions() method
  715. private List getFileCompletions(View view, String currentDirName, String typedFilename,
  716. boolean directoriesOnly)
  717. {
  718. String expandedTypedFilename = expandVariables(view, typedFilename);
  719. int lastSeparatorIndex = expandedTypedFilename.lastIndexOf(File.separator);
  720. // The directory part of what the user typed, including the file
  721. // separator.
  722. String typedDirName = lastSeparatorIndex == -1 ? "" : expandedTypedFilename
  723. .substring(0, lastSeparatorIndex + 1);
  724. // The file typed by the user.
  725. File typedFile = new File(expandedTypedFilename).isAbsolute() ? new File(
  726. expandedTypedFilename) : new File(currentDirName, expandedTypedFilename);
  727. boolean directory = expandedTypedFilename.endsWith(File.separator)
  728. || expandedTypedFilename.length() == 0;
  729. // The parent directory of the file typed by the user (or itself
  730. // if it's already a directory).
  731. File dir = directory ? typedFile : typedFile.getParentFile();
  732. // The filename part of the file typed by the user, or "" if
  733. // it's a directory.
  734. String fileName = directory ? "" : typedFile.getName();
  735. // The list of files we're going to try to match
  736. String[] filenames = dir.list();
  737. if ((filenames == null) || (filenames.length == 0))
  738. return null;
  739. boolean isOSCaseSensitive = ProcessRunner.getProcessRunner().isCaseSensitive();
  740. ArrayList matchingFilenames = new ArrayList(filenames.length);
  741. int matchingFilenamesCount = 0;
  742. String matchedString = isOSCaseSensitive ? fileName : fileName.toLowerCase();
  743. for (int i = 0; i < filenames.length; i++)
  744. {
  745. String matchedAgainst = isOSCaseSensitive ? filenames[i] : filenames[i]
  746. .toLowerCase();
  747. if (matchedAgainst.startsWith(matchedString))
  748. {
  749. String match;
  750. File matchFile = new File(dir, filenames[i]);
  751. if (directoriesOnly && !matchFile.isDirectory())
  752. continue;
  753. match = typedDirName + filenames[i];
  754. // Add a separator at the end if it's a
  755. // directory
  756. if (matchFile.isDirectory() && !match.endsWith(File.separator))
  757. match = match + File.separator;
  758. matchingFilenames.add(match);
  759. }
  760. }
  761. return matchingFilenames;
  762. } // }}}
  763. // {{{ getCommandCompletions() method
  764. private List getCommandCompletions(View view, String currentDirName, String command)
  765. {
  766. ArrayList list = new ArrayList();
  767. Iterator iter = commands.keySet().iterator();
  768. while (iter.hasNext())
  769. {
  770. String cmd = (String) iter.next();
  771. if (cmd.startsWith(command))
  772. list.add(cmd);
  773. }
  774. iter = aliases.keySet().iterator();
  775. while (iter.hasNext())
  776. {
  777. String cmd = (String) iter.next();
  778. if (cmd.startsWith(command))
  779. list.add(cmd);
  780. }
  781. list.addAll(getFileCompletions(view, currentDirName, command, false));
  782. return list;
  783. } // }}}
  784. // {{{ findLastArgument() method
  785. /**
  786. * Returns the last argument in the given command by using the given
  787. * delimiters. The delimiters can be escaped.
  788. */
  789. private static String findLastArgument(String command, String delimiters)
  790. {
  791. int i = command.length() - 1;
  792. while (i >= 0)
  793. {
  794. char c = command.charAt(i);
  795. if (delimiters.indexOf(c) != -1)
  796. {
  797. if ((i == 0) || (command.charAt(i - 1) != '\\'))
  798. break;
  799. else
  800. i--;
  801. }
  802. i--;
  803. }
  804. return command.substring(i + 1);
  805. } // }}}
  806. // {{{ unescape() method
  807. /**
  808. * Unescapes the given delimiters in the given string.
  809. */
  810. private static String unescape(String s, String delimiters)
  811. {
  812. StringBuffer buf = new StringBuffer(s.length());
  813. int i = s.length() - 1;
  814. while (i >= 0)
  815. {
  816. char c = s.charAt(i);
  817. buf.append(c);
  818. if (delimiters.indexOf(c) != -1)
  819. {
  820. if (s.charAt(i - 1) == '\\')
  821. i--;
  822. }
  823. i--;
  824. }
  825. return buf.reverse().toString();
  826. } // }}}
  827. // {{{ escape() method
  828. /**
  829. * Escapes the given delimiters in the given string.
  830. */
  831. private static String escape(String s, String delimiters)
  832. {
  833. StringBuffer buf = new StringBuffer();
  834. int length = s.length();
  835. for (int i = 0; i < length; i++)
  836. {
  837. char c = s.charAt(i);
  838. if (delimiters.indexOf(c) != -1)
  839. buf.append('\\');
  840. buf.append(c);
  841. }
  842. return buf.toString();
  843. } // }}}
  844. // {{{ containsWhitespace() method
  845. /**
  846. * Returns <code>true</code> if the first string contains any of the
  847. * characters in the second string. Returns <code>false</code>
  848. * otherwise.
  849. */
  850. private static boolean containsCharacters(String s, String characters)
  851. {
  852. int stringLength = s.length();
  853. for (int i = 0; i < stringLength; i++)
  854. if (characters.indexOf(s.charAt(i)) != -1)
  855. return true;
  856. return false;
  857. }
  858. // }}}
  859. // {{{ ConsoleState class
  860. static class ConsoleState
  861. {
  862. String currentDirectory = System.getProperty("user.dir");
  863. String lastDirectory = System.getProperty("user.dir");
  864. Stack directoryStack = new Stack();
  865. ConsoleProcess process;
  866. void gotoLastDirectory(Console console)
  867. {
  868. setCurrentDirectory(console, lastDirectory);
  869. }
  870. void setCurrentDirectory(Console console, String newDir)
  871. {
  872. String[] pp = { newDir };
  873. File file = new File(newDir);
  874. if (!file.exists())
  875. {
  876. console.getOutput().print(console.getErrorColor(),
  877. jEdit.getProperty("console.shell.cd.error", pp));
  878. }
  879. else if (!file.isDirectory())
  880. {
  881. console.getOutput().print(console.getErrorColor(),
  882. jEdit.getProperty("console.shell.cd.file", pp));
  883. }
  884. else
  885. {
  886. lastDirectory = currentDirectory;
  887. currentDirectory = newDir;
  888. }
  889. }
  890. }
  891. // }}}
  892. // {{{ private members
  893. private ProcessBuilder processBuilder;
  894. private Hashtable<Console, ConsoleState> consoleStateMap;
  895. private final char dosSlash = 127;
  896. private Hashtable<String, String> aliases;
  897. private Hashtable<String, SystemShellBuiltIn> commands;
  898. private boolean initialized;
  899. private byte[] lineSep;
  900. // }}}
  901. }