Page MenuHomePhabricator

D19700.diff
No OneTemporary

D19700.diff

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -19,7 +19,6 @@
/support/xhpast/parser.yacc.output
/support/xhpast/node_names.hpp
/support/xhpast/xhpast
-/support/xhpast/xhpast.exe
/src/parser/xhpast/bin/xhpast
## NOTE: Don't .gitignore these files! Even though they're build artifacts, we
@@ -32,9 +31,8 @@
# This is an OS X build artifact.
/support/xhpast/xhpast.dSYM
-# libphutil
-/support/phutiltestlib/.phutil_module_cache
-
# This file overrides "default.pem" if present.
/resources/ssl/custom.pem
+# Generated shell completion rules.
+/support/shell/rules/*
diff --git a/src/log/ArcanistLogEngine.php b/src/log/ArcanistLogEngine.php
--- a/src/log/ArcanistLogEngine.php
+++ b/src/log/ArcanistLogEngine.php
@@ -77,5 +77,13 @@
return $trace;
}
+ public function writeHint($label, $message) {
+ return $this->writeMessage(
+ $this->newMessage()
+ ->setColor('cyan')
+ ->setLabel($label)
+ ->setMessage($message));
+ }
+
}
diff --git a/src/toolset/ArcanistWorkflowArgument.php b/src/toolset/ArcanistWorkflowArgument.php
--- a/src/toolset/ArcanistWorkflowArgument.php
+++ b/src/toolset/ArcanistWorkflowArgument.php
@@ -6,6 +6,8 @@
private $key;
private $help;
private $wildcard;
+ private $parameter;
+ private $isPathArgument;
public function setKey($key) {
$this->key = $key;
@@ -34,6 +36,11 @@
$spec['wildcard'] = true;
}
+ $parameter = $this->getParameter();
+ if ($parameter !== null) {
+ $spec['param'] = $parameter;
+ }
+
return $spec;
}
@@ -46,5 +53,23 @@
return $this->help;
}
+ public function setParameter($parameter) {
+ $this->parameter = $parameter;
+ return $this;
+ }
+
+ public function getParameter() {
+ return $this->parameter;
+ }
+
+ public function setIsPathArgument($is_path_argument) {
+ $this->isPathArgument = $is_path_argument;
+ return $this;
+ }
+
+ public function getIsPathArgument() {
+ return $this->isPathArgument;
+ }
+
}
diff --git a/src/toolset/workflow/ArcanistShellCompleteWorkflow.php b/src/toolset/workflow/ArcanistShellCompleteWorkflow.php
--- a/src/toolset/workflow/ArcanistShellCompleteWorkflow.php
+++ b/src/toolset/workflow/ArcanistShellCompleteWorkflow.php
@@ -1,203 +1,623 @@
<?php
-/**
- * Powers shell-completion scripts.
- */
-final class ArcanistShellCompleteWorkflow extends ArcanistWorkflow {
-
- public function supportsToolset(ArcanistToolset $toolset) {
- return true;
- }
+final class ArcanistShellCompleteWorkflow
+ extends ArcanistWorkflow {
public function getWorkflowName() {
return 'shell-complete';
}
- public function getWorkflowSynopses() {
+ public function getWorkflowInformation() {
+ $help = pht(<<<EOTEXT
+Install shell completion so you can use the "tab" key to autocomplete
+commands and flags in your shell for Arcanist toolsets and workflows.
+
+The **bash** shell is supported.
+
+**Installing Completion**
+
+To install shell completion, run the command:
+
+ $ arc shell-complete
+
+This will install shell completion into your current shell. After installing,
+you may need to start a new shell (or open a new terminal window) to pick up
+the updated configuration.
+
+Once installed, completion should work across all Arcanist toolsets.
+
+**Using Completion**
+
+After completion is installed, use the "tab" key to automatically complete
+workflows and flags. For example, if you type:
+
+ $ arc diff --draf<tab>
+
+...your shell should automatically expand the flag to:
+
+ $ arc diff --draft
+
+**Updating Completion**
+
+To update shell completion, run the same command:
+
+ $ arc shell-complete
+
+You can update shell completion without reinstalling it by running:
+
+ $ arc shell-complete --generate
+
+You may need to update shell completion if:
+
+ - you install new Arcanist toolsets; or
+ - you move the Arcanist directory; or
+ - you upgrade Arcanist and the new version fixes shell completion bugs.
+EOTEXT
+);
+
+ return $this->newWorkflowInformation()
+ ->setHelp($help);
+ }
+
+ public function getWorkflowArguments() {
return array(
- pht('**shell-complete** __--current__ __N__ -- [__argv__]'),
+ $this->newWorkflowArgument('current')
+ ->setParameter('cursor-position')
+ ->setHelp(
+ pht(
+ 'Internal. Current term in the argument list being completed.')),
+ $this->newWorkflowArgument('generate')
+ ->setHelp(
+ pht(
+ 'Regenerate shell completion rules, without installing any '.
+ 'configuration.')),
+ $this->newWorkflowArgument('shell')
+ ->setParameter('shell-name')
+ ->setHelp(
+ pht(
+ 'Install completion support for a particular shell.')),
+ $this->newWorkflowArgument('argv')
+ ->setWildcard(true),
);
}
- public function getWorkflowHelp() {
- return pht(<<<EOTEXT
-Implements shell completion. To use shell completion, source the appropriate
-script from 'resources/shell/' in your .shellrc.
-EOTEXT
- );
+ public function runWorkflow() {
+ $log = $this->getLogEngine();
+
+ $argv = $this->getArgument('argv');
+
+ $is_generate = $this->getArgument('generate');
+ $is_shell = (bool)strlen($this->getArgument('shell'));
+ $is_current = $this->getArgument('current');
+
+ if ($argv) {
+ $should_install = false;
+ $should_generate = false;
+
+ if ($is_generate) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You can not use "--generate" when completing arguments.'));
+ }
+
+ if ($is_shell) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You can not use "--shell" when completing arguments.'));
+ }
+
+ } else if ($is_generate) {
+ $should_install = false;
+ $should_generate = true;
+
+ if ($is_current) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You can not use "--current" when generating rules.'));
+ }
+
+ if ($is_shell) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'The flags "--generate" and "--shell" are mutually exclusive. '.
+ 'The "--shell" flag selects which shell to install support for, '.
+ 'but the "--generate" suppresses installation.'));
+ }
+
+ } else {
+ $should_install = true;
+ $should_generate = true;
+
+ if ($is_current) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You can not use "--current" when installing support.'));
+ }
+ }
+
+ if ($should_install) {
+ $this->runInstall();
+ }
+
+ if ($should_generate) {
+ $this->runGenerate();
+ }
+
+ if ($should_install || $should_generate) {
+ $log->writeHint(
+ pht('NOTE'),
+ pht(
+ 'You may need to open a new terminal window or launch a new shell '.
+ 'before the changes take effect.'));
+ return 0;
+ }
+
+ $this->runAutocomplete();
}
- public function getArguments() {
- return array(
- 'current' => array(
- 'param' => 'cursor_position',
- 'paramtype' => 'int',
- 'help' => pht('Current term in the argument list being completed.'),
+ private function runInstall() {
+ $log = $this->getLogEngine();
+
+ $shells = array(
+ array(
+ 'key' => 'bash',
+ 'path' => '/bin/bash',
+ 'file' => '.profile',
+ 'source' => 'hooks/bash-completion.sh',
),
- '*' => 'argv',
);
+ $shells = ipull($shells, null, 'key');
+
+ $shell = $this->getArgument('shell');
+ if (!$shell) {
+ $shell = $this->detectShell($shells);
+ } else {
+ $shell = $this->selectShell($shells, $shell);
+ }
+
+ $spec = $shells[$shell];
+ $file = $spec['file'];
+ $home = getenv('HOME');
+
+ if (!strlen($home)) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'The "HOME" environment variable is not defined, so this workflow '.
+ 'can not identify where to install shell completion.'));
+ }
+
+ $file_path = getenv('HOME').'/'.$file;
+ $file_display = '~/'.$file;
+
+ if (Filesystem::pathExists($file_path)) {
+ $file_path = Filesystem::resolvePath($file_path);
+ $data = Filesystem::readFile($file_path);
+ $is_new = false;
+ } else {
+ $data = '';
+ $is_new = true;
+ }
+
+ $line = csprintf(
+ 'source %R # arcanist-shell-complete',
+ $this->getShellPath($spec['source']));
+
+ $matches = null;
+ $replace = preg_match(
+ '/(\s*\n)?[^\n]+# arcanist-shell-complete\s*(\n\s*)?/',
+ $data,
+ $matches,
+ PREG_OFFSET_CAPTURE);
+
+ $log->writeSuccess(
+ pht('INSTALL'),
+ pht('Installing shell completion support for "%s".', $shell));
+
+ if ($replace) {
+ $replace_pos = $matches[0][1];
+ $replace_line = $matches[0][0];
+ $replace_len = strlen($replace_line);
+ $replace_display = trim($replace_line);
+
+ if ($replace_pos === 0) {
+ $new_line = $line."\n";
+ } else {
+ $new_line = "\n\n".$line."\n";
+ }
+
+ $new_data = substr_replace($data, $new_line, $replace_pos, $replace_len);
+
+ if ($new_data === $data) {
+ // If we aren't changing anything in the file, just skip the write
+ // completely.
+ $needs_write = false;
+
+ $log->writeStatus(
+ pht('SKIP'),
+ pht('Shell completion for "%s" is already installed.', $shell));
+
+ return;
+ }
+
+ echo tsprintf(
+ "%s\n\n %s\n\n%s\n\n %s\n",
+ pht(
+ 'To update shell completion support for "%s", your existing '.
+ '"%s" file will be modified. This line will be removed:',
+ $shell,
+ $file_display),
+ $replace_display,
+ pht('This line will be added:'),
+ $line);
+
+ $prompt = pht('Rewrite this file?');
+ } else {
+ if ($is_new) {
+ $new_data = $line."\n";
+
+ echo tsprintf(
+ "%s\n\n %s\n",
+ pht(
+ 'To install shell completion support for "%s", a new "%s" file '.
+ 'will be created with this content:',
+ $shell,
+ $file_display),
+ $line);
+
+ $prompt = pht('Create this file?');
+ } else {
+ $new_data = rtrim($data)."\n\n".$line."\n";
+
+ echo tsprintf(
+ "%s\n\n %s\n",
+ pht(
+ 'To install shell completion support for "%s", this line will be '.
+ 'added to your existing "%s" file:',
+ $shell,
+ $file_display),
+ $line);
+
+ $prompt = pht('Append to this file?');
+ }
+ }
+
+ // TOOLSETS: Generalize prompting.
+
+ if (!phutil_console_confirm($prompt, false)) {
+ throw new PhutilArgumentUsageException(pht('Aborted.'));
+ }
+
+ Filesystem::writeFile($file_path, $new_data);
+
+ $log->writeSuccess(
+ pht('INSTALLED'),
+ pht(
+ 'Installed shell completion support for "%s" to "%s".',
+ $shell,
+ $file_display));
}
- protected function shouldShellComplete() {
- return false;
+ private function selectShell(array $shells, $shell_arg) {
+ foreach ($shells as $shell) {
+ if ($shell['key'] === $shell_arg) {
+ return $shell_arg;
+ }
+ }
+
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'The shell "%s" is not supported. Supported shells are: %s.',
+ $shell_arg,
+ implode(', ', ipull($shells, 'key'))));
+ }
+
+ private function detectShell(array $shells) {
+ // NOTE: The "BASH_VERSION" and "ZSH_VERSION" shell variables are not
+ // passed to subprocesses, so we can't inspect them to figure out which
+ // shell launched us. If we could figure this out in some other way, it
+ // would be nice to do so.
+
+ // Instead, just look at "SHELL" (the user's startup shell).
+
+ $log = $this->getLogEngine();
+
+ $detected = array();
+ $log->writeStatus(
+ pht('DETECT'),
+ pht('Detecting current shell...'));
+
+ $shell_env = getenv('SHELL');
+ if (!strlen($shell_env)) {
+ $log->writeWarning(
+ pht('SHELL'),
+ pht(
+ 'The "SHELL" environment variable is not defined, so it can '.
+ 'not be used to detect the shell to install rules for.'));
+ } else {
+ $found = false;
+ foreach ($shells as $shell) {
+ if ($shell['path'] !== $shell_env) {
+ continue;
+ }
+
+ $found = true;
+ $detected[] = $shell['key'];
+
+ $log->writeSuccess(
+ pht('SHELL'),
+ pht(
+ 'The "SHELL" environment variable has value "%s", so the '.
+ 'target shell was detected as "%s".',
+ $shell_env,
+ $shell['key']));
+ }
+
+ if (!$found) {
+ $log->writeStatus(
+ pht('SHELL'),
+ pht(
+ 'The "SHELL" environment variable does not match any recognized '.
+ 'shell.'));
+ }
+ }
+
+ if (!$detected) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Unable to detect any supported shell, so autocompletion rules '.
+ 'can not be installed. Use "--shell" to select a shell.'));
+ } else if (count($detected) > 1) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Multiple supported shells were detected. Unable to determine '.
+ 'which shell to install autocompletion rules for. Use "--shell" '.
+ 'to select a shell.'));
+ }
+
+ return head($detected);
}
- public function run() {
- $pos = $this->getArgument('current');
- $argv = $this->getArgument('argv', array());
+ private function runGenerate() {
+ $log = $this->getLogEngine();
+
+ $toolsets = ArcanistToolset::newToolsetMap();
+
+ $log->writeStatus(
+ pht('GENERATE'),
+ pht('Generating shell completion rules...'));
+
+ $shells = array('bash');
+ foreach ($shells as $shell) {
+
+ $rules = array();
+ foreach ($toolsets as $toolset) {
+ $rules[] = $this->newCompletionRules($toolset, $shell);
+ }
+ $rules = implode("\n", $rules);
+
+ $rules_path = $this->getShellPath('rules/'.$shell.'-rules.sh');
+
+ // If a write wouldn't change anything, skip the write. This allows
+ // "arc shell-complete" to work if "arcanist/" is on a read-only NFS
+ // filesystem or something unusual like that.
+
+ $skip_write = false;
+ if (Filesystem::pathExists($rules_path)) {
+ $current = Filesystem::readFile($rules_path);
+ if ($current === $rules) {
+ $skip_write = true;
+ }
+ }
+
+ if ($skip_write) {
+ $log->writeStatus(
+ pht('SKIP'),
+ pht(
+ 'Rules are already up to date for "%s" in: %s',
+ $shell,
+ Filesystem::readablePath($rules_path)));
+ } else {
+ Filesystem::writeFile($rules_path, $rules);
+ $log->writeStatus(
+ pht('RULES'),
+ pht(
+ 'Wrote updated completion rules for "%s" to: %s.',
+ $shell,
+ Filesystem::readablePath($rules_path)));
+ }
+ }
+ }
+
+ private function newCompletionRules(ArcanistToolset $toolset, $shell) {
+ $template_path = $this->getShellPath('templates/'.$shell.'-template.sh');
+ $template = Filesystem::readFile($template_path);
+
+ $variables = array(
+ 'BIN' => $toolset->getToolsetKey(),
+ );
+
+ foreach ($variables as $key => $value) {
+ $template = str_replace('{{{'.$key.'}}}', $value, $template);
+ }
+
+ return $template;
+ }
+
+ private function getShellPath($to_file = null) {
+ $arc_root = dirname(phutil_get_library_root('arcanist'));
+ return $arc_root.'/support/shell/'.$to_file;
+ }
+
+ private function runAutocomplete() {
+ $argv = $this->getArgument('argv');
$argc = count($argv);
- if ($pos === null) {
+
+ $pos = $this->getArgument('current');
+ if (!$pos) {
$pos = $argc - 1;
}
if ($pos > $argc) {
throw new ArcanistUsageException(
pht(
- 'Specified position is greater than the number of '.
- 'arguments provided.'));
+ 'Argument position specified with "--current" ("%s") is greater '.
+ 'than the number of arguments provided ("%s").',
+ new PhutilNumber($pos),
+ new PhutilNumber($argc)));
}
- // Determine which revision control system the working copy uses, so we
- // can filter out commands and flags which aren't supported. If we can't
- // figure it out, just return all flags/commands.
- $vcs = null;
+ $workflows = $this->getRuntime()->getWorkflows();
- // We have to build our own because if we requiresWorkingCopy() we'll throw
- // if we aren't in a .arcconfig directory. We probably still can't do much,
- // but commands can raise more detailed errors.
- $configuration_manager = $this->getConfigurationManager();
- $working_copy = ArcanistWorkingCopyIdentity::newFromPath(getcwd());
- if ($working_copy->getVCSType()) {
- $configuration_manager->setWorkingCopyIdentity($working_copy);
- $repository_api = ArcanistRepositoryAPI::newAPIFromConfigurationManager(
- $configuration_manager);
+ // NOTE: This isn't quite right. For example, "arc --con<tab>" will
+ // try to autocomplete workflows named "--con", but it should actually
+ // autocomplete global flags and suggest "--config".
- $vcs = $repository_api->getSourceControlSystemName();
- }
+ $is_workflow = ($pos <= 1);
- $arc_config = $this->getArcanistConfiguration();
+ if ($is_workflow) {
+ // NOTE: There was previously some logic to try to filter impossible
+ // workflows out of the completion list based on the VCS in the current
+ // working directory: for example, if you're in an SVN working directory,
+ // "arc a" is unlikely to complete to "arc amend" because "amend" does
+ // not support SVN. It's not clear this logic is valuable, but we could
+ // consider restoring it if good use cases arise.
- if ($pos <= 1) {
- $workflows = $arc_config->buildAllWorkflows();
+ // TOOLSETS: Restore the ability for workflows to opt out of shell
+ // completion. It is exceptionally unlikely that users want to shell
+ // complete obscure or internal workflows, like "arc shell-complete"
+ // itself. Perhaps a good behavior would be to offer these as
+ // completions if they are the ONLY available completion, since a user
+ // who has typed "arc shell-comp<tab>" likely does want "shell-complete".
$complete = array();
- foreach ($workflows as $name => $workflow) {
- if (!$workflow->shouldShellComplete()) {
+ foreach ($workflows as $workflow) {
+ $complete[] = $workflow->getWorkflowName();
+ }
+
+ foreach ($this->getConfig('aliases') as $alias) {
+ if ($alias->getException()) {
continue;
}
- $workflow->setArcanistConfiguration($this->getArcanistConfiguration());
- $workflow->setConfigurationManager($this->getConfigurationManager());
-
- if ($vcs || $workflow->requiresWorkingCopy()) {
- $supported_vcs = $workflow->getSupportedRevisionControlSystems();
- if (!in_array($vcs, $supported_vcs)) {
- continue;
- }
+ if ($alias->getToolset() !== $this->getToolsetKey()) {
+ continue;
}
- $complete[] = $name;
+ $complete[] = $alias->getTrigger();
}
- // Also permit autocompletion of "arc alias" commands.
- $aliases = ArcanistAliasWorkflow::getAliases($configuration_manager);
- foreach ($aliases as $key => $value) {
- $complete[] = $key;
+ // Remove invalid possibilities. For example, if the user has typed
+ // "skun<tab>", it obviously can't complete to "zebra". We don't really
+ // need to do this filtering ourselves: the shell completion will
+ // automatically match things for us even if we emit impossible results.
+ // However, it's a little easier to debug the raw output if we clean it
+ // up here before printing it out.
+ $partial = $argv[$pos];
+ foreach ($complete as $key => $candidate) {
+ if (strncmp($partial, $candidate, strlen($partial))) {
+ unset($complete[$key]);
+ }
+ }
+
+ if ($complete) {
+ return $this->suggestStrings($complete);
+ } else {
+ return $this->suggestNothing();
}
- echo implode(' ', $complete)."\n";
- return 0;
} else {
- $workflow = $arc_config->buildWorkflow($argv[1]);
+ // TOOLSETS: We should resolve aliases before picking a workflow, so
+ // that if you alias "arc draft" to "arc diff --draft", we can suggest
+ // other "diff" flags when you type "arc draft --q<tab>".
+
+ // TOOLSETS: It's possible the workflow isn't in position 1. The user
+ // may be running "arc --trace diff --dra<tab>", for example.
+
+ $workflow = idx($workflows, $argv[1]);
if (!$workflow) {
- list($new_command, $new_args) = ArcanistAliasWorkflow::resolveAliases(
- $argv[1],
- $arc_config,
- array_slice($argv, 2),
- $configuration_manager);
- if ($new_command) {
- $workflow = $arc_config->buildWorkflow($new_command);
- }
- if (!$workflow) {
- return 1;
- } else {
- $argv = array_merge(
- array($argv[0]),
- array($new_command),
- $new_args);
- }
+ return $this->suggestNothing();
}
- $arguments = $workflow->getArguments();
+ $arguments = $workflow->getWorkflowArguments();
+ $arguments = mpull($arguments, null, 'getKey');
+ $argument = null;
$prev = idx($argv, $pos - 1, null);
if (!strncmp($prev, '--', 2)) {
$prev = substr($prev, 2);
- } else {
- $prev = null;
- }
-
- if ($prev !== null &&
- isset($arguments[$prev]) &&
- isset($arguments[$prev]['param'])) {
-
- $type = idx($arguments[$prev], 'paramtype');
- switch ($type) {
- case 'file':
- echo "FILE\n";
- break;
- case 'complete':
- echo implode(' ', $workflow->getShellCompletions($argv))."\n";
- break;
- default:
- echo "ARGUMENT\n";
- break;
- }
- return 0;
- } else {
- $output = array();
- foreach ($arguments as $argument => $spec) {
- if ($argument == '*') {
- continue;
- }
- if ($vcs &&
- isset($spec['supports']) &&
- !in_array($vcs, $spec['supports'])) {
- continue;
- }
- $output[] = '--'.$argument;
+ $argument = idx($arguments, $prev);
+ }
+
+ // If the last argument was a "--thing" argument, test if "--thing" is
+ // a parameterized argument. If it is, the next argument should be a
+ // parameter.
+
+ if ($argument && strlen($argument->getParameter())) {
+ if ($argument->getIsPathArgument()) {
+ return $this->suggestPaths();
+ } else {
+ return $this->suggestNothing();
}
- $cur = idx($argv, $pos, '');
- $any_match = false;
+ // TOOLSETS: We can allow workflows and arguments to provide a specific
+ // list of completeable values, like the "--shell" argument for this
+ // workflow.
+ }
- if (strlen($cur)) {
- foreach ($output as $possible) {
- if (!strncmp($possible, $cur, strlen($cur))) {
- $any_match = true;
- }
- }
+ $flags = array();
+ $wildcard = null;
+ foreach ($arguments as $argument) {
+ if ($argument->getWildcard()) {
+ $wildcard = $argument;
+ continue;
}
- if (!$any_match && isset($arguments['*'])) {
- // TODO: This is mega hacktown but something else probably breaks
- // if we use a rich argument specification; fix it when we move to
- // PhutilArgumentParser since everything will need to be tested then
- // anyway.
- if ($arguments['*'] == 'branch' && isset($repository_api)) {
- $branches = $repository_api->getAllBranches();
- $branches = ipull($branches, 'name');
- $output = $branches;
- } else {
- $output = array('FILE');
+ $flags[] = '--'.$argument->getKey();
+ }
+
+ $current = idx($argv, $pos, '');
+ $matches = array();
+ if (strlen($current)) {
+ foreach ($flags as $possible) {
+ if (!strncmp($possible, $current, strlen($current))) {
+ $matches[] = $possible;
}
}
+ }
+
+ // If whatever the user is completing does not match the prefix of any
+ // flag, try to autcomplete a wildcard argument if it has some kind of
+ // meaningful completion. For example, "arc lint READ<tab>" should
+ // autocomplete a file.
+
+ if (!$matches && $wildcard) {
- echo implode(' ', $output)."\n";
+ // TOOLSETS: There was previously some very questionable support for
+ // autocompleting branches here. This could be moved into Arguments
+ // and Workflows.
- return 0;
+ if ($wildcard->getIsPathArgument()) {
+ return $this->suggestPaths();
+ }
}
+
+ return $this->suggestStrings($matches);
}
}
+ private function suggestPaths() {
+ echo "FILE\n";
+ return 0;
+ }
+
+ private function suggestNothing() {
+ echo "ARGUMENT\n";
+ return 0;
+ }
+
+ private function suggestStrings(array $strings) {
+ echo implode(' ', $strings)."\n";
+ return 0;
+ }
+
}
diff --git a/support/shell/hooks/bash-completion.sh b/support/shell/hooks/bash-completion.sh
new file mode 100644
--- /dev/null
+++ b/support/shell/hooks/bash-completion.sh
@@ -0,0 +1,9 @@
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
+
+# Try to generate the shell completion rules if they do not yet exist.
+if [ ! -f "${SCRIPTDIR}/bash-rules.sh" ]; then
+ arc shell-complete --generate >/dev/null 2>/dev/null
+fi;
+
+# Source the shell completion rules.
+source "${SCRIPTDIR}/../rules/bash-rules.sh"
diff --git a/support/shell/rules/.keep b/support/shell/rules/.keep
new file mode 100644
diff --git a/resources/shell/bash-completion b/support/shell/templates/bash-template.sh
rename from resources/shell/bash-completion
rename to support/shell/templates/bash-template.sh
--- a/resources/shell/bash-completion
+++ b/support/shell/templates/bash-template.sh
@@ -1,12 +1,9 @@
-if [[ -n ${ZSH_VERSION-} ]]; then
- autoload -U +X bashcompinit && bashcompinit
-fi
-
-_arc ()
+_arcanist_complete_{{{BIN}}} ()
{
- CUR="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=()
- OPTS=$(echo | arc shell-complete --current ${COMP_CWORD} -- ${COMP_WORDS[@]})
+
+ CUR="${COMP_WORDS[COMP_CWORD]}"
+ OPTS=$(echo | {{{BIN}}} shell-complete --current ${COMP_CWORD} -- ${COMP_WORDS[@]} 2>/dev/null)
if [ $? -ne 0 ]; then
return $?
@@ -23,4 +20,4 @@
COMPREPLY=( $(compgen -W "${OPTS}" -- ${CUR}) )
}
-complete -F _arc -o filenames arc
+complete -F _arcanist_complete_{{{BIN}}} -o filenames {{{BIN}}}

File Metadata

Mime Type
text/plain
Expires
Sun, Dec 22, 1:12 PM (10 h, 20 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6918330
Default Alt Text
D19700.diff (27 KB)

Event Timeline