#	$eterna: compl.mods,v 1.2 2001/08/12 15:57:10 mrg Exp $
#
# 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<TAB>'
# the input line has nothing on it '<TAB>'
# the input line has only one argument and the character before the 
#   cursor is a space    '/msg bob <TAB>'
# plus it now does nickname completion
# /m D<TAB>   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<TAB> 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 <TAB> for list or /connect <uniqid tag><TAB>
# eg.  /connect pen<TAB>  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<TAB> 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 <cmd> <stuff....pat>
# this is called by the complete routines.
@ tp.tmp = [0 :]
# [0 :] represents a count of 0.. and a null pattern :<pat> 
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<tab>
	if (tp.cnt > 1)
	{
		@ tp.pat = [$(${tp.cnt - 1})]
		# '/command pattern ' leaves <pat> null
		# '/command pattern'  sets <pat> 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 :<pat> <listname> <newpat>
		@ 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 <cnt> :<pat> <listname> <newpat>
#                          $0    $1       $2        $3
# returns <counter> [:,.]<pattern>
#                    : == 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  <cnt>
#
# 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 <newpat> for which <pat> is not a subset, it'll reassign
# <pat> to <newpat> and restart the process.. 
# It defaults to expansion..  so..  <pat> = 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 <pat> from  :<pat>    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(<begin> <end> <words>)
# 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<tab> 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<TAB>   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<TAB> -> /eat apple<TAB> -> /eat apricot<TAB> 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<TAB> 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.. 
