mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 10:58:58 +00:00
Support zsh completion
This commit is contained in:
parent
2e306d3249
commit
64c1579544
@ -1,3 +1,8 @@
|
|||||||
|
2018-12-23 Jay Berkenbilt <ejb@ql.org>
|
||||||
|
|
||||||
|
* Tweak completion so it works with zsh as well using
|
||||||
|
bashcompinit.
|
||||||
|
|
||||||
2018-12-22 Jay Berkenbilt <ejb@ql.org>
|
2018-12-22 Jay Berkenbilt <ejb@ql.org>
|
||||||
|
|
||||||
* Add new options --json, --json-key, and --json-object to
|
* Add new options --json, --json-key, and --json-object to
|
||||||
|
@ -303,11 +303,12 @@ make
|
|||||||
<sect1 id="ref.shell-completion">
|
<sect1 id="ref.shell-completion">
|
||||||
<title>Shell Completion</title>
|
<title>Shell Completion</title>
|
||||||
<para>
|
<para>
|
||||||
Starting in qpdf version 8.3.0, qpdf provides its own bash
|
Starting in qpdf version 8.3.0, qpdf provides its own completion
|
||||||
completion support. You can enable bash completion with
|
support for zsh and bash. You can enable bash completion with
|
||||||
<command>eval $(qpdf --completion-bash)</command>. If
|
<command>eval $(qpdf --completion-bash)</command> and zsh
|
||||||
<command>qpdf</command> is not in your path, you should invoke it
|
completion with <command>eval $(qpdf --completion-zsh)</command>.
|
||||||
above with an absolute path. If you invoke it with a relative
|
If <command>qpdf</command> is not in your path, you should invoke
|
||||||
|
it above with an absolute path. If you invoke it with a relative
|
||||||
path, it will warn you, and the completion won't work if you're in
|
path, it will warn you, and the completion won't work if you're in
|
||||||
a different directory.
|
a different directory.
|
||||||
</para>
|
</para>
|
||||||
@ -342,6 +343,24 @@ make
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--completion-bash</option></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Output a completion command you can eval to enable shell
|
||||||
|
completion from bash.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--completion-zsh</option></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Output a completion command you can eval to enable shell
|
||||||
|
completion from zsh.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>--password=password</option></term>
|
<term><option>--password=password</option></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
|
96
qpdf/qpdf.cc
96
qpdf/qpdf.cc
@ -446,6 +446,7 @@ class ArgParser
|
|||||||
void argVersion();
|
void argVersion();
|
||||||
void argCopyright();
|
void argCopyright();
|
||||||
void argCompletionBash();
|
void argCompletionBash();
|
||||||
|
void argCompletionZsh();
|
||||||
void argJsonHelp();
|
void argJsonHelp();
|
||||||
void argPositional(char* arg);
|
void argPositional(char* arg);
|
||||||
void argPassword(char* parameter);
|
void argPassword(char* parameter);
|
||||||
@ -520,7 +521,7 @@ class ArgParser
|
|||||||
void readArgsFromFile(char const* filename);
|
void readArgsFromFile(char const* filename);
|
||||||
void doFinalChecks();
|
void doFinalChecks();
|
||||||
void addOptionsToCompletions();
|
void addOptionsToCompletions();
|
||||||
void addChoicesToCompletions(std::string const&);
|
void addChoicesToCompletions(std::string const&, std::string const&);
|
||||||
void handleCompletion();
|
void handleCompletion();
|
||||||
std::vector<PageSpec> parsePagesOptions();
|
std::vector<PageSpec> parsePagesOptions();
|
||||||
void parseRotationParameter(std::string const&);
|
void parseRotationParameter(std::string const&);
|
||||||
@ -534,6 +535,7 @@ class ArgParser
|
|||||||
Options& o;
|
Options& o;
|
||||||
int cur_arg;
|
int cur_arg;
|
||||||
bool bash_completion;
|
bool bash_completion;
|
||||||
|
bool zsh_completion;
|
||||||
std::string bash_prev;
|
std::string bash_prev;
|
||||||
std::string bash_cur;
|
std::string bash_cur;
|
||||||
std::string bash_line;
|
std::string bash_line;
|
||||||
@ -556,7 +558,8 @@ ArgParser::ArgParser(int argc, char* argv[], Options& o) :
|
|||||||
argv(argv),
|
argv(argv),
|
||||||
o(o),
|
o(o),
|
||||||
cur_arg(0),
|
cur_arg(0),
|
||||||
bash_completion(false)
|
bash_completion(false),
|
||||||
|
zsh_completion(false)
|
||||||
{
|
{
|
||||||
option_table = &main_option_table;
|
option_table = &main_option_table;
|
||||||
initOptionTable();
|
initOptionTable();
|
||||||
@ -619,6 +622,7 @@ ArgParser::initOptionTable()
|
|||||||
(*t)["version"] = oe_bare(&ArgParser::argVersion);
|
(*t)["version"] = oe_bare(&ArgParser::argVersion);
|
||||||
(*t)["copyright"] = oe_bare(&ArgParser::argCopyright);
|
(*t)["copyright"] = oe_bare(&ArgParser::argCopyright);
|
||||||
(*t)["completion-bash"] = oe_bare(&ArgParser::argCompletionBash);
|
(*t)["completion-bash"] = oe_bare(&ArgParser::argCompletionBash);
|
||||||
|
(*t)["completion-zsh"] = oe_bare(&ArgParser::argCompletionZsh);
|
||||||
(*t)["json-help"] = oe_bare(&ArgParser::argJsonHelp);
|
(*t)["json-help"] = oe_bare(&ArgParser::argJsonHelp);
|
||||||
|
|
||||||
t = &this->main_option_table;
|
t = &this->main_option_table;
|
||||||
@ -809,6 +813,9 @@ ArgParser::argHelp()
|
|||||||
void
|
void
|
||||||
ArgParser::argCompletionBash()
|
ArgParser::argCompletionBash()
|
||||||
{
|
{
|
||||||
|
std::cout << "complete -o bashdefault -o default -o nospace"
|
||||||
|
<< " -C " << argv[0] << " " << whoami << std::endl;
|
||||||
|
// Put output before error so calling from zsh works properly
|
||||||
std::string path = argv[0];
|
std::string path = argv[0];
|
||||||
size_t slash = path.find('/');
|
size_t slash = path.find('/');
|
||||||
if ((slash != 0) && (slash != std::string::npos))
|
if ((slash != 0) && (slash != std::string::npos))
|
||||||
@ -816,10 +823,14 @@ ArgParser::argCompletionBash()
|
|||||||
std::cerr << "WARNING: qpdf completion enabled"
|
std::cerr << "WARNING: qpdf completion enabled"
|
||||||
<< " using relative path to qpdf" << std::endl;
|
<< " using relative path to qpdf" << std::endl;
|
||||||
}
|
}
|
||||||
std::cout << "complete -o bashdefault -o default -o nospace"
|
|
||||||
<< " -C " << argv[0] << " " << whoami << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::argCompletionZsh()
|
||||||
|
{
|
||||||
|
std::cout << "autoload -U +X bashcompinit && bashcompinit && ";
|
||||||
|
argCompletionBash();
|
||||||
|
}
|
||||||
void
|
void
|
||||||
ArgParser::argJsonHelp()
|
ArgParser::argJsonHelp()
|
||||||
{
|
{
|
||||||
@ -1543,6 +1554,7 @@ Basic Options\n\
|
|||||||
--copyright show qpdf's copyright and license information\n\
|
--copyright show qpdf's copyright and license information\n\
|
||||||
--help show command-line argument help\n\
|
--help show command-line argument help\n\
|
||||||
--completion-bash output a bash complete command you can eval\n\
|
--completion-bash output a bash complete command you can eval\n\
|
||||||
|
--completion-zsh output a zsh complete command you can eval\n\
|
||||||
--password=password specify a password for accessing encrypted files\n\
|
--password=password specify a password for accessing encrypted files\n\
|
||||||
--verbose provide additional informational output\n\
|
--verbose provide additional informational output\n\
|
||||||
--progress give progress indicators while writing output\n\
|
--progress give progress indicators while writing output\n\
|
||||||
@ -2198,13 +2210,61 @@ ArgParser::checkCompletion()
|
|||||||
// cursor for completion purposes.
|
// cursor for completion purposes.
|
||||||
bash_line = bash_line.substr(0, p);
|
bash_line = bash_line.substr(0, p);
|
||||||
}
|
}
|
||||||
if (argc >= 4)
|
// Set bash_cur and bash_prev based on bash_line rather than
|
||||||
|
// relying on argv. This enables us to use bashcompinit to get
|
||||||
|
// completion in zsh too since bashcompinit sets COMP_LINE and
|
||||||
|
// COMP_POINT but doesn't invoke the command with options like
|
||||||
|
// bash does.
|
||||||
|
|
||||||
|
// p is equal to length of the string. Walk backwards looking
|
||||||
|
// for the first separator. bash_cur is everything after the
|
||||||
|
// last separator, possibly empty.
|
||||||
|
char sep(0);
|
||||||
|
while (--p > 0)
|
||||||
{
|
{
|
||||||
bash_cur = argv[2];
|
char ch = bash_line.at(p);
|
||||||
bash_prev = argv[3];
|
if ((ch == ' ') || (ch == '=') || (ch == ':'))
|
||||||
handleBashArguments();
|
{
|
||||||
bash_completion = true;
|
sep = ch;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
bash_cur = bash_line.substr(1+p, std::string::npos);
|
||||||
|
if ((sep == ':') || (sep == '='))
|
||||||
|
{
|
||||||
|
// Bash sets prev to the non-space separator if any.
|
||||||
|
// Actually, if there are multiple separators in a row,
|
||||||
|
// they are all included in prev, but that detail is not
|
||||||
|
// important to us and not worth coding.
|
||||||
|
bash_prev = bash_line.substr(p, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Go back to the last separator and set prev based on
|
||||||
|
// that.
|
||||||
|
int p1 = p;
|
||||||
|
while (--p1 > 0)
|
||||||
|
{
|
||||||
|
char ch = bash_line.at(p1);
|
||||||
|
if ((ch == ' ') || (ch == ':') || (ch == '='))
|
||||||
|
{
|
||||||
|
bash_prev = bash_line.substr(p1 + 1, p - p1 - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bash_prev.empty())
|
||||||
|
{
|
||||||
|
bash_prev = bash_line.substr(0, p);
|
||||||
|
}
|
||||||
|
if (argc == 1)
|
||||||
|
{
|
||||||
|
// This is probably zsh using bashcompinit. There are a
|
||||||
|
// few differences in the expected output.
|
||||||
|
zsh_completion = true;
|
||||||
|
}
|
||||||
|
handleBashArguments();
|
||||||
|
bash_completion = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2377,7 +2437,8 @@ ArgParser::doFinalChecks()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ArgParser::addChoicesToCompletions(std::string const& option)
|
ArgParser::addChoicesToCompletions(std::string const& option,
|
||||||
|
std::string const& extra_prefix)
|
||||||
{
|
{
|
||||||
if (this->option_table->count(option) != 0)
|
if (this->option_table->count(option) != 0)
|
||||||
{
|
{
|
||||||
@ -2385,7 +2446,7 @@ ArgParser::addChoicesToCompletions(std::string const& option)
|
|||||||
for (std::set<std::string>::iterator iter = oe.choices.begin();
|
for (std::set<std::string>::iterator iter = oe.choices.begin();
|
||||||
iter != oe.choices.end(); ++iter)
|
iter != oe.choices.end(); ++iter)
|
||||||
{
|
{
|
||||||
completions.insert(*iter);
|
completions.insert(extra_prefix + *iter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2414,6 +2475,7 @@ ArgParser::addOptionsToCompletions()
|
|||||||
void
|
void
|
||||||
ArgParser::handleCompletion()
|
ArgParser::handleCompletion()
|
||||||
{
|
{
|
||||||
|
std::string extra_prefix;
|
||||||
if (this->completions.empty())
|
if (this->completions.empty())
|
||||||
{
|
{
|
||||||
// Detect --option=... Bash treats the = as a word separator.
|
// Detect --option=... Bash treats the = as a word separator.
|
||||||
@ -2450,7 +2512,12 @@ ArgParser::handleCompletion()
|
|||||||
}
|
}
|
||||||
if (! choice_option.empty())
|
if (! choice_option.empty())
|
||||||
{
|
{
|
||||||
addChoicesToCompletions(choice_option);
|
if (zsh_completion)
|
||||||
|
{
|
||||||
|
// zsh wants --option=choice rather than just choice
|
||||||
|
extra_prefix = "--" + choice_option + "=";
|
||||||
|
}
|
||||||
|
addChoicesToCompletions(choice_option, extra_prefix);
|
||||||
}
|
}
|
||||||
else if ((! bash_cur.empty()) && (bash_cur.at(0) == '-'))
|
else if ((! bash_cur.empty()) && (bash_cur.at(0) == '-'))
|
||||||
{
|
{
|
||||||
@ -2467,11 +2534,12 @@ ArgParser::handleCompletion()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::string prefix = extra_prefix + bash_cur;
|
||||||
for (std::set<std::string>::iterator iter = completions.begin();
|
for (std::set<std::string>::iterator iter = completions.begin();
|
||||||
iter != completions.end(); ++iter)
|
iter != completions.end(); ++iter)
|
||||||
{
|
{
|
||||||
if (this->bash_cur.empty() ||
|
if (prefix.empty() ||
|
||||||
((*iter).substr(0, bash_cur.length()) == bash_cur))
|
((*iter).substr(0, prefix.length()) == prefix))
|
||||||
{
|
{
|
||||||
std::cout << *iter << std::endl;
|
std::cout << *iter << std::endl;
|
||||||
}
|
}
|
||||||
|
@ -116,6 +116,7 @@ my @completion_tests = (
|
|||||||
['qpdf --decode-l', undef, 'decode-l'],
|
['qpdf --decode-l', undef, 'decode-l'],
|
||||||
['qpdf --decode-lzzz', 15, 'decode-l'],
|
['qpdf --decode-lzzz', 15, 'decode-l'],
|
||||||
['qpdf --decode-level=', undef, 'decode-level'],
|
['qpdf --decode-level=', undef, 'decode-level'],
|
||||||
|
['qpdf --decode-level=g', undef, 'decode-level-g'],
|
||||||
['qpdf --check -', undef, 'later-arg'],
|
['qpdf --check -', undef, 'later-arg'],
|
||||||
['qpdf infile outfile oops --ch', undef, 'usage-empty'],
|
['qpdf infile outfile oops --ch', undef, 'usage-empty'],
|
||||||
['qpdf --encrypt \'user " password\' ', undef, 'quoting'],
|
['qpdf --encrypt \'user " password\' ', undef, 'quoting'],
|
||||||
@ -124,16 +125,26 @@ my @completion_tests = (
|
|||||||
['qpdf --encrypt "user pass\'word" ', undef, 'quoting'],
|
['qpdf --encrypt "user pass\'word" ', undef, 'quoting'],
|
||||||
['qpdf --encrypt user\ password ', undef, 'quoting'],
|
['qpdf --encrypt user\ password ', undef, 'quoting'],
|
||||||
);
|
);
|
||||||
$n_tests += scalar(@completion_tests);
|
$n_tests += 2 * scalar(@completion_tests);
|
||||||
foreach my $c (@completion_tests)
|
foreach my $c (@completion_tests)
|
||||||
{
|
{
|
||||||
my ($cmd, $point, $description) = @$c;
|
my ($cmd, $point, $description) = @$c;
|
||||||
my $out = "completion-$description.out";
|
my $out = "completion-$description.out";
|
||||||
|
my $zout = "completion-$description-zsh.out";
|
||||||
|
if (! -f $zout)
|
||||||
|
{
|
||||||
|
$zout = $out;
|
||||||
|
}
|
||||||
$td->runtest("bash completion: $description",
|
$td->runtest("bash completion: $description",
|
||||||
{$td->COMMAND => [@{bash_completion($cmd, $point)}],
|
{$td->COMMAND => [@{bash_completion($cmd, $point)}],
|
||||||
$td->FILTER => "perl filter-completion.pl $out"},
|
$td->FILTER => "perl filter-completion.pl $out"},
|
||||||
{$td->FILE => "$out", $td->EXIT_STATUS => 0},
|
{$td->FILE => "$out", $td->EXIT_STATUS => 0},
|
||||||
$td->NORMALIZE_NEWLINES);
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
$td->runtest("zsh completion: $description",
|
||||||
|
{$td->COMMAND => [@{zsh_completion($cmd, $point)}],
|
||||||
|
$td->FILTER => "perl filter-completion.pl $zout"},
|
||||||
|
{$td->FILE => "$zout", $td->EXIT_STATUS => 0},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
}
|
}
|
||||||
|
|
||||||
show_ntests();
|
show_ntests();
|
||||||
@ -3208,6 +3219,16 @@ sub bash_completion
|
|||||||
"qpdf", $this, $cur, $prev];
|
"qpdf", $this, $cur, $prev];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub zsh_completion
|
||||||
|
{
|
||||||
|
my ($line, $point) = @_;
|
||||||
|
if (! defined $point)
|
||||||
|
{
|
||||||
|
$point = length($line);
|
||||||
|
}
|
||||||
|
['env', "COMP_LINE=$line", "COMP_POINT=$point", "qpdf"];
|
||||||
|
}
|
||||||
|
|
||||||
sub check_pdf
|
sub check_pdf
|
||||||
{
|
{
|
||||||
my ($description, $command, $output, $status) = @_;
|
my ($description, $command, $output, $status) = @_;
|
||||||
|
6
qpdf/qtest/qpdf/completion-decode-level-g-zsh.out
Normal file
6
qpdf/qtest/qpdf/completion-decode-level-g-zsh.out
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
--decode-level=generalized
|
||||||
|
!--decode-level=all
|
||||||
|
!--decode-level=none
|
||||||
|
!all
|
||||||
|
!generalized
|
||||||
|
!none
|
6
qpdf/qtest/qpdf/completion-decode-level-g.out
Normal file
6
qpdf/qtest/qpdf/completion-decode-level-g.out
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
generalized
|
||||||
|
!--decode-level=all
|
||||||
|
!--decode-level=generalized
|
||||||
|
!--decode-level=none
|
||||||
|
!all
|
||||||
|
!none
|
7
qpdf/qtest/qpdf/completion-decode-level-zsh.out
Normal file
7
qpdf/qtest/qpdf/completion-decode-level-zsh.out
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
--decode-level=all
|
||||||
|
--decode-level=generalized
|
||||||
|
--decode-level=none
|
||||||
|
!--help
|
||||||
|
!all
|
||||||
|
!generalized
|
||||||
|
!none
|
@ -1,4 +1,7 @@
|
|||||||
all
|
all
|
||||||
generalized
|
generalized
|
||||||
none
|
none
|
||||||
|
!--decode-level=all
|
||||||
|
!--decode-level=generalized
|
||||||
|
!--decode-level=none
|
||||||
!--help
|
!--help
|
||||||
|
Loading…
Reference in New Issue
Block a user