/*
** 1998-09-25 -	Here's a complete rewrite of the command subsystem, with a far more
**		flexible and fun architecture behind it. Some parts will undboubtely
**		be copied from the old code, but much is brand new. Fun.
** 1998-10-21 -	Moved the huge native command initialization function in here.
** 1999-04-04 -	Completely redid handling of builtin ("native") commands. Now accomodates
**		support for command-specifig configuration data, and stuff.
*/

#include "gentoo.h"

#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "commands.h"
#include "errors.h"
#include "strutil.h"
#include "dirpane.h"
#include "dialog.h"
#include "fileutil.h"
#include "cfg_gui.h"		/* For the cfg_gui() function ("Configure" built-in). */

#include "children.h"
#include "cmdparse.h"
#include "cmdgrab.h"
#include "cmdarg.h"
#include "cmdseq.h"

/* ----------------------------------------------------------------------------------------- */

static const gchar	*row_type_name[] = { "Built-In", "External" };
static GMemChunk	*builtin_chunk = NULL;

/* This describes a single built-in command. The 'cmd' field is the actual command execution
** function, like cmd_mkdir or cmd_activateother. The 'cfg' field is the corresponding commands's
** configuration function, it is called to establish the connection between the command's
** internal configuration data and the cmdseq_config module which handles loading / saving.
** Most built-in commands don't have any config data; they will have a NULL 'cfg' pointer.
*/
typedef struct {
	Command		cmd;
	CommandCfg	cfg;
} CmdDesc;

/* ----------------------------------------------------------------------------------------- */

void csq_add_builtin(MainInfo *min, const gchar *name, Command cmd, CommandCfg cmd_cfg)
{
	CfgInfo	*cfg = &min->cfg;

	if(cfg->commands.builtin == NULL)
		cfg->commands.builtin = g_hash_table_new(g_str_hash, g_str_equal);
	if(builtin_chunk == NULL)
		builtin_chunk = g_mem_chunk_new("CmdDesc", sizeof(CmdDesc), 64, G_ALLOC_ONLY);
	if((cfg->commands.builtin != NULL) && (builtin_chunk != NULL))
	{
		CmdDesc	*cdesc;

		if((cdesc = g_mem_chunk_alloc(builtin_chunk)) != NULL)
		{
			cdesc->cmd = cmd;
			cdesc->cfg = cmd_cfg;
			g_hash_table_insert(cfg->commands.builtin, (gpointer) name, cdesc);
			if(cmd_cfg != NULL)
				cmd_cfg(min);
		}
	}
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-26 -	Copy characters one by one from <src> to <dst>, while doing some special
**		operations to make the result "more suitable" as a command sequence name.
**		These operations include stripping out whitespace and almost all other
**		non-alphanumerical letters.
*/
void csq_cmdseq_filter_name(gchar *dst, const gchar *src)
{
	gchar	*od = dst;

	if((dst != NULL) && (src != NULL))
	{
		for(; *src != '\0' && dst - od < CSQ_NAME_MAX - 1; src++)
		{
			if(isalnum((int) *src) || *src == '_')
				*dst++ = *src;
		}
		*dst = '\0';
	}
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-27 -	A callback for g_hash_table; find maximum X in all UnnamedX-keys. OK? */
static void check_name(gpointer key, gpointer data, gpointer user)
{
	gint	here, *iptr = (gint *) user;

	if((strcmp((char *) key, "Unnamed") == 0) && iptr != NULL && *iptr == -1)
		*iptr = 0;
	else if(sscanf((char *) key, "Unnamed_%d", &here) == 1)
	{
		if(iptr != NULL && here > *iptr)
			*iptr = here;
	}
}

/* 1998-09-27 -	Construct (and return a pointer to) a somewhat neutral name, with the
**		interesting property that it is unique among all keys of <hash>.
*/
const gchar * csq_cmdseq_unique_name(GHashTable *hash)
{
	static gchar	buf[CSQ_NAME_MAX];
	gint		index = -1;

	if(hash != NULL)
	{
		g_hash_table_foreach(hash, check_name, &index);
		if(index == -1)
			return "Unnamed";
		g_snprintf(buf, sizeof buf, "Unnamed_%d", index + 1);
		return buf;
	}
	return NULL;
}

/* ----------------------------------------------------------------------------------------- */

/* 1999-05-22 -	Emit a warning message, and return <to>. */
static const gchar * map_name(const gchar *from, const gchar *to, const gchar *context)
{
	fprintf(stderr, "**Notice: Reference to obsolete command '%s' replaced by '%s' in %s\n", from, to, context);

	return to;
}

/* 1999-05-22 -	Map a command sequence name, if necessary. This should be called by all code
**		that loads cmdseq names, from config files and stuff. It should not be used
**		on input directly from the user, since that might be very annoying. :) The
**		point of the function is to create a central location where old obsolete
**		command names can be translated into their modern equivalents.
**		  This function is not written for speed.
*/
const gchar * csq_cmdseq_map_name(const gchar *name, const gchar *context)
{
	/* Ignore NULL and names beginning with a lower case letter (user-defined). */
	if((name == NULL) || (islower((int) *name)))
		return name;

	if(strcmp(name, "FileDefault") == 0)
		return map_name(name, "FileAction", context);
	else if(strcmp(name, "FileView") == 0)
		return map_name(name, "FileAction action=View", context);
	else if(strcmp(name, "FileEdit") == 0)
		return map_name(name, "FileAction action=Edit", context);
	else if(strcmp(name, "FilePrint") == 0)
		return map_name(name, "FileAction action=Print", context);
	else if(strcmp(name, "FilePlay") == 0)
		return map_name(name, "FileAction action=Play", context);
	else if(strcmp(name, "ViewTextHex") == 0)
		return map_name(name, "ViewText mode=Hex", context);
	else if(strcmp(name, "ViewTextOrHex") == 0)
		return map_name(name, "ViewText mode=Auto", context);

	return name;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-25 -	Create a new command sequence of the given name. The sequence is created
**		empty, i.e. with no rows attached.
*/
CmdSeq * csq_cmdseq_new(const gchar *name, guint32 flags)
{
	CmdSeq	*cs;

	if((cs = malloc(sizeof *cs)) != NULL)
	{
		str_strncpy(cs->name, name, sizeof cs->name);
		cs->flags = flags;
		cs->rows = NULL;
	}
	return cs;
}

/* 1998-09-26 -	Create a "deep" (non-memory-sharing) copy of <src>, and return it. */
CmdSeq * csq_cmdseq_copy(CmdSeq *src)
{
	CmdSeq	*dst = NULL;
	CmdRow	*row;
	GList	*iter;

	if((dst = malloc(sizeof *dst)) != NULL)
	{
		str_strncpy(dst->name, src->name, sizeof dst->name);
		dst->flags = src->flags;
		dst->rows = NULL;
		for(iter = src->rows; iter != NULL; iter = g_list_next(iter))
		{
			if((row = csq_cmdrow_copy((CmdRow *) iter->data)) != NULL)
				dst->rows = g_list_append(dst->rows, row);
			else
			{
				csq_cmdseq_destroy(dst);
				return NULL;
			}
		}
	}
	return dst;
}

/* 1998-09-25 -	Set the name of a command sequence. Takes care to filter it first. This
**		is somewhat more complex than it might seem, since it needs to rehash
**		the sequence.
*/
void csq_cmdseq_set_name(GHashTable *hash, CmdSeq *cs, char *name)
{
	if(hash != NULL && cs != NULL && name != NULL)
	{
		g_hash_table_remove(hash, (gpointer) cs->name);
		csq_cmdseq_filter_name(cs->name, name);
		if(cs->name[0] == '\0' || g_hash_table_lookup(hash, (gpointer) cs->name) != NULL)
			str_strncpy(cs->name, csq_cmdseq_unique_name(hash), sizeof cs->name);
		csq_cmdseq_hash(&hash, cs);
	}
}

/* 1998-09-25 -	Insert given command <cs> into hash table at <*hash>. The hash table is
**		created if it doesn't already exist.
*/
void csq_cmdseq_hash(GHashTable **hash, CmdSeq *cs)
{
	if(hash != NULL)
	{
		if(*hash == NULL)
			*hash = g_hash_table_new(g_str_hash, g_str_equal);
		if(*hash != NULL)
			g_hash_table_insert(*hash, cs->name, (gpointer) cs);
	}
}

/* 1998-09-25 -	Just a callback for g_list_foreach(). */
static void row_destroy(gpointer d, gpointer u)
{
	csq_cmdrow_destroy((CmdRow *) d);
}

/* 1998-09-25 -	Destroy a command sequence, freing all memory used by it (that includes
**		the memory occupied by the rows, of course.
*/
void csq_cmdseq_destroy(CmdSeq *cs)
{
	if(cs != NULL)
	{
		g_list_foreach(cs->rows, row_destroy, NULL);
		free(cs);
	}
}

/* 1998-09-25 -	Append a row to the given command sequence. Returns the index of the
**		newly appended row (i.e. the length of the list minus one).
*/
gint csq_cmdseq_row_append(CmdSeq *cs, CmdRow *row)
{
	if(cs != NULL && row != NULL)
	{
		cs->rows = g_list_append(cs->rows, (gpointer) row);
		return g_list_length(cs->rows) - 1;
	}
	return -1;
}

/* 1998-09-27 -	Move the <row> (which must already be part of <cs>) either one row up
**		(<delta> == -1) or down (1). The row will wrap around at the top and
**		bottom positions, since I think that's fun. :) Returns new index of
**		the row.
*/
gint csq_cmdseq_row_move(CmdSeq *cs, CmdRow *row, gint delta)
{
	GList	*link;
	gint	pos, np;

	if(cs != NULL && row != NULL && (delta == -1 || delta == 1))
	{
		link = g_list_find(cs->rows, (gpointer) row);
		pos  = g_list_position(cs->rows, link);
		cs->rows = g_list_remove(cs->rows, (gpointer) row);
		np = pos + delta;
		if(np < 0)
			np = g_list_length(cs->rows);
		else if(np > (gint) g_list_length(cs->rows))
			np = 0;
		cs->rows = g_list_insert(cs->rows, (gpointer) row, np);
		return np;
	}
	return -1;
}

/* 1998-09-25 -	Remove given row from command sequence. Returns the index of the
**		item following the deleted one (or the one before if deleting the
**		last).
*/
gint csq_cmdseq_row_delete(CmdSeq *cs, CmdRow *row)
{
	gint	pos;

	if(cs != NULL && row != NULL)
	{
		pos = g_list_index(cs->rows, (gpointer) row);
		cs->rows = g_list_remove(cs->rows, (gpointer) row);
		if(pos >= (gint) g_list_length(cs->rows))
			return pos - 1;
		return pos;
	}
	return -1;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-25 -	Execute a built-in command. Currently very simple.
** 1999-05-06 -	Rewritten, now handles command arguments. Very nice.
*/
static int execute_builtin(MainInfo *min, const char *cmd)
{
	gchar	msg[CSQ_NAME_MAX + 64];
	gchar	*spc, **argv;
	CmdDesc	*cdesc;

	if((spc = strchr(cmd, ' ')) != NULL)		/* Does command string contain space, and thus arguments? */
	{
		if((argv = cpr_parse(min, cmd)) != NULL)
		{
			if((cdesc = (CmdDesc *) g_hash_table_lookup(min->cfg.commands.builtin, argv[0])) != NULL)
			{
				CmdArg	*ca;
				DirPane	*src = min->gui->cur_dir;
				gint	ret;

				ca = car_create(argv);
				ret = cdesc->cmd(min, src, dp_mirror(min, src), ca);
				car_destroy(ca);
				return ret;
			}
			g_snprintf(msg, sizeof msg, "Unable to execute unknown\ncommand \"%s\".", cmd);
			dlg_dialog_error(msg);
		}
		return 1;
	}
	else
	{
		if((cdesc = (CmdDesc *) g_hash_table_lookup(min->cfg.commands.builtin, cmd)) != NULL)
		{
			DirPane	*src = min->gui->cur_dir;

			return cdesc->cmd(min, src, dp_mirror(min, src), NULL);
		}
	}
	g_snprintf(msg, sizeof msg, "Unable to execute unknown\ncommand \"%s\".", cmd);
	dlg_dialog_error(msg);

	return 0;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-26 -	Handle a set of before or after flags. */
void csq_handle_ba_flags(MainInfo *min, guint32 flags)
{
	DirPane	*src = min->gui->cur_dir, *dst = dp_mirror(min, src);

	if(flags & CBAF_RESCAN_SOURCE)
		dp_rescan_dir(src);
	if(flags & CBAF_RESCAN_DEST)
		dp_rescan_dir(dst);
	if(flags & CBAF_CD_SOURCE)
		fut_cd(src->dir.path, NULL, 0);
	else if(flags & CBAF_CD_DEST)
		fut_cd(dst->dir.path, NULL, 0);
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-05-27 -	Do the actual fork() call, and execution of external command. Put in a separate routine
**		to make handling of pre-command dialogues easier (GTK+ - the library that calls back).
** 1998-05-28 -	Major bug fix: when waiting for a synchronous child, we really DO want to hang (so, no WNOHANG).
** 1998-05-30 -	Fixed up the error-checking after waitpid(), which was completely broken. If command fails
**		(i.e. returns non-zero exit status), we now detect that and inform the user. In a perfect
**		world, we would like to have access to the child's 'errno' at its time of failure... Also
**		shut down the child's file descriptors, to avoid it flooding the original shell with output.
**		This is a temporary solution; I would like the stdin/out/err behaviour to be configurable.
** 1998-07-04 -	Um... It seems that I'm not exactly king at programming with processes and stuff, since the
**		exit status check was completely broken. I blame the man-page for waitpid(2), which uses
**		language not compatible with my brain. :) It seems WIFEXITED() returns non-zero (meaning the
**		child exited normally), even if the child failed. I mis-parsed that one.
** 1998-09-10 -	Massively extended and rewritten. Now supports output capturing, and always runs stuff asynch.
** 1998-09-11 -	Detected a race condition here; if the child terminates before we register its pid, there's
**		a chance that the SIGCHLD signal handler hasn't got the data it needs to properly handle
**		the child's death. To fix this, we generate a SIGCHLD when we know that the data is there.
** 1998-09-25 -	Moved into the new cmdseq module, and adapted accordingly.
** 1999-05-29 -	Added pre-fork() protection through new call in fileutil module. Very nice.
*/
static int fork_and_execute(MainInfo *min, CmdRow *row, char **argv)
{
	pid_t	child;
	int	estat, fd_out[2], fd_err[2];
	CX_Ext	*ext = &row->extra.external;

	if(!fut_locate_executable(g_getenv("PATH"), argv[0]))
	{
		err_set(min, errno, "Execute", argv[0]);
		err_show(min);
		return 0;
	}

	if(ext->gflags & CGF_KILLPREV)
		chd_kill_child(argv[0]);

	if(!cgr_grab_init(min, row, fd_out, fd_err))
		return 0;

	csq_handle_ba_flags(min, ext->baflags[0]);

	if((child = fork()) >= 0)
	{
		if(child == 0)		/* In child process? */
		{
			cgr_set_child_pipes(min, row, fd_out, fd_err);
			estat = execvp(argv[0], argv);
			perror("execvp() returned");
			exit(EXIT_FAILURE);
		}
		else			/* In parent. */
		{
			chd_register(argv[0], child, ext->gflags, ext->baflags[1]);
			cgr_set_parent_pipes(min, row, argv[0], child, fd_out, fd_err);
			kill(getpid(), SIGCHLD);		/* Resolves race condition? */
		}
	}
	err_show(min);
	return errno == 0;
}

/* 1998-09-25 -	Execute an external command. */
static int execute_external(MainInfo *min, CmdRow *row)
{
	gchar	**argv;

	if((argv = cpr_parse(min, row->def->str)) != NULL)
	{
		fork_and_execute(min, row, argv);
		cpr_free(argv);
	}
	return 1;
}

/* 1998-09-25 -	Execute a single row, returning success or failure. */
static int execute_row(MainInfo *min, CmdRow *row)
{
	switch(row->type)
	{
		case CRTP_BUILTIN:
			return execute_builtin(min, row->def->str);
		case CRTP_EXTERNAL:
			return execute_external(min, row);
		default:
			fprintf(stderr, "**CMDSEQ: Unknown type for row!\n");
	}
	return 1;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-25 -	Figure out whether running <row> results in a block (i.e. no further rows are
**		executed until <row> has completed, or not.
*/
static int row_is_blocking(MainInfo *min, CmdRow *row)
{
	switch(row->type)
	{
		case CRTP_BUILTIN:		/* Built-ins never block. */
			return 0;
		case CRTP_EXTERNAL:		/* Externals may or may not block. */
			return (row->extra.external.gflags & CGF_RUNINBG) ? 0 : 1;
		default:
			;
	}
	return 0;
}

/* 1998-09-25 -	Execute a command sequence. This, of course, is sort of the most important
**		functionality in this module, the core that makes the rest worth having.
*/
static int cmdseq_execute(MainInfo *min, CmdSeq *cs)
{
	GList	*iter;
	CmdRow	*row;
	int	ok = 0, block, i;

	if(cs == NULL)
		return 0;

	err_clear(min);
	for(iter = cs->rows, i = 0; iter != NULL; iter = g_list_next(iter), i++)
	{
		row = (CmdRow *) iter->data;
		block = row_is_blocking(min, row);
		if((ok = execute_row(min, row)) != 0)
		{
			if(block)
			{
				chd_set_running(cs, i + 1);
				break;
			}
		}
		else
			break;
	}
	return ok;
}

/* 1998-09-25 -	Execute a command. The <name> parameter is somewhat magic: if it is the name of
**		an existing sequence (multi-row aggregate command), that sequence is executed.
**		Otherwise, if the name is that of a built-in command, the built-in is run!
**		This duality provides a nice, clean way of running a known built-in without the
**		overhead of a named sequence just to contain that single built-in.
** 1998-10-04 -	Now ignores empty (and NULL) command names.
*/
gint csq_execute(MainInfo *min, const gchar *name)
{
	CmdSeq	*cs;

	if(name == NULL || *name == '\0')
		return 0;

	if((min->cfg.commands.cmdseq != NULL) && ((cs = (CmdSeq *) g_hash_table_lookup(min->cfg.commands.cmdseq, name)) != NULL))
		return cmdseq_execute(min, cs);
	return execute_builtin(min, name);
}

/* 1998-09-25 -	Continue execution of a command.
** 1999-03-08 -	Added support for the repeat flag.
*/
void csq_continue(MainInfo *min)
{
	CmdSeq	*cs;
	CmdRow	*row;
	GList	*iter = NULL;
	guint	index;

	if((cs = chd_get_running(&index)) != NULL)
	{
		for(iter = g_list_nth(cs->rows, index); iter != NULL; iter = g_list_next(iter))
		{
			chd_set_running(cs, ++index);
			row = (CmdRow *) iter->data;
			if(row_is_blocking(min, row))
			{
				if(execute_row(min, row))
					return;
				else
					break;
			}
			if(!execute_row(min, row))
				break;
		}
	}
	chd_clear_running();

	/* Did we successfully run the entire sequence, and is the repeat mode on, and is there a selection? */
	if((cs != NULL) && (iter == NULL) && (cs->flags & CSFLG_REPEAT) && (dp_has_selection(min->gui->cur_dir)))
		csq_execute(min, cs->name);

	min->gui->pane[0].dbclk_row = min->gui->pane[1].dbclk_row = -1;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-27 -	Convert a command row type identifier to a string. */
const gchar * csq_cmdrow_type_to_string(CRType type)
{
	if((type >= CRTP_BUILTIN) && (type < CRTP_NUM_TYPES))
		return row_type_name[type];
	return NULL;
}

/* 1998-09-27 -	Convert a string to a type identifier, if possible. */
CRType csq_cmdrow_string_to_type(const gchar *type)
{
	guint	i;

	for(i = 0; i < sizeof row_type_name / sizeof row_type_name[0]; i++)
	{
		if(strcmp(row_type_name[i], type) == 0)
			return (CRType) i;
	}
	return -1;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-25 -	Create a new command sequence row with the given type, definition, and flags. */
CmdRow * csq_cmdrow_new(CRType type, const gchar *def, guint32 flags)
{
	CmdRow		*row;
	const gchar	*def2;

	row = g_malloc(sizeof *row);
	row->type  = type;
	row->flags = flags;
	memset(&row->extra, 0, sizeof row->extra);
	if(row->type == CRTP_BUILTIN)
		def2 = csq_cmdseq_map_name(def, "command row");
	else
		def2 = def;
	if((row->def = g_string_new(def2)) != NULL)
		return row;
	g_free(row);
	return NULL;
}

/* 1998-09-26 -	Create and return a verbatim copy of <src>. The copy is "deep"; i.e. there is
**		zero sharing of memory between the original and the copy.
*/
CmdRow * csq_cmdrow_copy(CmdRow *src)
{
	CmdRow	*dst;

	if((dst = malloc(sizeof *dst)) != NULL)
	{
		dst->type  = src->type;
		dst->flags = src->flags;
		memcpy(&dst->extra, &src->extra, sizeof dst->extra);
		if((dst->def = g_string_new(src->def->str)) != NULL)
			return dst;
		free(dst);
	}
	return NULL;
}

/* 1998-09-27 -	Set a new command type for the given command row. */
void csq_cmdrow_set_type(CmdRow *row, CRType type)
{
	if(row != NULL)
	{
		row->type = type;
		memset(&row->extra, 0, sizeof row->extra);
	}
}

/* 1998-09-27 -	Change the definition of the given row. */
void csq_cmdrow_set_def(CmdRow *row, const gchar *def)
{
	if(row != NULL && def != NULL)
		g_string_assign(row->def, def);
}

/* 1998-09-25 -	Destroy a command sequence row. */
void csq_cmdrow_destroy(CmdRow *row)
{
	if(row != NULL)
	{
		if(row->def != NULL)
			g_string_free(row->def, TRUE);
		free(row);
	}
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-10-21 -	Init built-in commands. Any new commands written must be added to the
**		hash table by adding a line in here. This might not be elegant, but it works.
** 1999-04-04 -	Completely rewritten, since we now handle config data for commands.
*/
void csq_init_commands(MainInfo *min)
{
	min->cfg.commands.builtin = NULL;

	csq_add_builtin(min, "DirEnter",	cmd_direnter,		NULL);
	csq_add_builtin(min, "DirFromOther", 	cmd_fromother,		NULL);
	csq_add_builtin(min, "DirToOther", 	cmd_toother,		NULL);
	csq_add_builtin(min, "DirParent",	cmd_parent,		NULL);
	csq_add_builtin(min, "DirPrevious",	cmd_dirprevious,	NULL);
	csq_add_builtin(min, "DirRescan",	cmd_rescan,		NULL);
	csq_add_builtin(min, "DirSwap",		cmd_swap,		NULL);

	csq_add_builtin(min, "DpHide",		cmd_dphide,		NULL);
	csq_add_builtin(min, "DpRecenter",	cmd_dprecenter,		NULL);
	csq_add_builtin(min, "DpFocus",		cmd_dpfocus,		cfg_dpfocus);

	csq_add_builtin(min, "ActivateOther",	cmd_activateother,	NULL);
	csq_add_builtin(min, "ActivateLeft",	cmd_activateleft,	NULL);
	csq_add_builtin(min, "ActivateRight",	cmd_activateright,	NULL);
	csq_add_builtin(min, "ActivatePush",	cmd_activatepush,	NULL);
	csq_add_builtin(min, "ActivatePop",	cmd_activatepop,	NULL);

	csq_add_builtin(min, "Copy",		cmd_copy,		cfg_copy);
	csq_add_builtin(min, "CopyAs", 		cmd_copyas,		NULL);
	csq_add_builtin(min, "Clone",		cmd_clone,		NULL);
	csq_add_builtin(min, "Move",		cmd_move,		NULL);
	csq_add_builtin(min, "MoveAs",		cmd_moveas,		NULL);
	csq_add_builtin(min, "Delete",		cmd_delete,		NULL);
	csq_add_builtin(min, "Rename",		cmd_rename,		NULL);
	csq_add_builtin(min, "ChMod",		cmd_chmod,		NULL);
	csq_add_builtin(min, "ChOwn",		cmd_chown,		NULL);
	csq_add_builtin(min, "Split",		cmd_split,		NULL);
	csq_add_builtin(min, "MkDir",		cmd_mkdir,		cfg_mkdir);
	csq_add_builtin(min, "GetSize",		cmd_getsize,		cfg_getsize);
	csq_add_builtin(min, "ClearSize",	cmd_clearsize,		NULL);
	csq_add_builtin(min, "SymLink",		cmd_symlink,		NULL);
	csq_add_builtin(min, "SymLinkAs",	cmd_symlinkas,		NULL);
	csq_add_builtin(min, "SymLinkClone",	cmd_symlinkclone,	NULL);
	csq_add_builtin(min, "SymLinkEdit",	cmd_symlinkedit,	NULL);

	csq_add_builtin(min, "ViewText",	cmd_viewtext,		cfg_viewtext);

	csq_add_builtin(min, "FileAction",	cmd_fileaction,		NULL);

	csq_add_builtin(min, "SelectAll",	cmd_selectall,		NULL);
	csq_add_builtin(min, "SelectNone",	cmd_selectnone,		NULL);
	csq_add_builtin(min, "SelectToggle",	cmd_selecttoggle,	NULL);
	csq_add_builtin(min, "SelectRE",	cmd_selectre,		NULL);
	csq_add_builtin(min, "SelectExt",	cmd_selectext,		NULL);
	csq_add_builtin(min, "SelectType",	cmd_selecttype,		NULL);
	csq_add_builtin(min, "UnselectFirst",	cmd_unselectfirst,	NULL);

	csq_add_builtin(min, "Information",	cmd_information,	cfg_information);

	csq_add_builtin(min, "About",		cmd_about,		NULL);
	csq_add_builtin(min, "Run",		cmd_run,		NULL);
	csq_add_builtin(min, "Configure",	cmd_configure,		NULL);
	csq_add_builtin(min, "ConfigureSave",	cmd_configuresave,	NULL);
	csq_add_builtin(min, "Quit", 		cmd_quit,		NULL);
}
