1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
|
#
# OK, here's the plan:
#
# Tabkey cuts the input line up into edible chunks, and selects a function
# to do the actual completion. The function will receive a single word as
# its argument, and return a list of potential matches. The word is whatever
# is under the cursor, up to the insertion point at the current time. The
# word may be empty or contain spaces and the function should be able to
# cope with this.
#
# The exact function chosen works like this:
# * If the current _word_ is a command (first word, begins with $cmdchars),
# call command_completion by way of parsekey (don't call any aliases).
# * Or, if the current line is a command, it gets handed to $tabkey.cmd().
# * Otherwise, call tabkey.default, which is a stub that can be changed
# to suit your preferences. The default tabkey.default calls
# tabkey.nickchan which matches against all channels you're currently on
# and either the nicks in your current channel, or failing that, the
# nicks in all channels.
# * tabkey.default is also called if tabkey.cmd() returns nothing. If
# necessary, the function can prevent this behaviour by returning a single
# space, but this is discouraged.
# * tabkey.default is also called by $tabkey.cmd() if it cannot find an
# appropriate command handler.
# * $tabkey.cmd() will search for an appropriate function by joining the
# command and all its arguments together with dots and progressively
# removing those arguments until a function is found.
#
# tabkey "exports" a number of local variables that the functions may use
# or alter after using "bless":
# * $curword is what tabkey believes the current word is. The function
# may make use of this for context sensitivity. I say believes because
# this may be open to different interpretations.
# * $wordind is the character index of the _beginning_ of the word that is
# being completed. If it is equal to $curpos(), then the first argument
# will be empty and the user has hit tab at the beginning of the word or
# in between words and the function should return all possible matches.
# The reason you may need to use it is if the function is designed to
# complete something that is not a single word. It, and the current
# cursor position may be altered to cause tabkey to replace the chosen
# part of the string instead of just the word.
#
# The organisation of the supporting functions goes like this:
# * "Context sensitive" functions are to be named with "tabkey.cmd." as a
# prefix followed by the name of the command that they are used to
# complete. This is where tabkey.cmd will look for them.
# * Generally, completion "methods" should be placed under tabkey.method
# Context sensitive functions should be as simple as possible and the bulk
# of the work should be done by the methods.
#
# NOTE: The "first argument" description above is no longer true. The
# functions argument list is to be treated as a single argument.
#
# Remaining known issues:
# Doesn't work (very well) with commands with dots in them.
package tabkey.ce
bind ^i parse_command tabkey.main
alias tabkey.default tabkey.method.nickchan
alias tabkey.main {
@ :oxd = xdebug(extractw)
xdebug extractw
@ :curpos = curpos()
@ :curword = indextoword($curpos $L )
@ :wordind = wordtoindex($curword $L)
@ :cmdind = 0
if (32 >= (127 & ascii($mid(${curpos-1} 1 $L )))) {
@ curword++
@ wordind = curpos
}
@ :word = mid($wordind ${curpos-wordind} $L)
if (word=~["*"]) {
@ :word = shift(word)
} elsif (word=~["*]) {
@ :word#=["]
@ :word = shift(word)
}
if (!index($cmdchars $L)) {
wait for @ :matches = tabkey.cmd($word)
} else {
wait for @ :matches = tabkey.default($word)
}
@ :prefix = prefix($matches)
if (1 < numwords($matches) && word == prefix) {
echo Completions for \"$word\": $matches
} elsif (#matches && strlen($word) <= strlen($prefix)) {
@ :prefix = 0 > index("$chr($jot(32 1))" $prefix) ? prefix : ["$prefix"]
@ :line = mid(0 $wordind $L)
parsekey erase_to_beg_of_line
xtype -l $line$prefix${1<#matches?[]:[ ]}
}
xdebug $oxd
}
alias tabkey.cmd {
bless
@ :ret = :cmd = []
@ :pass = chr($jot($ascii(AZ)))
@ :pass#= chr($jot($ascii(az)))
@ :pass#= chr($jot($ascii(09)))
@ :args = wordtoindex($cmdind $L)
@ :args = mid($args $curpos $L)
@ :args = unsplit(. $args)
@ :args = pass(._$pass $args)
if (!curword) {
@ ret = tabkey.method.commands($args)
repeat $#ret {push ret /$shift(ret)}
return $ret
}
while (args) {
@ :matches = aliasctl(alias pmatch tabkey.cmd.$args*)
@ :matches = prefix($matches)
if (curword == count(. $args)) {
} elsif (matches != [tabkey.cmd.$args]) {
} elsif (aliasctl(alias exists $matches)) {
@ args = matches
break
}
@ args = before(-1 . $args)
}
if (args) {
wait for @ ret = ${args}($*)
}
unless (strlen($ret)) {
wait for @ ret = tabkey.default($*)
}
return $ret
}
# Used for things like /eval and /repeat where what you are completing
# is another command. depth is the arg number where the command begins.
# This is not well cpu optimised. See examples below.
alias tabkey.recurse (depth,...) {
bless
@ :curpos += wordtoindex($cmdind $L )
@ :curpos -= wordtoindex(${cmdind+depth} $L )
@ :cmdind += depth
@ :curword -= depth
@ function_return = 0 > curword ? [] : tabkey.cmd($*)
@ :curword += depth
@ :cmdind -= depth
@ :curpos += wordtoindex(${cmdind+depth} $L )
@ :curpos -= wordtoindex($cmdind $L )
}
# Context sensitive completion goes here.
alias tabkey.cmd.eval tabkey.recurse 1
alias tabkey.cmd.repeat tabkey.recurse 2
alias tabkey.cmd.dcc (args) {
bless
switch ($curword) {
(1) {return $pattern("$args*" CHAT CLOSE CLOSEALL GET LIST RAW RENAME RESUME SEND)}
}
}
alias tabkey.cmd.dcc.resume tabkey.cmd.dcc.send
alias tabkey.cmd.dcc.send {
bless
switch ($curword) {
(0) (1) {echo This should never happen.;call;local}
(2) {return $tabkey.method.nick($*)}
(*) {return $tabkey.method.filei($*)}
}
}
alias tabkey.cmd.help {
bless
@ :matches = []
@ :path = restw(1 $left($curpos $L))
if (wordind == curpos) {@ push(path *)}
repeat $#path @ push(path $shift(path)*)
@ matches = globi($unsplit(/ $getset(HELP_PATH) $path))
repeat $#matches @ push(matches $rightw(1 $remws(/ $split(/ $shift(matches)))))
return $matches
}
# Methods.
alias tabkey.method.commands {
@ :ret = sort($uniq($getcommands($**) $aliasctl(alias match $*) $aliasctl(alias match $**)))
return $ret
}
alias tabkey.method.chan (args) { return $pattern("$args*" $mychannels()); }
alias tabkey.method.notify (args) { return $pattern("$args*" $notify(on)); }
stack push alias alias.foo
alias alias.foo alias $*;alias $sar(g/globi/glob/$sar(g/filei/file/$*))
alias.foo tabkey.method.filei (f0) {
@ :f0 = sar(gr/\\/\\\\/f0)
@ :f0 = sar(gr/*/\\*/f0)
@ :f0 = sar(gr/?/\\?/f0)
@ :f0 = sar(gr/[/\\[/f0)
@ :f0 = sar(gr/]/\\]/f0)
@ :f0 = :f1 = globi("" $f0*)
while (numwords($f0) == 1 && f0 =~ [*/]) {
@ f0 = globi("" $f0*)
}
return ${f0 ? f0 : f1}
}
stack pop alias alias.foo
alias tabkey.method.nick (args) {
if (match("$args*" $chanusers())) {
return $pattern("$args*" $chanusers());
} else {
@ :chanusers = mychannels()
repeat $#chanusers @ push(chanusers $chanusers($shift(chanusers)))
return $pattern("$args*" $sort($uniq($chanusers)));
}
}
alias tabkey.method.nickchan {
return $remws(/ $tabkey.method.chan($*) $tabkey.method.nick($*))
}
alias tabkey.method.nickchannotify {
return $remws(/ $tabkey.method.chan($*) $sort($uniq($tabkey.method.nick($*) $tabkey.method.notify($*))))
}
|