# tabkey.ce - Tab key completion script for EPIC5
# Copyright (c) 2002-2013 EPIC Software Labs
# See the 'COPYRIGHT' file for more information.
#
# Version: 1.3.1
#
# This script is based on the tabkey.ce script written by CrazyEddy and
# distributed with the EPIC4 IRC client. The original design and all
# of the really important code are his so he deserves most of the credit.
# If not for his genius none of this would be possible. :-)
#
# This version is being extended and maintained by Brian Weiss.
# Please send all bug reports, questions, and comments to brian@epicsol.org.
#
# This script uses serial number 420 for all /ON hooks.
#
# Original header follows, with modifications where necessary.
#
#  -bmw
#

#
# OK, here's the plan:
#
# tabkey.main 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 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
#   and shouldn't rely on the exported variables being available, as user
#   supplied functions may forget to re-export them.  Context sensitive
#   functions should be as simple as possible.
#
# NOTE:  The "first argument" description above is no longer true.  The
#        functions argument list is to be treated as a single argument.
#

# Make sure this file gets loaded with the PF loader
if (word(2 $loadinfo()) != [pf]) {
	load -pf $word(1 $loadinfo());
	return;
};

#
# /SET vars
#
@ symbolctl(create TABKEY_COMPLETE_TOLOWER);
@ symbolctl(set TABKEY_COMPLETE_TOLOWER 1 builtin_variable type bool);
^set TABKEY_COMPLETE_TOLOWER OFF;

@ symbolctl(create TABKEY_MAX_MSG_HISTORY);
@ symbolctl(set TABKEY_MAX_MSG_HISTORY 1 builtin_variable type int);
^set TABKEY_MAX_MSG_HISTORY 6;

@ symbolctl(create TABKEY_MSGHIST_SENT_ONLY);
@ symbolctl(set TABKEY_MSGHIST_SENT_ONLY 1 builtin_variable type bool);
^set TABKEY_MSGHIST_SENT_ONLY OFF;

@ symbolctl(create TABKEY_NICKCOMP_SUFFIX);
@ symbolctl(set TABKEY_NICKCOMP_SUFFIX 1 builtin_variable type str);
^set TABKEY_NICKCOMP_SUFFIX ,;

#
# The following functions are to be used with the /BIND command
# to cause a particular key sequence to perform completion on the
# current input line.
#
@ bindctl(function TABKEY_COMPLETION create "tabkey.main");
@ bindctl(function TABKEY_COMPLETION_FORWARD create "tabkey.main 1");
@ bindctl(function TABKEY_COMPLETION_BACKWARD create "tabkey.main -1");

#
# This binds the Tab key to the functions that call tabkey.main.
# By default this will enable forward match cycling.
#
bind ^I TABKEY_COMPLETION_FORWARD;
bind ^[^I TABKEY_COMPLETION_BACKWARD;

#
# tabkey.main [-1|0|1]
#
# This is the main alias that will be called by the bind functions
# created with $bindctl() above. Match cycling can be enabled by
# passing either '1' or '-1' as the first argument ('1' for forward
# cycling, '-1' for backward cycling). If no argument (or '0') is
# specified then matches will not be cycled.
#
alias tabkey.main (cycle, void)
{
	@ :xd_extractw = 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);

	#
	# Attempt to cycle through the list of matches if tabkey.main is
	# run a second time without any change in the input line. -bmw
	#
	if (cycle && #TABKEY.LAST_MATCHES && L == TABKEY.LAST_LINE)
	{
		# Adjust the index if we change directions.
		if (TABKEY.LAST_CYCLE_DIRECTION && cycle != TABKEY.LAST_CYCLE_DIRECTION)
			repeat 2 @ TABKEY.INDEX += cycle;

		if (cycle > 0 && (TABKEY.INDEX == #TABKEY.LAST_MATCHES || TABKEY.INDEX < 0)) {
			@ TABKEY.INDEX = 0;
		} else if (cycle < 0 && (TABKEY.INDEX == [] || TABKEY.INDEX < 0)) {
			@ TABKEY.INDEX = #TABKEY.LAST_MATCHES - 1;
		};

		@ :nextword = word($TABKEY.INDEX $TABKEY.LAST_MATCHES);
		parsekey delete_to_previous_space;
		xtype -l $nextword;

		@ TABKEY.INDEX += cycle;
		@ TABKEY.LAST_LINE = L;
		@ TABKEY.LAST_CYCLE_DIRECTION = cycle;

		xdebug $xd_extractw;
		return;
	};

	if (word =~ ["*"]) {
		@ :word = shift(word);
	} elsif (word =~ ["*]) {
		@ :word #= ["];
		@ :word = shift(word);
	};

	if (!index($cmdchars $L)) {
		wait for @ :matches = tabkey.cmd($word);
	} elsif (L == []) {
		wait for @ :matches = tabkey.cmd.msg($*);
	} else {
		wait for @ :matches = tabkey.default($word);
	};

	@ :prefix = prefix($matches);

	if (#matches == 1 && matches != [ ])
	{
		@ :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${[ ]};
	}
	else if (#matches > 1)
	{
		if (word == prefix)
		{
			echo Completions for \"$word\": $matches;
			@ TABKEY.INDEX = -1;
			@ TABKEY.LAST_LINE = L;
			@ TABKEY.LAST_MATCHES = matches;
			@ TABKEY.LAST_CYCLE_DIRECTION = [];
		}
		else if (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;
		};
	};

	xdebug $xd_extractw;
};

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);
	# 
	# Make absolutely sure we remove $CMDCHARS from the beginning of the
	# the string. This is needed whenever $CMDCHARS contains one of the
	# characters in the call to $pass(). -bmw
	#
	if (left($strlen($CMDCHARS) $args) == CMDCHARS) {
		@ :args = rest($strlen($CMDCHARS) $args);
	};
	@ :args = unsplit(. $args);
	@ :args = pass(._$pass $args);

	if (!curword)
	{
		@ :ret = tabkey.method.command($args);
		repeat $#ret {push ret $K$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;
};

alias tabkey.default (...)
{
	bless;
	if (curword == 0) {
		@ :matches = tabkey.method.nick($*);
		if (#matches == 1) {
			@ function_return = matches ## TABKEY_NICKCOMP_SUFFIX;
		} else {
			@ function_return = matches ? matches : tabkey.method.nickchannotify($*);
		};
	} else {
		@ function_return = tabkey.method.nickchannotify($*);
	};
};

alias tabkey.msg_handler (serv, nick, void)
{
	if (!nick) {
		echo Error: tabkey.msg_handler: Not enough arguments;
		return;
	};

	if (match($nick $TABKEY.MSGHIST[$encode($serv)])) {
		@ TABKEY.MSGHIST.$encode($serv) = remw($nick $TABKEY.MSGHIST[$encode($serv)]);
	};
	if (numwords($TABKEY.MSGHIST[$encode($serv)]) == TABKEY_MAX_MSG_HISTORY) {
		shift TABKEY.MSGHIST.$encode($serv);
	};
	push TABKEY.MSGHIST.$encode($serv) $nick;
	@ TABKEY.MSGHIST_INDEX.$encode($serv) = -1;
};

#
# This prevents tabkey.main from falling back to tabkey.default if
# the completion routine returns nothing. See aliases below
# for examples. -bmw
#
alias tabkey.nofallback (...)
{
	@ function_return = [$*] == [] ? [ ] : [$*];
};

#
# This was contributed by zlonix
#
alias tabkey.normalize (input)
{
	if (TABKEY_COMPLETE_TOLOWER == [ON]) {
		return $tolower($input);
	} else {
		return $input;
	};
};

# 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.alias (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.variable(alias)));
};

alias tabkey.cmd.assign (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.variable(assign)));
};

alias tabkey.cmd.channel (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.chan($*));
};

alias tabkey.cmd.dcc (args)
{
	bless;
	if (curword == 1) {
		@ function_return = tabkey.normalize($tabkey.nofallback($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) {echo This should never happen.;call;local}
		(1) {echo This should never happen.;call;local}
		(2) {@ function_return = tabkey.nofallback($tabkey.method.nick($*))}
		(*) {@ function_return = tabkey.nofallback($tabkey.method.file($*))}
	};
};

alias tabkey.cmd.do (void)
{
	tabkey.recurse 1;
};

alias tabkey.cmd.eval (void)
{
	tabkey.recurse 1;
};

alias tabkey.cmd.exec (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.file($*));
};

alias tabkey.cmd.foreach (...)
{
	bless;
	if (curword == 1 && left(1 $word) == [-]) {
		@ :retval = tabkey.method.variable(alias);
	} else if (curword == 1) {
		@ :retval = tabkey.method.variable(assign);
	};
	@ function_return = tabkey.nofallback($retval);
};

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)))));
	@ function_return = tabkey.nofallback($remw(CVS $matches));
};

alias tabkey.cmd.ig (...)
{
	tabkey.cmd.ignore $*;
};

alias tabkey.cmd.ignore (...)
{
	bless;
	if (curword == 1) {
		@ :retval = tabkey.method.mask($*);
	} else if (curword > 1) {
		@ :retval = pattern($0* $levelctl(LEVELS));
	};
	@ function_return = tabkey.nofallback($retval);
};

alias tabkey.cmd.j (...)
{
	tabkey.cmd.join $*;
};

alias tabkey.cmd.join (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.chan($*));
};

alias tabkey.cmd.l (...)
{
	tabkey.cmd.leave $*;
};

alias tabkey.cmd.leave (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.chan($*));
};

alias tabkey.cmd.less (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.file($*));
};

alias tabkey.cmd.load (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.file($*));
};

alias tabkey.cmd.mode (...)
{
	bless;
	if (curword == 1) {
		@ :retval = tabkey.method.chan($*);
	} else {
		@ :retval = tabkey.method.nick($*);
	};
	@ function_return = tabkey.nofallback($retval);
};

alias tabkey.cmd.m (...)
{
	tabkey.cmd.msg $*;
};

alias tabkey.cmd.msg (...)
{
	bless;
	if (curword == 1)
	{
		@ :retval = pattern($word* $sort($uniq($TABKEY.MSGHIST[$encode($winserv())] $tabkey.method.nickchan($*))));
	}{
		@ :cmd = word(0 $L);
		if (!cmd) {
			@ :cmd = [$K\MSG];
		};
		@ :cmd = tabkey.normalize($cmd);
		@ :nicks = TABKEY.MSGHIST[$encode($winserv())];
		@ :index = TABKEY.MSGHIST_INDEX[$encode($winserv())];
		@ :trailing = restw(2 $L);
		if (nicks)
		{
			if (index > 0)
			{
				@ :nick = word(${index - 1} $nicks);
				@ TABKEY.MSGHIST_INDEX.$encode($winserv()) = index - 1;
			}{
				@ :nick = word(${numwords($nicks) - 1} $nicks);
				@ TABKEY.MSGHIST_INDEX.$encode($winserv()) = numwords($nicks) - 1;
			};
			parsekey erase_line;
			xtype -l $cmd $nick $trailing;
			@ :retval = [ ];
		}{
			@ TABKEY.MSGHIST_INDEX.$encode($winserv()) = -1;
		};
	};

	@ function_return = retval;
};

alias tabkey.cmd.part (...)
{
	tabkey.cmd.leave $*;
};

alias tabkey.cmd.pop (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.variable(assign));
};

alias tabkey.cmd.push (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.variable(assign));
};

alias tabkey.cmd.q (...)
{
	tabkey.cmd.query $*;
};

alias tabkey.cmd.query (...)
{
	@ function_return = pattern($0* $sort($uniq($TABKEY.MSGHIST[$encode($winserv())] $tabkey.method.nickchan($*))));
};

alias tabkey.cmd.repeat (void)
{
	tabkey.recurse 2;
};

alias tabkey.cmd.set (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.variable(set));
};

alias tabkey.cmd.shift (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.variable(assign));
};

alias tabkey.cmd.unshift (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.variable(assign));
};

alias tabkey.cmd.whois (...)
{
	@ function_return = tabkey.nofallback($tabkey.method.nick($*));
};

alias tabkey.cmd.window (...)
{
	bless;
	@ :windowcmds = [ADD BACK BALANCE BEEP_ALWAYS CHANNEL CHECK CLAIM CLEAR CLEARLEVEL CLEARREGEX CREATE DELETE DELETE_KILL DESCRIBE DISCON DOUBLE ECHO FIXED FLUSH FLUSH_SCROLLBACK GOTO GROW HIDE HIDE_OTHERS HOLD_INTERVAL HOLD_MODE HOLD_SLIDER INDENT KILL KILL_ALL_HIDDEN KILL_OTHERS KILLABLE KILLSWAP LAST LASTLOG LASTLOG_LEVEL LEVEL LIST LOG LOGFILE LOG_MANGLE LOG_REWRITE MERGE MOVE MOVE_TO NAME NEW NEW_HIDE NEXT NOSERV NOTIFIED NOTIFY NOTIFY_LEVEL NOTIFY_NAME NUMBER POP PREVIOUS PROMPT PUSH QUERY REBUILD_SCROLLBACK REFNUM REFNUM_OR_SWAP REFRESH REMOVE REJOIN REMOVE SCRATCH SCROLL SCROLLADJ SCROLLBACK SCROLL_BACKWARD SCROLL_END SCROLL_FORWARD SCROLL_LINES SCROLL_START SEARCH_BACK SEARCH_FORWARD SERVER SHOW SHOW_ALL SHRINK SIZE SKIP STACK STATUS_FORMAT STATUS_FORMAT1 STATUS_FORMAT2 STATUS_PREFIX_WHEN_CURRENT STATUS_PREFIX_WHEN_NOT_CURRENT STATUS_SPECIAL SWAP SWAPPABLE TOPLINE TOPLINES UNCLEAR];
	if (curword == 1) {
		@ :cmd = [WINDOW];
	} else {
		@ :cmd = word(${curword - 1} $L);
	};

	switch ($cmd)
	{
		(ADD) (QUERY) (REMOVE) (SEARCH_BACK) (SEARCH_FORWARD) {
			@ :list = tabkey.method.nickchan($*);
		}
		(BEEP_ALWAYS) (DOUBLE) (FIXED) (HOLD_MODE) (LOG) (SCRATCH) (SCROLL) (SKIP) {
			@ :list = [ON OFF TOGGLE];
		}
		(BIND) (CHANNEL) {
			@ :list = tabkey.method.chan($*);
		}
		(ECHO) (GOTO) (GROW) (HOLD_INTERVAL) (LASTLOG) (MOVE) (NAME) (NUMBER) (PROMPT) (REFNUM) (REFNUM_OR_SWAP) (SCROLLBACK) (SERVER) (SHRINK) (SIZE) (STATUS_%) (SWAP) {
			@ :nocomp = 1;
		}
		(LASTLOG_LEVEL) (LEVEL) (NOTIFY_LEVEL)
		{
			if (match(%,% $word)) {
				@ :word_a = before(-1 , $word);
				@ :word_b = after(-1 , $word);
			};
			@ :list = levelctl(LEVELS);
		}
		(LOGFILE) {
			@ :list = tabkey.method.file($*);
		}
		(*) {
			@ :list = windowcmds;
		}
	};

	if (word_a)
	{
		@ :matches = pattern($word_b* $list);
		if (#matches == 1)
		{
			parsekey delete_to_previous_space;
			xtype -l $word_a\,$matches${[ ]};
		}
		else if (#matches > 1)
		{
			parsekey delete_to_previous_space;
			xtype -l $word_a\,$prefix($matches);
			echo Completions for \"$word_b\": $matches;
		};
		@ :retval = [ ];
	}
	else if (nocomp)
	{
		@ :retval = [ ];
	}
	else
	{
		@ :retval = pattern($word* $list);
	};

	@ function_return = tabkey.normalize($tabkey.nofallback($retval));
};


# Methods.

alias tabkey.method.array (array, args)
{
	for ii from 0 to ${numitems($array) - 1} {
		@ push(:list $getitem($array $ii));
	};
	@ :list = sort($uniq($list));
	@ function_return = args ? pattern($args* $list) : list;
};

alias tabkey.method.command (...)
{
	@ function_return = tabkey.normalize($sort($uniq($getcommands($**) $aliasctl(alias match $*) $aliasctl(alias match $**))));
};

alias tabkey.method.chan (...)
{
	@ function_return = (:foo = pattern($sar(g/\\/\\\\/$*)* $mychannels())) ? foo :
	                    (:foo = pattern(#$sar(g/\\/\\\\/$*)* $mychannels())) ? foo :
	                    pattern(&$sar(g/\\/\\\\/$*)* $mychannels());
};

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*);
	};
	@ function_return = f0 ? f0 : f1;
};
stack pop alias alias.foo;

alias tabkey.method.mask (...)
{
	@ :uhost = userhost($0);
	if (uhost == [<UNKNOWN>@<UNKNOWN>] && aliasctl(alias exists uh)) {
		@ :uhost = uh($0);
	};
	@ function_return = uhost == [<UNKNOWN>@<UNKNOWN>] ? [] : mask(3 $uhost);
};

alias tabkey.method.nick (...)
{
	@ :args = sar(g/\\/\\\\/$*);
	if (match($args* $chanusers())) {
		@ function_return = pattern($args* $chanusers());
	} else {
		@ :chanusers = mychannels();
		repeat $#chanusers @ push(chanusers $chanusers($shift(chanusers)));
		@ function_return = pattern($args* $sort($uniq($chanusers)));
	};
};

alias tabkey.method.nickchan (...)
{
	@ :chans = pattern($sar(g/\\/\\\\/$*)* $mychannels());
	@ function_return = remws(/ $chans $tabkey.method.nick($*));
};

alias tabkey.method.nickchannotify (...)
{
	@ :chans = pattern($sar(g/\\/\\\\/$*)* $mychannels());
	@ function_return = remws(/ $chans $sort($uniq($tabkey.method.nick($*) $tabkey.method.notify($*))));
};

alias tabkey.method.notify (...)
{
	@ function_return = pattern($sar(g/\\/\\\\/$*)* $notify(on));
};

alias tabkey.method.variable (type, void)
{
	bless;

	if (!type)
		return;

	if (curword == 1)
	{
		if (left(1 $word) == [-]) {
			@ :rm = 1;
			@ :word2 = rest(1 $word);
		} else {
			@ :word2 = word;
		};

		switch ($type)
		{
			(ALIAS) (ASSIGN) {@ :matches = aliasctl($type match $word2);}
			(SET)            {@ :matches = getsets($word2*);}
		};

		if (rm) {
			for ii in ($matches) {
				@ push(:retval -$ii);
			};
		} else {
			@ :retval = matches;
		};
	}
	else if (curword == 2 && word == [] && (left(1 $word(${curword - 1} $L)) != [-]))
	{
		eval xtype -l \$$word(${curword - 1} $L);
	};

	@ function_return = tabkey.normalize($retval);
};


#
# Add the nicks of users that you're chatting with to the msg history.
#
on #^msg 420 "*" {
	if (TABKEY_MSGHIST_SENT_ONLY == [OFF]) {
		tabkey.msg_handler $servernum() $0;
	};
};

on #^send_msg 420 "*" {
	tabkey.msg_handler $servernum() $0;
};

on #^dcc_chat 420 "*" {
	if (TABKEY_MSGHIST_SENT_ONLY == [OFF]) {
		for serv in (-1 $myservers(0)) {
			tabkey.msg_handler $serv =$0;
		};
	};
};

on #^send_dcc_chat 420 "*" {
	for serv in (-1 $myservers(0)) {
		tabkey.msg_handler $serv =$0;
	};
};

on #^dcc_request 420 "% CHAT *" {
	if (TABKEY_MSGHIST_SENT_ONLY == [OFF]) {
		for serv in (-1 $myservers(0)) {
			tabkey.msg_handler $serv =$0;
		};
	};
};

on #^send_ctcp 420 "PRIVMSG % DCC CHAT *" {
	for serv in (-1 $myservers(0)) {
		tabkey.msg_handler $serv =$1;
	};
};

