Scandum 6 жил өмнө
parent
commit
a2da406daf
3 өөрчлөгдсөн 2480 нэмэгдсэн , 0 устгасан
  1. 527 0
      src/draw.c
  2. 983 0
      src/regex.c
  3. 970 0
      src/trigger.c

+ 527 - 0
src/draw.c

@@ -0,0 +1,527 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 of the License, or         *
+*   (at your option) any later version.                                       *
+*                                                                             *
+*   This program is distributed in the hope that it will be useful,           *
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of            *
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
+*   GNU General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                     coded by Igor van den Hoven 2019                        *
+******************************************************************************/
+
+#include "tintin.h"
+
+DO_COMMAND(do_draw)
+{
+	char *nst_arg;
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], arg4[BUFFER_SIZE];
+	int index, flags;
+	int top_row, top_col, bot_row, bot_col, rows, cols;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " DRAW OPTIONS ");
+
+		for (index = 0 ; *draw_table[index].fun ; index++)
+		{
+			if (*draw_table[index].name)
+			{
+				tintin_printf2(ses, "  [%-24s] %s", draw_table[index].name, draw_table[index].desc);
+			}
+		}
+		tintin_header(ses, "");
+	}
+	else
+	{
+		flags = HAS_BIT(ses->flags, SES_FLAG_UTF8) ? DRAW_FLAG_UTF8 : 0;
+
+		nst_arg = get_arg_in_braces(ses, arg1, arg2, GET_ONE);
+
+/*		if (is_abbrev(arg2, "ESCAPED"))
+		{
+			SET_BIT(flags, DRAW_FLAG_ESCAPED);
+		}
+		else */if (is_abbrev(arg2, "PRUNED"))
+		{
+			SET_BIT(flags, DRAW_FLAG_PRUNED);
+		}
+		else if (is_abbrev(arg2, "ROUNDED"))
+		{
+			SET_BIT(flags, DRAW_FLAG_ROUNDED);
+		}
+		else if (is_abbrev(arg2, "CROSSED"))
+		{
+			SET_BIT(flags, DRAW_FLAG_CROSSED);
+		}
+
+		if (HAS_BIT(flags, DRAW_FLAG_PRUNED|DRAW_FLAG_ROUNDED|DRAW_FLAG_CROSSED))
+		{
+			if (*nst_arg)
+			{
+				get_arg_in_braces(ses, nst_arg, arg1, GET_ALL);
+			}
+			else
+			{
+				arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+			}
+		}
+
+		for (index = 0 ; *draw_table[index].name ; index++)
+		{
+			if (is_abbrev(arg1, draw_table[index].name))
+			{
+				arg = sub_arg_in_braces(ses, arg, arg4, GET_ALL, SUB_VAR|SUB_FUN);
+
+				nst_arg = arg4;
+
+				nst_arg = get_arg_in_braces(ses, nst_arg, arg2, GET_ONE);
+				nst_arg = get_arg_in_braces(ses, nst_arg, arg3, GET_ONE);
+
+				top_row = get_row_index(ses, arg2);
+				top_col = get_col_index(ses, arg3);
+
+				nst_arg = get_arg_in_braces(ses, nst_arg, arg2, GET_ONE);
+				nst_arg = get_arg_in_braces(ses, nst_arg, arg3, GET_ONE);
+
+				bot_row = get_row_index(ses, arg2);
+				bot_col = get_col_index(ses, arg3);
+
+				rows = URANGE(1, 1 + bot_row - top_row, gtd->screen->rows);
+				cols = URANGE(1, 1 + bot_col - top_col, gtd->screen->cols);
+
+				if (*arg == 0)
+				{
+					arg = nst_arg;
+				}
+
+				*arg1 = 0;
+				*arg2 = 0;
+
+				save_pos(ses);
+
+				draw_table[index].fun(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, arg, arg1, arg2);
+
+				load_pos(ses);
+
+				return ses;
+			}
+		}
+		show_error(ses, LIST_COMMAND, "#ERROR: #DRAW {%s} IS NOT A VALID OPTION.", capitalize(arg1));
+	}
+
+	return ses;
+}
+
+// utilities
+
+char *draw_corner(int flags, char *str)
+{
+	static char result[10][10];
+	static int cnt;
+
+	cnt = (cnt + 1) % 10;
+
+	if (HAS_BIT(flags, DRAW_FLAG_UTF8))
+	{
+		if (HAS_BIT(flags, DRAW_FLAG_PRUNED))
+		{
+			strcpy(result[cnt], "\e[C");
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_ROUNDED))
+		{
+			strcpy(result[cnt], "○");
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_CROSSED))
+		{
+			strcpy(result[cnt], "┼");
+		}
+		else
+		{
+			strcpy(result[cnt], str);
+		}
+	}
+	else
+	{
+		if (HAS_BIT(flags, DRAW_FLAG_PRUNED))
+		{
+			strcpy(result[cnt], "\e[C");
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_ROUNDED))
+		{
+			strcpy(result[cnt], "o");
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_CROSSED))
+		{
+			strcpy(result[cnt], "+");
+		}
+		else
+		{
+			strcpy(result[cnt], "+");
+		}
+	}
+	return result[cnt];
+}
+
+char *draw_horizontal(int flags, char *str)
+{
+	static char result[10][10];
+	static int cnt;
+
+	cnt = (cnt + 1) % 10;
+
+	if (HAS_BIT(flags, DRAW_FLAG_UTF8))
+	{
+		strcpy(result[cnt], "─");
+	}
+	else
+	{
+		strcpy(result[cnt], "-");
+	}
+	return result[cnt];
+}
+
+char *draw_vertical(int flags, char *str)
+{
+	static char result[10][10];
+	static int cnt;
+
+	cnt = (cnt + 1) % 10;
+
+	if (HAS_BIT(flags, DRAW_FLAG_UTF8))
+	{
+		strcpy(result[cnt], "│");
+	}
+	else
+	{
+		strcpy(result[cnt], "|");
+	}
+	return result[cnt];
+}
+
+// subcommands
+
+DO_DRAW(draw_blank)
+{
+	int row;
+
+	arg = arg1;
+
+	arg1 += sprintf(arg1, "\e[%d;%dH", top_row, top_col);
+
+	for (row = 0 ; row < rows ; row++)
+	{
+		arg1 += sprintf(arg1, "\e[%dX\v", bot_col - top_col + 1);
+	}
+
+	printf("%s", arg);
+}
+
+
+DO_DRAW(draw_bot_line)
+{
+	int col;
+
+	goto_pos(ses, bot_row, top_col);
+
+	arg = arg1;
+
+	arg1 += sprintf(arg1, "%s", draw_corner(flags, "└"));
+
+	for (col = top_col + 1 ; col < bot_col ; col++)
+	{
+		arg1 += sprintf(arg1, "%s", draw_horizontal(flags, "─"));
+	}
+
+	arg1 += sprintf(arg1, "%s", draw_corner(flags, "┘"));
+
+	printf("%s", arg);
+}
+
+DO_DRAW(draw_box)
+{
+	draw_top_line(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, arg, arg1, arg2);
+	draw_bot_line(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, arg, arg1, arg2);
+
+	draw_left_line (ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, arg, arg1, arg2);
+	draw_right_line(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, arg, arg1, arg2);
+}
+
+DO_DRAW(draw_box_text)
+{
+	draw_box (ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, arg, arg1, arg2);
+
+	draw_text(ses, top_row + 1, top_col + 1, bot_row - 1, bot_col - 1, rows - 2, cols - 2, flags, arg, arg1, arg2);
+}
+
+DO_DRAW(draw_center_left_line)
+{
+	int row = top_row;
+
+	arg = arg1;
+
+	arg1 += sprintf(arg1, "\e[%d;%dH%s", top_row, top_col, draw_corner(flags, "┬"));
+
+	while (++row < bot_row)
+	{
+		arg1 += sprintf(arg1, "\e[%d;%dH%s", row, top_col, draw_vertical(flags, "│"));
+	}
+
+	arg1 += sprintf(arg1, "\e[%d;%dH%s", bot_row, top_col, draw_corner(flags, "┴"));
+
+	printf("%s", arg);
+}
+
+DO_DRAW(draw_center_right_line)
+{
+	int row = top_row;
+
+	arg = arg1;
+
+	arg1 += sprintf(arg1, "\e[%d;%dH%s", top_row, bot_col, draw_corner(flags, "┬"));
+
+	while (++row < bot_row)
+	{
+		arg1 += sprintf(arg1, "\e[%d;%dH%s", row, bot_col, draw_vertical(flags, "│"));
+	}
+
+	arg1 += sprintf(arg1, "\e[%d;%dH%s", bot_row, bot_col, draw_corner(flags, "┴"));
+
+	printf("%s", arg);
+}
+
+DO_DRAW(draw_horizontal_line)
+{
+	int col;
+
+	arg = arg1;
+
+	arg1 += sprintf(arg1, "\e[%d;%dH%s", top_row, top_col, draw_horizontal(flags, "─"));
+
+	for (col = top_col + 1 ; col <= bot_col ; col++)
+	{
+		arg1 += sprintf(arg1, "%s", draw_horizontal(flags, "─"));
+	}
+
+	printf("%s", arg);
+}
+
+DO_DRAW(draw_left_line)
+{
+	int row = top_row;
+
+	arg = arg1;
+
+	arg1 += sprintf(arg1, "\e[%d;%dH%s", top_row, top_col, draw_corner(flags, "┌"));
+
+	while (++row < bot_row)
+	{
+		arg1 += sprintf(arg1, "\e[%d;%dH%s", row, top_col, draw_vertical(flags, "│"));
+	}
+
+	arg1 += sprintf(arg1, "\e[%d;%dH%s", bot_row, top_col, draw_corner(flags, "└"));
+
+	printf("%s", arg);
+}
+
+DO_DRAW(draw_map)
+{
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	top_row = get_row_index(ses, arg1);
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	top_col = get_col_index(ses, arg1);
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	bot_row = get_row_index(ses, arg1);
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	bot_col = get_col_index(ses, arg1);
+
+	rows = URANGE(1, 1 + bot_row - top_row, gtd->screen->rows);
+	cols = URANGE(1, 1 + bot_col - top_col, gtd->screen->cols);
+
+	save_pos(ses);
+
+	draw_text(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, arg2, arg1, arg);
+
+	load_pos(ses);
+}
+
+DO_DRAW(draw_middle_top_line)
+{
+	int col;
+
+	goto_pos(ses, top_row, top_col);
+
+	arg = arg1;
+
+	arg1 += sprintf(arg1, "%s", draw_corner(flags, "├"));
+
+	for (col = top_col + 1 ; col < bot_col ; col++)
+	{
+		arg1 += sprintf(arg1, "%s", draw_horizontal(flags, "─"));
+	}
+	
+	arg1 += sprintf(arg1, "%s", draw_corner(flags, "┤"));
+
+	printf("%s", arg);
+}
+
+DO_DRAW(draw_middle_bot_line)
+{
+	int col;
+
+	goto_pos(ses, bot_row, top_col);
+
+	arg = arg1;
+
+	arg1 += sprintf(arg1, "%s", draw_corner(flags, "├"));
+
+	for (col = top_col + 1 ; col < bot_col ; col++)
+	{
+		arg1 += sprintf(arg1, "%s", draw_horizontal(flags, "─"));
+	}
+
+	arg1 += sprintf(arg1, "%s", draw_corner(flags, "┤"));
+
+	printf("%s", arg);
+}
+
+DO_DRAW(draw_right_line)
+{
+	int row = top_row;
+
+	arg = arg1;
+
+	arg1 += sprintf(arg1, "\e[%d;%dH%s", top_row, bot_col, draw_corner(flags, "┐"));
+
+	while (++row < bot_row)
+	{
+		arg1 += sprintf(arg1, "\e[%d;%dH%s", row, bot_col, draw_vertical(flags, "│"));
+	}
+
+	arg1 += sprintf(arg1, "\e[%d;%dH%s", bot_row, bot_col, draw_corner(flags, "┘"));
+
+	printf("%s", arg);
+}
+
+DO_DRAW(draw_side_lines)
+{
+	draw_vertical_line(ses, top_row, top_col, bot_row, top_col, rows, cols, flags, arg, arg1, arg2);
+	draw_vertical_line(ses, top_row, bot_col, bot_row, bot_col, rows, cols, flags, arg, arg1, arg2);
+}
+
+DO_DRAW(draw_side_lines_text)
+{
+	draw_side_lines(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, arg, arg1, arg2);
+
+	draw_text(ses, top_row, top_col + 1, bot_row, bot_col - 1, rows, cols - 2, flags, arg, arg1, arg2);
+}
+
+DO_DRAW(draw_top_line)
+{
+	int col;
+
+	goto_pos(ses, top_row, top_col);
+
+	arg = arg1;
+
+	arg1 += sprintf(arg1, "%s", draw_corner(flags, "┌"));
+
+	for (col = top_col + 1 ; col < bot_col ; col++)
+	{
+		arg1 += sprintf(arg1, "%s", draw_horizontal(flags, "─"));
+	}
+	
+	arg1 += sprintf(arg1, "%s", draw_corner(flags, "┐"));
+
+	printf("%s", arg);
+}
+
+
+DO_DRAW(draw_text)
+{
+	char *txt;
+	int row, col, lines;
+
+	push_call("draw_text(%p,%d,%p,%p,%p)",ses,flags,arg,arg1,arg2);
+
+	draw_blank(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, arg, arg1, arg2);
+
+	txt = arg2;
+
+	while (*arg)
+	{
+		arg = sub_arg_in_braces(ses, arg, arg1, SUB_VAR|SUB_FUN|SUB_COL|SUB_ESC, GET_ALL);
+
+		txt += sprintf(txt, "%s\n", arg1);
+
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+	}
+
+	lines = -1 + word_wrap_split(ses, arg2, arg1, cols, 0, BUFFER_SIZE / cols);
+
+	txt = arg1;
+
+	while (*txt && lines > rows)
+	{
+		txt = strchr(txt, '\n');
+		txt++;
+		lines--;
+	}
+
+	arg = txt;
+	row = top_row;
+	col = top_col;
+
+	while (*arg)
+	{
+		arg = strchr(arg, '\n');
+
+		*arg++ = 0;
+
+		goto_pos(ses, row++, col);
+
+		printf("%s", txt);
+
+		txt = arg;
+	}
+	pop_call();
+	return;
+}
+
+DO_DRAW(draw_vertical_line)
+{
+	int row = top_row;
+
+	arg = arg1;
+
+	arg1 += sprintf(arg1, "\e[%d;%dH%s", top_row, top_col, draw_vertical(flags, "│"));
+
+	for (row = top_row + 1 ; row <= bot_row ; row++)
+	{
+		arg1 += sprintf(arg1, "\e[%d;%dH%s", row, top_col, draw_vertical(flags, "│"));
+	}
+
+	printf("%s", arg);
+}

+ 983 - 0
src/regex.c

@@ -0,0 +1,983 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 of the License, or         *
+*   (at your option) any later version.                                       *
+*                                                                             *
+*   This program is distributed in the hope that it will be useful,           *
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of            *
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
+*   GNU General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include <sys/types.h>
+#include <pcre.h>
+
+#include "tintin.h"
+
+
+int match(struct session *ses, char *str, char *exp, int sub)
+{
+	char expbuf[BUFFER_SIZE];
+
+	sprintf(expbuf, "\\A%s\\Z", exp);
+
+	substitute(ses, expbuf, expbuf, sub);
+
+	return tintin_regexp(ses, NULL, str, expbuf, 0, 0);
+}
+
+int find(struct session *ses, char *str, char *exp, int sub, int flag)
+{
+	if (HAS_BIT(sub, SUB_VAR|SUB_FUN))
+	{
+		char expbuf[BUFFER_SIZE], strbuf[BUFFER_SIZE];
+
+		substitute(ses, str, strbuf, SUB_VAR|SUB_FUN);
+		substitute(ses, exp, expbuf, SUB_VAR|SUB_FUN);
+
+		return tintin_regexp(ses, NULL, strbuf, expbuf, 0, flag);
+	}
+	else
+	{
+		return tintin_regexp(ses, NULL, str, exp, 0, flag);
+	}
+}
+
+DO_COMMAND(do_regexp)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], is_t[BUFFER_SIZE], is_f[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, is_t, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, is_f, GET_ALL);
+
+	if (*is_t == 0)
+	{
+		show_error(ses, LIST_COMMAND, "SYNTAX: #REGEXP {string} {expression} {true} {false}.");
+	}
+	else
+	{
+		if (tintin_regexp(ses, NULL, arg1, arg2, 0, SUB_CMD))
+		{
+			substitute(ses, is_t, is_t, SUB_CMD);
+
+			ses = script_driver(ses, LIST_COMMAND, is_t);
+		}
+		else if (*is_f)
+		{
+			ses = script_driver(ses, LIST_COMMAND, is_f);
+		}
+	}
+	return ses;
+}
+
+int regexp_compare(pcre *nodepcre, char *str, char *exp, int option, int flag)
+{
+	pcre *regex;
+	const char *error;
+	int i, j, matches, match[303];
+
+	if (nodepcre == NULL)
+	{
+		regex = pcre_compile(exp, option, &error, &i, NULL);
+	}
+	else
+	{
+		regex = nodepcre;
+	}
+
+	if (regex == NULL)
+	{
+		return FALSE;
+	}
+
+	matches = pcre_exec(regex, NULL, str, strlen(str), 0, 0, match, 303);
+
+	if (matches <= 0)
+	{
+		if (nodepcre == NULL)
+		{
+			free(regex);
+		}
+		return FALSE;
+	}
+
+	// SUB_FIX handles %1 to %99 usage. Backward compatibility.
+
+	switch (flag)
+	{
+		case SUB_CMD:
+			for (i = 0 ; i < matches ; i++)
+			{
+				gtd->cmds[i] = restringf(gtd->cmds[i], "%.*s", match[i*2+1] - match[i*2], &str[match[i*2]]);
+			}
+			break;
+
+		case SUB_CMD + SUB_FIX:
+			for (i = 0 ; i < matches ; i++)
+			{
+				j = gtd->args[i];
+
+				gtd->cmds[j] = restringf(gtd->cmds[j], "%.*s", match[i*2+1] - match[i*2], &str[match[i*2]]);
+			}
+			break;
+
+		case SUB_ARG:
+			for (i = 0 ; i < matches ; i++)
+			{
+				gtd->vars[i] = restringf(gtd->vars[i], "%.*s", match[i*2+1] - match[i*2], &str[match[i*2]]);
+			}
+			break;
+
+		case SUB_ARG + SUB_FIX:
+			for (i = 0 ; i < matches ; i++)
+			{
+				j = gtd->args[i];
+
+				gtd->vars[j] = restringf(gtd->vars[j], "%.*s", match[i*2+1] - match[i*2], &str[match[i*2]]);
+			}
+			break;
+	}
+
+	if (nodepcre == NULL)
+	{
+		free(regex);
+	}
+
+	return TRUE;
+}
+
+pcre *regexp_compile(char *exp, int option)
+{
+	const char *error;
+	int i;
+
+	return pcre_compile(exp, option, &error, &i, NULL);
+}
+
+
+
+
+/******************************************************************************
+* Calls tintin_regexp checking if the string matches, and automatically fills *
+* in the text represented by the wildcards on success.                        *
+******************************************************************************/
+
+int check_one_regexp(struct session *ses, struct listnode *node, char *line, char *original, int option)
+{
+	char *exp, *str;
+
+	if (node->regex == NULL)
+	{
+		char result[BUFFER_SIZE];
+
+		substitute(ses, node->arg1, result, SUB_VAR|SUB_FUN);
+
+		exp = result;
+	}
+	else
+	{
+		exp = node->arg1;
+	}
+
+	if (*node->arg1 == '~')
+	{
+		exp++;
+		str = original;
+	}
+	else
+	{
+		str = line;
+	}	
+
+	return tintin_regexp(ses, node->regex, str, exp, option, SUB_ARG);
+}
+
+/*
+	Keep synched with tintin_regexp and tintin_regexp_compile
+*/
+
+int tintin_regexp_check(struct session *ses, char *exp)
+{
+	if (*exp == '^')
+	{
+		return TRUE;
+	}
+
+	while (*exp)
+	{
+
+		if (HAS_BIT(ses->flags, SES_FLAG_BIG5) && *exp & 128 && exp[1] != 0)
+		{
+			exp += 2;
+			continue;
+		}
+
+		switch (exp[0])
+		{
+			case '\\':
+			case '{':
+				return TRUE;
+
+			case '$':
+				if (exp[1] == 0)
+				{
+					return TRUE;
+				}
+				break;
+
+			case '%':
+				switch (exp[1])
+				{
+					case '0':
+					case '1':
+					case '2':
+					case '3':
+					case '4':
+					case '5':
+					case '6':
+					case '7':
+					case '8':
+					case '9':
+					case 'd':
+					case 'D':
+					case 'i':
+					case 'I':
+					case 's':
+					case 'S':
+					case 'w':
+					case 'W':
+					case '?':
+					case '*':
+					case '+':
+					case '.':
+					case '%':
+						return TRUE;
+
+					case '!':
+						switch (exp[2])
+						{
+							case 'd':
+							case 'D':
+							case 's':
+							case 'S':
+							case 'w':
+							case 'W':
+							case '?':
+							case '*':
+							case '+':
+							case '.':
+							case '{':
+								return TRUE;
+						}
+						break;
+				}
+				break;
+		}
+		exp++;
+	}
+	return FALSE;
+}
+				
+int tintin_regexp(struct session *ses, pcre *nodepcre, char *str, char *exp, int option, int flag)
+{
+	char out[BUFFER_SIZE], *pti, *pto;
+	int arg = 1, var = 1, fix = 0;
+
+	pti = exp;
+	pto = out;
+
+	while (*pti == '^')
+	{
+		*pto++ = *pti++;
+	}
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->flags, SES_FLAG_BIG5) && *pti & 128 && pti[1] != 0)
+		{
+			*pto++ = *pti++;
+
+			switch (*pti)
+			{
+				case '\\':
+				case '[':
+				case ']':
+				case '(':
+				case ')':
+				case '|':
+				case '.':
+				case '?':
+				case '+':
+				case '*':
+				case '$':
+				case '^':
+					*pto++ = '\\';
+					break;
+			}
+			*pto++ = *pti++;
+			continue;
+		}
+
+		switch (pti[0])
+		{
+			case '\\':
+				*pto++ = *pti++;
+				*pto++ = *pti++;
+				break;
+
+			case '{':
+				gtd->args[next_arg(var)] = next_arg(arg);
+				*pto++ = '(';
+				pti = get_arg_in_braces(ses, pti, pto, GET_ALL);
+				pto += strlen(pto);
+				*pto++ = ')';
+				break;
+
+			case '[':
+			case ']':
+			case '(':
+			case ')':
+			case '|':
+			case '.':
+			case '?':
+			case '+':
+			case '*':
+			case '^':
+				*pto++ = '\\';
+				*pto++ = *pti++;
+				break;
+
+			case '$':
+				if (pti[1] != DEFAULT_OPEN && !isalnum((int) pti[1]))
+				{
+					int i = 0;
+
+					while (pti[++i] == '$')
+					{
+						continue;
+					}
+
+					if (pti[i])
+					{
+						*pto++ = '\\';
+					}
+				}
+				*pto++ = *pti++;
+				break;
+
+			case '%':
+				switch (pti[1])
+				{
+					case '0':
+					case '1':
+					case '2':
+					case '3':
+					case '4':
+					case '5':
+					case '6':
+					case '7':
+					case '8':
+					case '9':
+						fix = SUB_FIX;
+						arg = isdigit((int) pti[2]) ? (pti[1] - '0') * 10 + (pti[2] - '0') : pti[1] - '0';
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += isdigit((int) pti[2]) ? 3 : 2;
+						strcpy(pto, *pti == 0 ? "(.*)" : "(.*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'd':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([0-9]*)" : "([0-9]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'D':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([^0-9]*)" : "([^0-9]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'i':
+						pti += 2;
+						strcpy(pto, "(?i)");
+						pto += strlen(pto);
+						break;
+
+					case 'I':
+						pti += 2;
+						strcpy(pto, "(?-i)");
+						pto += strlen(pto);
+						break;
+
+					case 's':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(\\s*)" : "(\\s*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'S':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(\\S*)" : "(\\S*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'w':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([a-zA-Z]*)" : "([a-zA-Z]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'W':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([^a-zA-Z]*)" : "([^a-zA-Z]*?)");
+						pto += strlen(pto);
+						break;
+
+					case '?':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(.?)" : "(.?" "?)");
+						pto += strlen(pto);
+						break;
+
+					case '*':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(.*)" : "(.*?)");
+						pto += strlen(pto);
+						break;
+
+					case '+':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(.+)" : "(.+?)");
+						pto += strlen(pto);
+						break;
+
+					case '.':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, "(.)");
+						pto += strlen(pto);
+						break;
+
+					case '%':
+						*pto++ = *pti++;
+						pti++;
+						break;
+
+					case '!':
+						switch (pti[2])
+						{
+							case 'd':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[0-9]*" : "[0-9]*?");
+								pto += strlen(pto);
+								break;
+
+							case 'D':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[^0-9]*" : "[^0-9]*?");
+									pto += strlen(pto);
+								break;
+
+							case 's':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "\\s*" : "\\s*?");
+								pto += strlen(pto);
+								break;
+
+							case 'S':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "\\S*" : "\\S*?");
+								pto += strlen(pto);
+								break;
+
+							case 'w':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[a-zA-Z]*" : "[a-zA-Z]*?");
+								pto += strlen(pto);
+								break;
+
+							case 'W':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[^a-zA-Z]*" : "[^a-zA-Z]*?");
+								pto += strlen(pto);
+								break;
+
+							case '?':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? ".?" : ".?" "?");
+								pto += strlen(pto);
+								break;
+
+							case '*':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? ".*" : ".*?");
+								pto += strlen(pto);
+								break;
+
+							case '+':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? ".+" : ".+?");
+								pto += strlen(pto);
+								break;
+
+							case '.':
+								pti += 3;
+								strcpy(pto, ".");
+								pto += strlen(pto);
+								break;
+
+							case '{':
+								pti = get_arg_in_braces(ses, pti+2, pto, GET_ALL);
+								pto += strlen(pto);
+								break;
+
+							default:
+								*pto++ = *pti++;
+								break;
+						}
+						break;
+
+					default:
+						*pto++ = *pti++;
+						break;
+				}
+				break;
+
+			default:
+				*pto++ = *pti++;
+				break;
+		}
+	}
+	*pto = 0;
+
+	return regexp_compare(nodepcre, str, out, option, flag + fix);
+}
+
+pcre *tintin_regexp_compile(struct session *ses, struct listnode *node, char *exp, int option)
+{
+	char out[BUFFER_SIZE], *pti, *pto;
+
+	pti = exp;
+	pto = out;
+
+	if (*pti == '~')
+	{
+		pti++;
+	}
+
+	while (*pti == '^')
+	{
+		*pto++ = *pti++;
+	}
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->flags, SES_FLAG_BIG5) && *pti & 128 && pti[1] != 0)
+		{
+			*pto++ = *pti++;
+
+			switch (*pti)
+			{
+				case '\\':
+				case '[':
+				case ']':
+				case '(':
+				case ')':
+				case '|':
+				case '.':
+				case '?':
+				case '+':
+				case '*':
+				case '$':
+				case '^':
+					*pto++ = '\\';
+					break;
+			}
+			*pto++ = *pti++;
+			continue;
+		}
+		switch (pti[0])
+		{
+			case '\\':
+				*pto++ = *pti++;
+				*pto++ = *pti++;
+				break;
+
+			case '{':
+				*pto++ = '(';
+				pti = get_arg_in_braces(ses, pti, pto, GET_ALL);
+				while (*pto)
+				{
+					if (pto[0] == '$' || pto[0] == '@')
+					{
+						if (pto[1])
+						{
+							return NULL;
+						}
+					}
+					pto++;
+				}
+				*pto++ = ')';
+				break;
+
+			case '&':
+				if (pti[1] == DEFAULT_OPEN || isalnum((int) pti[1]) || pti[1] == '&')
+				{
+					return NULL;
+				}
+				*pto++ = *pti++;
+				break;
+
+			case '@':
+				if (pti[1] == DEFAULT_OPEN || isalnum((int) pti[1]) || pti[1] == '@')
+				{
+					return NULL;
+				}
+				*pto++ = *pti++;
+				break;
+
+			case '$':
+				if (pti[1] == DEFAULT_OPEN || isalnum((int) pti[1]))
+				{
+					return NULL;
+				}
+				{
+					int i = 0;
+
+					while (pti[++i] == '$')
+					{
+						continue;
+					}
+
+					if (pti[i])
+					{
+						*pto++ = '\\';
+					}
+				}
+				*pto++ = *pti++;
+				break;
+
+			case '[':
+			case ']':
+			case '(':
+			case ')':
+			case '|':
+			case '.':
+			case '?':
+			case '+':
+			case '*':
+			case '^':
+				*pto++ = '\\';
+				*pto++ = *pti++;
+				break;
+
+			case '%':
+				switch (pti[1])
+				{
+					case '0':
+					case '1':
+					case '2':
+					case '3':
+					case '4':
+					case '5':
+					case '6':
+					case '7':
+					case '8':
+					case '9':
+						pti += isdigit((int) pti[2]) ? 3 : 2;
+						strcpy(pto, *pti == 0 ? "(.*)" : "(.*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'd':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([0-9]*)" : "([0-9]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'D':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([^0-9]*)" : "([^0-9]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'i':
+						pti += 2;
+						strcpy(pto, "(?i)");
+						pto += strlen(pto);
+						break;
+
+					case 'I':
+						pti += 2;
+						strcpy(pto, "(?-i)");
+						pto += strlen(pto);
+						break;
+
+					case 's':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(\\s*)" : "(\\s*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'S':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(\\S*)" : "(\\S*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'w':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([a-zA-Z]*)" : "([a-zA-Z]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'W':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([^a-zA-Z]*)" : "([^a-zA-Z]*?)");
+						pto += strlen(pto);
+						break;
+
+					case '?':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(.?)" : "(.?" "?)");
+						pto += strlen(pto);
+						break;
+
+					case '*':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(.*)" : "(.*?)");
+						pto += strlen(pto);
+						break;
+
+					case '+':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(.+)" : "(.+?)");
+						pto += strlen(pto);
+						break;
+
+					case '.':
+						pti += 2;
+						strcpy(pto, "(.)");
+						pto += strlen(pto);
+						break;
+
+					case '%':
+						*pto++ = *pti++;
+						pti++;
+						break;
+
+					case '!':
+						switch (pti[2])
+						{
+							case 'd':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[0-9]*" : "[0-9]*?");
+								pto += strlen(pto);
+								break;
+
+							case 'D':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[^0-9]*" : "[^0-9]*?");
+									pto += strlen(pto);
+								break;
+
+							case 's':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "\\s*" : "\\s*?");
+								pto += strlen(pto);
+								break;
+
+							case 'S':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "\\S*" : "\\S*?");
+								pto += strlen(pto);
+								break;
+
+							case 'w':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[a-zA-Z]*" : "[a-zA-Z]*?");
+								pto += strlen(pto);
+								break;
+
+							case 'W':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[^a-zA-Z]*" : "[^a-zA-Z]*?");
+								pto += strlen(pto);
+								break;
+
+							case '?':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? ".?" : ".?" "?");
+								pto += strlen(pto);
+								break;
+
+							case '*':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? ".*" : ".*?");
+								pto += strlen(pto);
+								break;
+
+							case '+':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? ".+" : ".+?");
+								pto += strlen(pto);
+								break;
+
+							case '.':
+								pti += 3;
+								strcpy(pto, ".");
+								pto += strlen(pto);
+								break;
+
+							case '{':
+								pti = get_arg_in_braces(ses, pti+2, pto, GET_ALL);
+
+								while (*pto)
+								{
+									if (pto[0] == '$' || pto[0] == '@')
+									{
+										if (pto[1])
+										{
+											return NULL;
+										}
+									}
+									pto++;
+								}
+								break;
+
+							default:
+								*pto++ = *pti++;
+								break;
+						}
+						break;
+
+					default:
+						*pto++ = *pti++;
+						break;
+				}
+				break;
+
+			default:
+				*pto++ = *pti++;
+				break;
+		}
+	}
+	*pto = 0;
+
+//	printf("debug regex compile (%s)\n", out);
+
+	return regexp_compile(out, option);
+}
+
+void tintin_macro_compile(char *input, char *output)
+{
+	char *pti, *pto;
+
+	pti = input;
+	pto = output;
+
+	if (*pti == '^')
+	{
+		pti++;
+	}
+
+	while (*pti)
+	{
+		switch (pti[0])
+		{
+			case '\\':
+				switch (pti[1])
+				{
+					case 'C':
+						if (pti[2] == '-' && pti[3])
+						{
+							*pto++  = pti[3] - 'a' + 1;
+							pti    += 4;
+						}
+						else
+						{
+							*pto++ = *pti++;
+						}
+						break;
+
+					case 'c':
+						*pto++ = pti[2] % 32;
+						pti += 3;
+						break;
+
+					case 'a':
+						*pto++  = ASCII_BEL;
+						pti += 2;
+						break;
+
+					case 'b':
+						*pto++  = 127;
+						pti    += 2;
+						break;
+
+					case 'e':
+						*pto++  = ASCII_ESC;
+						pti    += 2;
+						break;
+
+					case 'r':
+						*pto++ = ASCII_CR;
+						pti   += 2;
+						break;
+
+					case 't':
+						*pto++  = ASCII_HTAB;
+						pti    += 2;
+						break;
+
+					case 'x':
+						if (pti[2] && pti[3])
+						{
+							*pto++ = hex_number_8bit(&pti[2]);
+							pti += 4;
+						}
+						else
+						{
+							*pto++ = *pti++;
+						}
+						break;
+					default:
+						*pto++ = *pti++;
+						break;
+				}
+				break;
+
+			default:
+				*pto++ = *pti++;
+				break;
+		}
+	}
+	*pto = 0;
+}

+ 970 - 0
src/trigger.c

@@ -0,0 +1,970 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 of the License, or         *
+*   (at your option) any later version.                                       *
+*                                                                             *
+*   This program is distributed in the hope that it will be useful,           *
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of            *
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
+*   GNU General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+#include "tintin.h"
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                        coded by Peter Unold 1992                            *
+*                   recoded by Igor van den Hoven 2009                        *
+******************************************************************************/
+
+DO_COMMAND(do_action)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg3, GET_ALL);
+
+	if (*arg3 == 0)
+	{
+//		strcpy(arg3, "5");
+	}
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_ACTION], 0);
+	}
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_ACTION]) == FALSE)
+		{
+			show_message(ses, LIST_ACTION, "#ACTION: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_ACTION], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_ACTION, "#OK. #ACTION {%s} NOW TRIGGERS {%s} @ {%s}.", arg1, arg2, arg3);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unaction)
+{
+	delete_node_with_wild(ses, LIST_ACTION, arg);
+
+	return ses;
+}
+
+
+void check_all_actions(struct session *ses, char *original, char *line)
+{
+	struct listroot *root = ses->list[LIST_ACTION];
+	char buf[BUFFER_SIZE];
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		if (check_one_regexp(ses, root->list[root->update], line, original, 0))
+		{
+			show_debug(ses, LIST_ACTION, "#DEBUG ACTION {%s}", root->list[root->update]->arg1);
+
+			substitute(ses, root->list[root->update]->arg2, buf, SUB_ARG|SUB_SEC);
+
+			script_driver(ses, LIST_ACTION, buf);
+
+			return;
+		}
+	}
+}
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                        coded by Peter Unold 1992                            *
+*                   recoded by Igor van den Hoven 2009                        *
+******************************************************************************/
+
+DO_COMMAND(do_alias)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg3, GET_ALL);
+
+	if (*arg3 == 0)
+	{
+//		strcpy(arg3, "5");
+	}
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_ALIAS], 0);
+	}
+	else if (*arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_ALIAS]) == FALSE)
+		{
+			show_message(ses, LIST_ALIAS, "#ALIAS: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_ALIAS], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_ALIAS, "#ALIAS {%s} NOW TRIGGERS {%s} @ {%s}.", arg1, arg2, arg3);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unalias)
+{
+	delete_node_with_wild(ses, LIST_ALIAS, arg);
+
+	return ses;
+}
+
+int check_all_aliases(struct session *ses, char *input)
+{
+	struct listnode *node;
+	struct listroot *root;
+	char tmp[BUFFER_SIZE], line[BUFFER_SIZE], *arg;
+	int i;
+
+	root = ses->list[LIST_ALIAS];
+
+	if (HAS_BIT(root->flags, LIST_FLAG_IGNORE))
+	{
+		return FALSE;
+	}
+
+	substitute(ses, input, line, SUB_VAR|SUB_FUN);
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		if (check_one_regexp(ses, root->list[root->update], line, line, PCRE_ANCHORED))
+		{
+			node = root->list[root->update];
+
+			i = strlen(node->arg1);
+
+			if (!strncmp(node->arg1, line, i))
+			{
+				if (line[i])
+				{
+					if (line[i] != ' ')
+					{
+						continue;
+					}
+					arg = &line[i + 1];
+				}
+				else
+				{
+					arg = &line[i];
+				}
+
+				RESTRING(gtd->vars[0], arg)
+
+				for (i = 1 ; i < 100 ; i++)
+				{
+					arg = get_arg_in_braces(ses, arg, tmp, GET_ONE);
+
+					RESTRING(gtd->vars[i], tmp);
+
+					if (*arg == 0)
+					{
+						while (++i < 100)
+						{
+							if (*gtd->vars[i])
+							{
+								RESTRING(gtd->vars[i], "");
+							}
+						}
+						break;
+					}
+
+				}
+			}
+
+			substitute(ses, node->arg2, tmp, SUB_ARG);
+
+			if (!strncmp(node->arg1, line, strlen(node->arg1)) && !strcmp(node->arg2, tmp) && *gtd->vars[0])
+			{
+				sprintf(input, "%s %s", tmp, gtd->vars[0]);
+			}
+			else
+			{
+				sprintf(input, "%s", tmp);
+			}
+
+			show_debug(ses, LIST_ALIAS, "#DEBUG ALIAS {%s} {%s}", node->arg1, gtd->vars[0]);
+
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2019                       *
+******************************************************************************/
+
+
+DO_COMMAND(do_button)
+{
+	struct listnode *node;
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+	int index;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg3, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_BUTTON], 0);
+	}
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_BUTTON]) == FALSE)
+		{
+			show_message(ses, LIST_BUTTON, "#BUTTON: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		if (*arg3 == 0)
+		{
+//			strcpy(arg3, "5");
+		}
+		node = update_node_list(ses->list[LIST_BUTTON], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_BUTTON, "#OK. BUTTON {%s} NOW TRIGGERS {%s} @ {%s}.", arg1, arg2, arg3);
+
+		arg = arg1;
+
+		for (index = 0 ; index < 4 ; index++)
+		{
+			arg = get_arg_in_braces(ses, arg, arg2, GET_ONE);
+
+			node->val16[index] = (short) get_number(ses, arg2);
+
+			if (*arg == COMMAND_SEPARATOR)
+			{
+				arg++;
+			}
+		}
+
+		if (*arg)
+		{
+			arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+			RESTRING(node->arg4, arg2);
+		}
+		else
+		{
+			RESTRING(node->arg4, "PRESSED MOUSE BUTTON ONE");
+		}
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unbutton)
+{
+	delete_node_with_wild(ses, LIST_BUTTON, arg);
+
+	return ses;
+}
+
+void check_all_buttons(struct session *ses, short row, short col, char *arg1, char *arg2, char *word, char *line)
+{
+	char buf[BUFFER_SIZE], arg4[BUFFER_SIZE];
+	struct listnode *node;
+	struct listroot *root;
+	short val[4];
+
+	root = ses->list[LIST_BUTTON];
+
+	if (HAS_BIT(root->flags, LIST_FLAG_IGNORE))
+	{
+		return;
+	}
+
+	sprintf(arg4, "%s %s", arg1, arg2);
+
+	show_info(ses, LIST_BUTTON, "#INFO BUTTON {%d;%d;%d;%d;%s}", row, col, -1 - (gtd->screen->rows - row), -1 - (gtd->screen->cols - col), arg4);
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		node = root->list[root->update];
+
+		val[0] = node->val16[0] < 0 ? 1 + gtd->screen->rows + node->val16[0] : node->val16[0];
+		val[1] = node->val16[1] < 0 ? 1 + gtd->screen->cols + node->val16[1] : node->val16[1];
+
+		if (row < val[0] || col < val[1])
+		{
+			continue;
+		}
+
+		val[2] = node->val16[2] < 0 ? 1 + gtd->screen->rows + node->val16[2] : node->val16[2];
+		val[3] = node->val16[3] < 0 ? 1 + gtd->screen->cols + node->val16[3] : node->val16[3];
+
+		if (row > val[2] || col > val[3])
+		{
+			continue;
+		}
+
+		if (!strcmp(arg4, node->arg4))
+		{
+			show_debug(ses, LIST_BUTTON, "#DEBUG BUTTON {%s}", node->arg1);
+
+			RESTRING(gtd->vars[0], ntos(row));
+			RESTRING(gtd->vars[1], ntos(col));
+			RESTRING(gtd->vars[2], ntos(-1 - (gtd->screen->rows - row)));
+			RESTRING(gtd->vars[3], ntos(-1 - (gtd->screen->cols - col)));
+			RESTRING(gtd->vars[4], word);
+			RESTRING(gtd->vars[5], line);
+
+			substitute(ses, node->arg2, buf, SUB_ARG|SUB_SEC);
+			
+			script_driver(ses, LIST_BUTTON, buf);
+
+			return;
+		}
+	}
+}
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+DO_COMMAND(do_delay)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], temp[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_DELAY], 0);
+	}
+	else if (*arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_DELAY]) == FALSE)
+		{
+			show_message(ses, LIST_DELAY, "#DELAY: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		if (*arg3 == 0)
+		{
+			sprintf(arg3, "%lld", utime() + (long long) (1000000 * get_number(ses, arg1)));
+
+			get_number_string(ses, arg1, temp);
+
+			update_node_list(ses->list[LIST_DELAY], arg3, arg2, temp, "");
+
+			show_message(ses, LIST_TICKER, "#OK, IN {%s} SECONDS {%s} IS EXECUTED.", temp, arg2);
+		}
+		else
+		{
+			get_number_string(ses, arg3, temp);
+
+			update_node_list(ses->list[LIST_DELAY], arg1, arg2, temp, "");
+
+			show_message(ses, LIST_TICKER, "#OK, IN {%s} SECONDS {%s} IS EXECUTED.", temp, arg2);
+		}
+	}
+	return ses;
+}
+
+DO_COMMAND(do_undelay)
+{
+	delete_node_with_wild(ses, LIST_DELAY, arg);
+
+	return ses;
+}
+
+// checked in update.c
+
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                       coded by Sverre Normann 1999                          *
+*                    recoded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+DO_COMMAND(do_function)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_FUNCTION], 0);
+	}
+
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_FUNCTION]) == FALSE)
+		{
+			show_message(ses, LIST_FUNCTION, "#FUNCTION: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_FUNCTION], arg1, arg2, "", "");
+
+		show_message(ses, LIST_FUNCTION, "#OK. FUNCTION {%s} HAS BEEN SET TO {%s}.", arg1, arg2);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unfunction)
+{
+	delete_node_with_wild(ses, LIST_FUNCTION, arg);
+
+	return ses;
+}
+
+// checked in tinexp.c
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                       coded by Igor van den Hoven 2007                      *
+******************************************************************************/
+
+DO_COMMAND(do_gag)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_GAG], 0);
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_GAG], arg1, "", "", "");
+
+		show_message(ses, LIST_GAG, "#OK. {%s} IS NOW GAGGED.", arg1);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_ungag)
+{
+	delete_node_with_wild(ses, LIST_GAG, arg);
+
+	return ses;
+}
+
+void check_all_gags(struct session *ses, char *original, char *line)
+{
+	struct listroot *root = ses->list[LIST_GAG];
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		if (check_one_regexp(ses, root->list[root->update], line, original, 0))
+		{
+			show_debug(ses, LIST_GAG, "#DEBUG GAG {%s}", root->list[root->update]->arg1);
+
+			SET_BIT(ses->flags, SES_FLAG_GAG);
+
+			return;
+		}
+	}
+}
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                          coded by Bill Reiss 1993                           *
+*                     recoded by Igor van den Hoven 2004                      *
+******************************************************************************/
+
+
+DO_COMMAND(do_highlight)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], temp[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg3, GET_ALL);
+
+	if (*arg3 == 0)
+	{
+//		strcpy(arg3, "5");
+	}
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_HIGHLIGHT], 0);
+	}
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_HIGHLIGHT]) == FALSE)
+		{
+			show_message(ses, LIST_HIGHLIGHT, "#HIGHLIGHT: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		if (get_color_names(ses, arg2, temp) == FALSE)
+		{
+			tintin_printf2(ses, "#HIGHLIGHT: VALID COLORS ARE:\n");
+			tintin_printf2(ses, "reset, bold, light, faint, dim, dark, underscore, blink, reverse, black, red, green, yellow, blue, magenta, cyan, white, b black, b red, b green, b yellow, b blue, b magenta, b cyan, b white, azure, ebony, jade, lime, orange, pink, silver, tan, violet.");
+		}
+		else
+		{
+			update_node_list(ses->list[LIST_HIGHLIGHT], arg1, arg2, arg3, "");
+
+			show_message(ses, LIST_HIGHLIGHT, "#OK. {%s} NOW HIGHLIGHTS {%s} @ {%s}.", arg1, arg2, arg3);
+		}
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unhighlight)
+{
+	delete_node_with_wild(ses, LIST_HIGHLIGHT, arg);
+
+	return ses;
+}
+
+void check_all_highlights(struct session *ses, char *original, char *line)
+{
+	struct listroot *root = ses->list[LIST_HIGHLIGHT];
+	struct listnode *node;
+	char *pto, *ptl, *ptm;
+	char match[BUFFER_SIZE], color[BUFFER_SIZE], reset[BUFFER_SIZE], output[BUFFER_SIZE], plain[BUFFER_SIZE];
+	int len;
+
+	push_call("check_all_highlights(%p,%p,%p)",ses,original,line);
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		if (check_one_regexp(ses, root->list[root->update], line, original, 0))
+		{
+			node = root->list[root->update];
+
+			get_color_names(ses, node->arg2, color);
+
+			*output = *reset = 0;
+
+			pto = original;
+			ptl = line;
+
+			do
+			{
+				if (*gtd->vars[0] == 0)
+				{
+					break;
+				}
+
+				strcpy(match, gtd->vars[0]);
+
+				strip_vt102_codes(match, plain);
+
+				if (*node->arg1 == '~')
+				{
+					ptm = strstr(pto, match);
+
+					len = strlen(match);
+				}
+				else
+				{
+					ptm = strip_vt102_strstr(pto, match, &len);
+
+					ptl = strstr(ptl, match) + strlen(match);
+				}
+
+				*ptm = 0;
+
+				get_color_codes(reset, pto, reset);
+
+				cat_sprintf(output, "%s%s%s\e[0m%s", pto, color, plain, reset);
+
+				pto = ptm + len;
+
+				show_debug(ses, LIST_HIGHLIGHT, "#DEBUG HIGHLIGHT {%s}", node->arg1);
+			}
+			while (check_one_regexp(ses, node, ptl, pto, 0));
+
+			strcat(output, pto);
+
+			strcpy(original, output);
+		}
+	}
+	pop_call();
+	return;
+}
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2006                       *
+******************************************************************************/
+
+
+DO_COMMAND(do_macro)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_MACRO], 0);
+	}
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_MACRO]) == FALSE)
+		{
+			show_message(ses, LIST_MACRO, "#MACRO: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		tintin_macro_compile(arg1, arg3);
+
+		update_node_list(ses->list[LIST_MACRO], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_MACRO, "#OK. MACRO {%s} HAS BEEN SET TO {%s}.", arg1, arg2);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unmacro)
+{
+	delete_node_with_wild(ses, LIST_MACRO, arg);
+
+	return ses;
+}
+
+// checked in input.c
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+
+DO_COMMAND(do_prompt)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], arg4[BUFFER_SIZE];
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_PROMPT], 0);
+	}
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_PROMPT]) == FALSE)
+		{
+			show_message(ses, LIST_PROMPT, "#PROMPT: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+		arg = sub_arg_in_braces(ses, arg, arg4, GET_ONE, SUB_VAR|SUB_FUN);
+
+		update_node_list(ses->list[LIST_PROMPT], arg1, arg2, arg3, arg4);
+
+		show_message(ses, LIST_PROMPT, "#OK. {%s} NOW PROMPTS {%s} @ {%s} {%s}.", arg1, arg2, arg3, arg4);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unprompt)
+{
+	delete_node_with_wild(ses, LIST_PROMPT, arg);
+
+	return ses;
+}
+
+
+void check_all_prompts(struct session *ses, char *original, char *line)
+{
+	struct listroot *root = ses->list[LIST_PROMPT];
+	struct listnode *node;
+
+	if (!HAS_BIT(ses->flags, SES_FLAG_SPLIT))
+	{
+		return;
+	}
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		if (check_one_regexp(ses, root->list[root->update], line, original, 0))
+		{
+			node = root->list[root->update];
+
+			if (*node->arg2)
+			{
+				substitute(ses, node->arg2, original, SUB_ARG);
+				substitute(ses, original, original, SUB_VAR|SUB_FUN|SUB_COL|SUB_ESC);
+			}
+
+			show_debug(ses, LIST_PROMPT, "#DEBUG PROMPT {%s}", node->arg1);
+			show_debug(ses, LIST_GAG, "#DEBUG GAG {%s}", node->arg1);
+
+			split_show(ses, original, atoi(node->arg3), atoi(node->arg4));
+
+			SET_BIT(ses->flags, SES_FLAG_GAG);
+		}
+	}
+}
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                         coded by Peter Unold 1992                           *
+*                    recoded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+
+DO_COMMAND(do_substitute)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], *str;
+
+	str = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, str, arg2, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg3, GET_ALL);
+
+	if (*arg3 == 0)
+	{
+//		strcpy(arg3, "5");
+	}
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_SUBSTITUTE], 0);
+	}
+	else if (*str == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_SUBSTITUTE]) == FALSE)
+		{
+			show_message(ses, LIST_SUBSTITUTE, "#SUBSTITUTE: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_SUBSTITUTE], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_SUBSTITUTE, "#OK. {%s} IS NOW SUBSTITUTED AS {%s} @ {%s}.", arg1, arg2, arg3);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unsubstitute)
+{
+	delete_node_with_wild(ses, LIST_SUBSTITUTE, arg);
+
+	return ses;
+}
+
+void check_all_substitutions(struct session *ses, char *original, char *line)
+{
+	char match[BUFFER_SIZE], subst[BUFFER_SIZE], output[BUFFER_SIZE], temp[BUFFER_SIZE], *ptl, *ptm, *pto;
+	struct listroot *root = ses->list[LIST_SUBSTITUTE];
+	struct listnode *node;
+	int len;
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		if (check_one_regexp(ses, root->list[root->update], line, original, 0))
+		{
+			node = root->list[root->update];
+
+			pto = original;
+			ptl = line;
+
+			*output = 0;
+
+			do
+			{
+				if (*gtd->vars[0] == 0)
+				{
+					break;
+				}
+
+				strcpy(match, gtd->vars[0]);
+
+				substitute(ses, node->arg2, temp, SUB_ARG);
+				substitute(ses, temp, subst, SUB_VAR|SUB_FUN|SUB_COL|SUB_ESC);
+
+				if (*node->arg1 == '~')
+				{
+					ptm = strstr(pto, match);
+
+					len = strlen(match);
+				}
+				else
+				{
+					ptm = strip_vt102_strstr(pto, match, &len);
+
+					ptl = strstr(ptl, match) + strlen(match);
+				}
+
+				*ptm = 0;
+
+				cat_sprintf(output, "%s%s", pto, subst);
+
+				pto = ptm + len;
+
+				show_debug(ses, LIST_SUBSTITUTE, "#DEBUG SUBSTITUTE {%s} {%s}", node->arg1, match);
+			}
+			while (check_one_regexp(ses, node, ptl, pto, 0));
+
+			strcat(output, pto);
+
+//			substitute(ses, output, original, SUB_VAR|SUB_FUN|SUB_COL|SUB_ESC);
+
+			strcpy(original, output);
+
+			strip_vt102_codes(original, line);
+		}
+	}
+}
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2006                       *
+******************************************************************************/
+
+
+DO_COMMAND(do_tab)
+{
+	char arg1[BUFFER_SIZE];
+
+	sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_TAB], 0);
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_TAB], arg1, "", "", "");
+
+		show_message(ses, LIST_TAB, "#OK. {%s} IS NOW A TAB.", arg1);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_untab)
+{
+	delete_node_with_wild(ses, LIST_TAB, arg);
+
+	return ses;
+}
+
+// checked in cursor.c
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                         coded by Peter Unold 1992                           *
+*                     recoded by Igor van den Hoven 2004                      *
+******************************************************************************/
+
+
+DO_COMMAND(do_tick)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], arg4[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg4, GET_ALL);
+
+	if (*arg4 == 0)
+	{
+		strcpy(arg3, "60");
+	}
+	else
+	{
+		get_number_string(ses, arg4, arg3);
+	}
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_TICKER], 0);
+	}
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_TICKER]) == FALSE) 
+		{
+			show_message(ses, LIST_TICKER, "#TICK, NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_TICKER], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_TICKER, "#OK. #TICK {%s} NOW EXECUTES {%s} EVERY {%s} SECONDS.", arg1, arg2, arg3);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_untick)
+{
+	delete_node_with_wild(ses, LIST_TICKER, arg);
+
+	return ses;
+}
+
+
+// checked in update.c
+