/plugins/Console/tags/Console_4_2_3/console/SystemShell.java

#
Java | 1089 lines | 740 code | 148 blank | 201 comment | 173 complexity | d501ee3b2574337796d0d67c840fef63 MD5 | raw file

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