mirror of
git://git.code.sf.net/p/zsh/code
synced 2024-12-29 16:25:35 +01:00
340 lines
10 KiB
Perl
Executable file
340 lines
10 KiB
Perl
Executable file
#!/usr/local/bin/perl -w
|
|
#
|
|
# ``Wee have also Shelles, thee Lyke of whych you knowe not, wherein
|
|
# thee User may with thee merest Presse of thee Tabbe-Keye expande
|
|
# or compleat al Maner of Wordes and such-like Diversities.''
|
|
# - Francis Bacon, `New Atlantis' (or not).
|
|
#
|
|
# Convert tcsh "complete" statements to zsh "compctl" statements.
|
|
# Runs as a filter. Should ignore anything which isn't a "complete".
|
|
# It expects each "complete" statement to be the first thing on a line.
|
|
# All the examples in the tcsh manual give sensible results.
|
|
# Author: Peter Stephenson <pws@ibmth.df.unipi.it>
|
|
#
|
|
# Option:
|
|
# -x (exact): only applies in the case of command disambiguation (is
|
|
# that really a word?) If you have lines like
|
|
# complete '-co*' 'p/0/(compress)'
|
|
# (which makes co<TAB> always complete to `compress') then the
|
|
# resulting "compctl" statements will produce one of two behaviours:
|
|
# (1) By default (like tcsh), com<TAB> etc. will also complete to
|
|
# "compress" and nothing else.
|
|
# (2) With -x, com<TAB> does ordinary command completion: this is
|
|
# more flexible.
|
|
# I don't understand what the hyphen in complete does and I've ignored it.
|
|
#
|
|
# Notes:
|
|
# (1) The -s option is the way to do backquote expansion. In zsh,
|
|
# "compctl -s '`users`' talk" works (duplicates are removed).
|
|
# (2) Complicated backquote completions should definitely be rewritten as
|
|
# shell functions (compctl's "-K func" option). Although most of
|
|
# these will be translated correctly, differences in shell syntax
|
|
# are not handled.
|
|
# (3) Replacement of $:n with the n'th word on the current line with
|
|
# backquote expansion now works; it is not necessarily the most
|
|
# efficient way of doing it in any given case, however.
|
|
# (4) I have made use of zsh's more sophisticated globbing to change
|
|
# things like ^foo.{a,b,c,d} to ^foo.(a|b|c|d), which works better.
|
|
# It's just possible in some cases you may want to change it back.
|
|
# (5) Make sure all command names with wildcards are processed together --
|
|
# they need to be lumped into one "compctl -C" or "compctl -D"
|
|
# statement for zsh.
|
|
# (6) Group completion (complete's g flag) is not built into zsh, so
|
|
# you need perl to be available to generate the groups. If this
|
|
# script is useful, I assume that's not a problem.
|
|
# (7) I don't know what `completing completions' means, so the X
|
|
# flag to complete is not handled.
|
|
|
|
# Handle options
|
|
if (@ARGV) {
|
|
($ARGV[0] eq '-x') && shift && ($opt_x = 1);
|
|
($ARGV[0] =~ /^-+$/) && shift;
|
|
}
|
|
|
|
# Function names used (via magic autoincrement) when cmdline words are needed
|
|
$funcnam = 'compfn001';
|
|
|
|
# Read next word on command line
|
|
sub getword {
|
|
local($word, $word2, $ret);
|
|
($_) = /^\s*(.*)$/;
|
|
while ($_ =~ /^\S/) {
|
|
if (/^[\']/) {
|
|
($word, $_) = /^\'([^\']*).(.*)$/;
|
|
} elsif (/^[\"]/) {
|
|
($word, $_) = /^\"([^\"]*).(.*)$/;
|
|
while ($word =~ /\\$/) {
|
|
chop($word);
|
|
($word2, $_) = /^([^\"]*).(.*)$/;
|
|
$word .= '"' . $word2;
|
|
}
|
|
} elsif (/\S/) {
|
|
($word, $_) = /^([^\s\\\'\"\#;]*)(.*)$/;
|
|
# Backslash: literal next character
|
|
/^\\(.)/ && (($word .= substr($_,1,1)),
|
|
($_ = substr($_,2)));
|
|
# Rest of line quoted or end of command
|
|
/^[\#;]/ && ($_ = '');
|
|
} else {
|
|
return undef;
|
|
}
|
|
length($word) && ($ret = defined($ret) ? $ret . $word : $word);
|
|
}
|
|
$ret;
|
|
}
|
|
|
|
# Interpret the x and arg in 'x/arg/type/'
|
|
sub getpat {
|
|
local($pat,$arg) = @_;
|
|
local($ret,$i);
|
|
if ($pat eq 'p') {
|
|
$ret = "p[$arg]";
|
|
} elsif ($pat eq 'n' || $pat eq 'N') {
|
|
$let = ($arg =~ /[*?|]/) ? 'C' : 'c';
|
|
$num = ($pat eq 'N') ? 2 : 1;
|
|
$ret = "${let}[-${num},$arg]";
|
|
} elsif ($pat eq 'c' || $pat eq 'C') {
|
|
# A few tricks to get zsh to ignore up to the end of
|
|
# any matched pattern.
|
|
if (($pat eq 'c' && $arg =~ /^\*([^*?]*)$/)) {
|
|
$ret = "n[-1,$1]";
|
|
} elsif ($arg =~ /[*?]([^*?]*)$/) {
|
|
length($1) && ($ret = " n[-1,$1]");
|
|
$ret = "C[0,$arg] $ret";
|
|
} else {
|
|
$let = ($pat eq 'c') ? 's' : 'S';
|
|
$ret = "${let}[$arg]";
|
|
}
|
|
}
|
|
$ret =~ s/'/'\\''/g;
|
|
$ret;
|
|
}
|
|
|
|
# Interpret the type in 'x/arg/type/'
|
|
sub gettype {
|
|
local ($_) = @_;
|
|
local($qual,$c,$glob,$ret,$b,$m,$e,@m);
|
|
$c = substr($_,0,1);
|
|
($c =~ /\w/) && (substr($_,1,1) eq ':') && ($glob = substr($_,2));
|
|
# Nothing (n) can be handled by returning nothing. (C.f. King Lear, I.i.)
|
|
if ($c =~ /[abcjuv]/) {
|
|
$ret = "-$c";
|
|
} elsif ($c eq 'C') {
|
|
if (defined($glob)) {
|
|
$ret = "-W $glob -/g '*(.*)'";
|
|
undef($glob);
|
|
} else {
|
|
$ret = '-c';
|
|
}
|
|
} elsif ($c eq 'S') {
|
|
$ret = '-k signals';
|
|
} elsif ($c eq 'd') {
|
|
if (defined($glob)) {
|
|
$qual = '-/';
|
|
} else {
|
|
$ret = '-/';
|
|
}
|
|
} elsif ($c eq 'D') {
|
|
if (defined($glob)) {
|
|
$ret = "-W $glob -/";
|
|
undef($glob);
|
|
} else {
|
|
$ret = '-/';
|
|
}
|
|
} elsif ($c eq 'e') {
|
|
$ret = '-E';
|
|
} elsif ($c eq 'f' && !$glob) {
|
|
$ret = '-f';
|
|
} elsif ($c eq 'F') {
|
|
if (defined($glob)) {
|
|
$ret = "-W $glob -f";
|
|
undef($glob);
|
|
} else {
|
|
$ret = '-f';
|
|
}
|
|
} elsif ($c eq 'g') {
|
|
$ret = "-s '\$(perl -e '\\''while ((\$name) = getgrent)\n" .
|
|
"{ print \$name, \"\\n\"; }'\\'')'";
|
|
} elsif ($c eq 'l') {
|
|
$ret = q!-k "(`limit | awk '{print $1}'`)"!;
|
|
} elsif ($c eq 'p') {
|
|
$ret = "-W $glob -f", undef($glob) if defined($glob);
|
|
} elsif ($c eq 's') {
|
|
$ret = '-p';
|
|
} elsif ($c eq 't') {
|
|
$qual = '.';
|
|
} elsif ($c eq 'T') {
|
|
if (defined($glob)) {
|
|
$ret = "-W $glob -g '*(.)'";
|
|
undef($glob);
|
|
} else {
|
|
$ret = "-g '*(.)'";
|
|
}
|
|
} elsif ($c eq 'x') {
|
|
$glob =~ s/'/'\\''/g;
|
|
$ret = "-X '$glob'";
|
|
undef($glob);
|
|
} elsif ($c eq '$') { # '){
|
|
$ret = "-k " . substr($_,1);
|
|
} elsif ($c eq '(') {
|
|
s/'/'\\''/g;
|
|
$ret = "-k '$_'";
|
|
} elsif ($c eq '`') {
|
|
# this took some working out...
|
|
if (s/\$:(\d+)/$foo=$1+1,"\${word[$foo]}"/ge) {
|
|
$ret = "-K $funcnam";
|
|
$genfunc .= <<"HERE";
|
|
function $funcnam {
|
|
local word
|
|
read -cA word
|
|
reply=($_)
|
|
}
|
|
HERE
|
|
$funcnam++;
|
|
} else {
|
|
s/'/'\\''/g;
|
|
$ret = "-s '$_'";
|
|
}
|
|
}
|
|
|
|
# foo{bar,ba,blak,sheap} -> foo(bar|ba|blak|sheap).
|
|
# This saves a lot of mess, since in zsh brace expansion occurs
|
|
# before globbing. I'm sorry, but I don't trust $` and $'.
|
|
while (defined($glob) && (($b,$m,$e) = ($glob =~ /^(.*)\{(.*)\}(.*)$/))
|
|
&& $m =~ /,/) {
|
|
@m = split(/,/, $m);
|
|
for ($i = 0; $i < @m; $i++) {
|
|
while ($m[$i] =~ /\\$/) {
|
|
substr($m[$i],-1,1) = "";
|
|
splice(@m,$i,2,"$m[$i]\\,$m[$i+1]");
|
|
}
|
|
}
|
|
$glob = $b . "(" . join('|',@m) . ")" . $e;
|
|
}
|
|
|
|
if ($qual) {
|
|
$glob || ($glob = '*');
|
|
$glob .= "($qual)";
|
|
}
|
|
$glob && (($glob =~ s/'/'\\''/g),($glob = "-g '$glob'"));
|
|
|
|
defined($ret) && defined($glob) && ($ret .= " $glob");
|
|
defined($ret) ? $ret : $glob;
|
|
}
|
|
|
|
# Quoted array separator for extended completions
|
|
$" = " - ";
|
|
|
|
while (<>) {
|
|
if (/^\s*complete\s/) {
|
|
undef(@stuff);
|
|
$default = '';
|
|
$_ = $';
|
|
while (/\\$/) {
|
|
# Remove backslashed newlines: in principle these should become
|
|
# real newlines inside quotes, but what the hell.
|
|
($_) = /^(.*)\\$/;
|
|
$_ .= <>;
|
|
}
|
|
$command = &getword;
|
|
if ($command =~ /^-/ || $command =~ /[*?]/) {
|
|
# E.g. complete -co* ...
|
|
$defmatch = $command;
|
|
($defmatch =~ /^-/) && ($defmatch = substr($defmatch,1));
|
|
} else {
|
|
undef($defmatch);
|
|
}
|
|
while (defined($word = &getword)) {
|
|
# Loop over remaining arguments to "complete".
|
|
$sep = substr($word,1,1);
|
|
$sep =~ s/(\W)/\\$1/g;
|
|
@split = split(/$sep/,$word,4);
|
|
for ($i = 0; $i < 3; $i++) {
|
|
while ($split[$i] =~ /\\$/) {
|
|
substr($split[$i],-1,1) = "";
|
|
splice(@split,$i,2,"$split[$i]\\$sep$split[$i+1]");
|
|
}
|
|
}
|
|
($pat,$arg,$type,$suffix) = @split;
|
|
defined($suffix) && ($suffix =~ /^\s*$/) && undef($suffix);
|
|
if (($word =~ /^n$sep\*$sep/) &&
|
|
(!defined($defmatch))) {
|
|
# The "complete" catch-all: treat this as compctl\'s
|
|
# default (requiring no pattern matching).
|
|
$default .= &gettype($type) . ' ';
|
|
defined($suffix) &&
|
|
(defined($defsuf) ? ($defsuf .= $suffix)
|
|
: ($defsuf = $suffix));
|
|
} else {
|
|
$pat = &getpat($pat,$arg);
|
|
$type = &gettype($type);
|
|
if (defined($defmatch)) {
|
|
# The command is a pattern: use either -C or -D option.
|
|
if ($pat eq 'p[0]') {
|
|
# Command word (-C): 'p[0]' is redundant.
|
|
if ($defmatch eq '*') {
|
|
$defcommand = $type;
|
|
} else {
|
|
($defmatch =~ /\*$/) && chop($defmatch);
|
|
if ($opt_x) {
|
|
$c = ($defmatch =~ /[*?]/) ? 'C' : 'c';
|
|
$pat = $c . "[0,${defmatch}]";
|
|
} else {
|
|
$pat = ($defmatch =~ /[*?]/) ?
|
|
"C[0,${defmatch}]" : "S[${defmatch}]";
|
|
}
|
|
push(@commandword,defined($suffix) ?
|
|
"'$pat' $type -S '$suffix'" : "'$pat' $type");
|
|
}
|
|
} elsif ($pat eq "C[-1,*]") {
|
|
# Not command word completion, but match
|
|
# command word (only)
|
|
if ($defmatch eq "*") {
|
|
# any word of any command
|
|
$defaultdefault .= " $type";
|
|
} else {
|
|
$pat = "W[0,$defmatch]";
|
|
push(@defaultword,defined($suffix) ?
|
|
"'$pat' $type -S '$suffix'" : "'$pat' $type");
|
|
}
|
|
} else {
|
|
# Not command word completion, but still command
|
|
# word with pattern
|
|
($defmatch eq '*') || ($pat = "W[0,$defmatch] $pat");
|
|
push(@defaultword,defined($suffix) ?
|
|
"'$pat' $type -S '$suffix'" : "'$pat' $type");
|
|
}
|
|
} else {
|
|
# Ordinary command
|
|
push(@stuff,defined($suffix) ?
|
|
"'$pat' $type -S '$suffix'" : "'$pat' $type");
|
|
}
|
|
}
|
|
}
|
|
if (!defined($defmatch)) {
|
|
# Ordinary commands with no pattern
|
|
print("compctl $default");
|
|
defined($defsuf) && print("-S '$defsuf' ") && undef($defsuf);
|
|
defined(@stuff) && print("-x @stuff -- ");
|
|
print("$command\n");
|
|
}
|
|
if (defined($genfunc)) {
|
|
print $genfunc;
|
|
undef($genfunc);
|
|
}
|
|
}
|
|
}
|
|
|
|
(defined(@commandword) || defined($defcommand)) &&
|
|
print("compctl -C ",
|
|
defined($defcommand) ? $defcommand : '-c',
|
|
defined(@commandword) ? " -x @commandword\n" : "\n");
|
|
|
|
if (defined($defaultdefault) || defined(@defaultword)) {
|
|
defined($defaultdefault) || ($defaultdefault = "-f");
|
|
print "compctl -D $defaultdefault";
|
|
defined(@defaultword) && print(" -x @defaultword");
|
|
print "\n";
|
|
}
|
|
|
|
__END__
|