# Complete Modules by Ian Frechette # Last updated 2-2-94 # example modules # Don't let the size scare you. This is a whole collection of # examples and comments about the design of modules # meat of all this are the interface functions to 'complete' # compl.add, and compl.list (soon to be compl.del) # the function that does the work is compl.parse which is very small # and whatever parsing routine it calls. # These should actually all be in their individual modules # but this is just an example file.. # Note.. compl.list is internal to complete.. shoulnd't normally be used if (compl.list == []) { # Note here.. Currently, if you compl.add /blah blahparse # followed by compl.add /blah otherparse only the latter will # called. No conflict message is shown unless you replace the # default 'null' and 'nomatch' parsers. compl.add -null -nomatch /m messparse compl.add /msg messparse compl.add /connect connparse compl.add /tr connparse compl.add /load loadparse compl.add /test testparse compl.add /con expand.con compl.add /conbunch expand.con } # message parser module.. Compatible with included tabscript # currently calls the tabkey script under 3 conditions # the input line has more than 2 argments on it.. '/msg bob thud' # the input line has nothing on it '' # the input line has only one argument and the character before the # cursor is a space '/msg bob ' # plus it now does nickname completion # /m D expands to /m Daemon and so on alias messparse { if (([$1] != []) && ([$2] == [])) { if (right(1 $*) != [ ]) { # this is the simple match.. just match first occurance # and expand if (mp.cnt = match($(1)* $tk.msglist)) { parsekey delete_previous_word # in case one does /m =ni it must delete the = if (index(#=% $1) >= 0) {parsekey backspace} type $word(${mp.cnt - 1} $tk.msglist) type ${[ ]} } } { ^tk.getmsg 1 $tk.msglist } } { ^tk.getmsg 1 $tk.msglist } } # connect module for opers.. easily changeable to kicks, bans.. etc.. # simply use /connect for list or /connect # eg. /connect pen expands to /connect penfold.ece.uiuc.edu # It always expands first matching string. Look at testparse for # a more intelligent way to do it. @ connlist = [irc.uiuc.edu goren1.u.washington.edu ircserver.iastate.edu w6yx.stanford.edu hamblin.math.byu.edu freedom.nmsu.edu dreamtime.unm.edu ircserver.santafe.edu irc.netsys.com irc-2.mit.edu cs-mail.bu.edu] alias connparse { if ([$1] != []) { @ cp.line = [$*] if ((right(1 $*) == [ ]) && ([$2] == [])&& ([$0] == [/connect])) { type 6667;type ${[ ]} # note.. if converted to use smartparse.. the port number must # be removed here.. the above logic conflicts with SP } { # expand only the first match found (See 'testparse' for better way if (cp.cnt = match($(${#cp.line -1})* $connlist)) { delword type $word(${cp.cnt - 1} $connlist) type ${[ ]} } { echo *** connlist $connlist } } } { echo *** connlist $connlist } } # Load module # /load net expand to /load netsplit and so on. # Note the problem right now is that it only finds and expands the first # name in the list I think we can get around this. @ loadlist = [netsplit ircgrep cut-paste compl.mods] alias loadparse { if ([$1] != []) { if (lp.cnt = match($(1)* $loadlist)) { parsekey delete_previous_word type $word(${lp.cnt - 1} $loadlist) } } { echo *** loadlist = $loadlist } ^assign -lp.cnt } # ############ stuff related to SMARTPARSE ################### # The new testparse rewritten to use the # extremely awsome smartparse routine. if (!match(/test $compl.list)) {compl.add /test testparse} # just a quick alias for making LINKS lists.. /makelist testlist *.edu # will make a list of all *.edu servers.. Note that just * is # generally too big a list for ircII alias makelist { if ([$1]) { ^on ^364 * push $0 $$1 links $1- wait -cmd eval ^on 364 -\;echo *** makelist finished } } @ testlist = [aone atwo athree bone btwo bthree ircserver.iastate.edu ircserver.santafe.edu] # testparse # this is called by the complete routines. @ tp.tmp = [0 :] # [0 :] represents a count of 0.. and a null pattern : alias testparse { # ignore this first line.. @ tp.cnt = [$*] @ tp.cnt = #tp.cnt ^assign -tp.pat # all the cnt stuff is in case you do /command word1 word2 pattern if (tp.cnt > 1) { @ tp.pat = [$(${tp.cnt - 1})] # '/command pattern ' leaves null # '/command pattern' sets to a new pattern # important because smartparse may leave a space if (right(1 $L) != [ ]) {@ tp.tmp = [0 :$tp.pat]} # Uncomment the following line to see how it works from here.. debugging # echo smartparse\($tp.tmp testlist $tp.pat\) # call testparse with current cnt : @ tp.tmp = smartparse($tp.tmp testlist $tp.pat) # note tp.tmp accounts for two arguments.. and is modified and saved if (left(1 $word(1 $tp.tmp)) == [,]) {echo *** no match for pattern [$tp.pat] found in list} {if (left(1 $word(1 $tp.tmp)) == [.]) {echo *** testlist: $testlist}} } { echo *** testlist : $testlist } } alias test echo *** TEST: You've matched: $* # test module # Trying to make some sort of intelligent handling of the tab lists. @ sp.cnt = 0 # call it with smartparse : # $0 $1 $2 $3 # returns [:,.] # : == successful match , == no match . == null # # Look at how testparse uses it.. you shouldn't have to touch any # smartparse vars.. It's all handled through the interface.. basically # you're telling it where to start looking in the list and how # long the list is.. Each time smartparse is called it returns a counter # value indicating where it left off last time. You can save it # or not.. testparse saves it.. and passes it back as the new # starting position # # Assuming the counter, pattern, and list are maintained through each call # it'll assume you're searching forward in the list from some place # after the last word matched in the list.. # # If you feed it a for which is not a subset, it'll reassign # to and restart the process.. # It defaults to expansion.. so.. = blah # will match blahone, blahtwo etc. Works with wildcards.. *a* matches a lot # Try it. ^assign -sp.tmp alias smartparse { # int sp.tmp - index of last match found # int sp.cnt - position in list # int sp.max - max number of elements in list # string sp.pat - match pattern if ([$3] != []) { # Extract from : Note.. It may be null @ sp.pat = mid(1 50 $1) @ sp.max = [$(#$2)] @ sp.cnt = [$0] # set pattern. Determine if we've changed the base pattern or not if (sp.pat == []) {@ sp.pat = [$3]} { if (!match($(sp.pat)* $3)) {@sp.pat = [$3]} } @ sp.run = 1 while (((sp.list = words($sp.cnt $sp.max $($2))) != []) && (!sp.tmp) && sp.run) { # look for match in list if (sp.tmp = match($(sp.pat)* $sp.list)) { # sp.cnt is absolute position in list. Jump over found item. # to set up for the next call to smartparse @ sp.cnt = sp.cnt + sp.tmp # parsekey delete_previous_word delword type $word(${sp.tmp - 1} $sp.list) type ${[ ]} } { # nothing found.. drop out of loop # for this condidtion to occur we must be at the beginning # of the loop... either first pass.. or just looped back if (!sp.cnt && !sp.tmp) { # notfound condition set for return value later @ sp.notfound = [$sp.cnt ,$sp.pat] @ sp.run = 0 # echo *** smartparse: no matching pattern } # loop back @ sp.cnt = 0 } } ^assign -sp.tmp if (!sp.list) {@sp.cnt = 0} ^assign -sp.list if (sp.notfound == []) {@ function_return = [$sp.cnt :$sp.pat]} {@ function_return = sp.notfound;^assign -sp.notfound} } { # echo *** sp NULL # echo *** $2: $($2) @ function_return = [$sp.cnt .$sp.pat] } ^assign -sp.run } # alias words.. usage $word( ) # words(0 2 zero one two three ... ) == 'zero one two' and so on alias words { @ function_return = [$(${[$0]+2}-${[$1] +2})] } # This is like DELETE_PREVIOUS_WORD except that it delets to the # previous space which is much more useful than the current # behavior of deleting to the previos non- [a-zA-Z0-9] char. :-? alias delword { parsekey erase_to_end_of_line if ((dw.char = right(1 $L)) != [ ]) {@ dw.nw = 0} {@dw.nw = 1} while (((dw.char != [ ]) || dw.nw) && (dw.char != [])) { parsekey backspace if ((dw.char = right(1 $L)) != [ ]) {@ dw.nw = 0} } ^assign -dw.char;^assign -dw.nw } # it is of course possible to do command completion with something like alias expand.con parsekey erase_to_beg_of_line;type /connect $() # /con expands to /connect # Be careful though.. note the $() at the end of the line. # Without this 'complete' sends expand.con /con and the above # alias will add the '/con' back onto the end of the line resulting # in /connect /con on the input line when you're done # # # # # # # # # # MODULE WRITING PHILOSOPHY # # # # # # # # # # # Some thoughts about using complete and designing complete modules. # # Basically for any given time you hit TAB there are three states # normal, null, and nomatch # normal - there is something on the command line to process # and the command is in the command list maintained by 'complete' # The associated command parser is called with the contents of the # input line in it's entirety as the arguments. # null - there is nothing at all on the command line and some # default action must be taken. No parser need be called at all as well. # nomatch - the command at the head of the input line is not # found in the list of commands maintained by 'complete'. # A default 'nomatch' parser may or may not be called but if it is called # it's passed the entire contents of the input line. # # This is not the end of the story however. # If you're writing a completion module of some sort there are the same # 3 states plus 1 more. Let's say you want to write something to # find a match for a given keyword prefix out of a list when you hit # TAB. e.g. /eat ap looks for matching words that start with ap # The 4 actions are # normal - There is a single match for ap and it expands /eat apple # multiple matches - There is more than one match for ap and thus # a choice must be made. Possible choices include # 1. do nothing # 2. list possible matches (like ^D) or set showmatch in tcsh shell # *** matches for prefix 'ap': apple apricot apendage # 3. match only the first occurance Currently what the /connect module # does /eat apple # 4. cycle through the possible matches for the keyword 'ap'. # The 'testparse' modules uses this scheme and it's my favorite # albiet a tad more expensive in terms of CPU cycles and responce # time. (I'm sure someone could see the diff.. I can't ;) # /eat ap -> /eat apple -> /eat apricot etc.. # 5. display worthless error message # *** non-unique matches found # nomatch - as as before, nomatching keywords are found, the choices are # limited to things like displaying the whole list or just cycling through # to the next item in the list like the 'tabkey' script's 'messparse' does. # null - This one is more likely to happen only if 'complete' saw the # input line as null, but then the null action is ussually special anyway. # Otherwise this may occur when you say just /eat and the obvious # thing to do here is just to display the list of items to choose from # in an appropriate format. # Just remember.. the parsing routine can really do anything it wants.. # it could simply 'sendline' the line on to the server and push a # button to start WWIII when you hit tab.. It doesn't have to mess with the # command line but it's more useful that way. Although.. you could write # a tab completion module that when tab was hit.. it spell checked # the line.. anything is possible..