tmuck2.4/
tmuck2.4/admin/scripts/
tmuck2.4/docs/
tmuck2.4/minimal-db/
tmuck2.4/minimal-db/data/
tmuck2.4/minimal-db/logs/
tmuck2.4/minimal-db/muf/
tmuck2.4/old/
tmuck2.4/src/
tmuck2.4/src/compile/
tmuck2.4/src/editor/
tmuck2.4/src/game/
tmuck2.4/src/interface/
tmuck2.4/src/scripts/
tmuck2.4/src/utilprogs/
/* Copyright (c) 1992 by David Moore.  All rights reserved. */
/* edit.c,v 2.8 1994/03/10 04:13:34 dmoore Exp */
#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "db.h"
#include "params.h"
#include "buffer.h"
#include "text.h"
#include "externs.h"
#include "editor.h"		/* Generated by mkcommands.sh */


/* player, program, array of strings (args) */
typedef void (*comm_func)(const dbref, const dbref, const char **);

struct command {
    const char *name;
    int min_match;
    comm_func func;
};

static struct command editcmds[] = {
#include "editortab.h"		/* Generated by mkcommandtab.sh */
};


struct edit_info {
    dbref program;		/* Program dbref. */
    dbref player;		/* Person editing the program. */
    struct text *text;		/* Text associated with it. */
};


static int commandcmp(const void *k, const void *d)
{
    const char *key = k;
    const struct command *data = d;

    /* Make uncase sensitive. */
    return muck_strnicmp(key, data->name, data->min_match);
}


static comm_func lookup_command(const char *key)
{
    struct command *result;

    result = bsearch(key, editcmds, sizeof(editcmds)/sizeof(struct command),
		     sizeof(struct command), commandcmp);
    if (result && muck_strprefix(result->name, key)) {
	return result->func;
    } else {
	return 0;
    }
}


/* Hash table of programs currently being edited. */
static hash_tab *being_edited = NULL;


/* Hash table routines.  Maps a program # -> text. */
static unsigned long hash_edit_entry(const void *entry)
{
    const struct edit_info *info = entry;

    return dbref2long(info->program);
}


static int compare_edit_entries(const void *entry1, const void *entry2)
{
    const struct edit_info *info1 = entry1;
    const struct edit_info *info2 = entry2;

    return (info1->program != info2->program);
}


static void delete_edit_entry(void *entry)
{
    struct edit_info *info = entry;

    FreeEditPrg(info->player);	/* Mark this player as program going byebye. */

    SetOffFlag(info->program, EDIT_LOCK);

    free_text(info->text);
    FREE(info);
}


void init_editor(void)
{
    if (being_edited) return;

    being_edited = init_hash_table("Editor table", compare_edit_entries,
			     hash_edit_entry, delete_edit_entry,
			     EDIT_HASH_SIZE);
}


void clear_editor(void)
{
    clear_hash_table(being_edited);
    being_edited = NULL;
}


static struct text *get_text(const dbref program)
{
    struct edit_info match;
    const struct edit_info *result;

    match.program = program;
    result = find_hash_entry(being_edited, &match);
    
    if (!result) {
	/* This really shouldn't ever happen. */
	log_status("EDITOR: program not in hash table!");
	return NULL;
    }

    return result->text;
}


static void make_edit_text(const dbref player, const dbref program, struct text *text)
{
    struct edit_info *info;

    
    MALLOC(info, struct edit_info, 1);

    info->program = program;
    info->player = player;
    info->text = text;
    add_hash_entry(being_edited, info);
}


static void write_program(const dbref program)
{
    write_text(make_progname(program), get_text(program));
}


struct text *read_program(const dbref program)
{
    if (Typeof(program) != TYPE_PROGRAM) return NULL;

    return read_text(make_progname(program));
}


static int parse_line_numbers(const dbref player, const dbref program, const int max_lines, const char * const *args, int *start, int *end)
{
    if (!args || !args[0] || !*args[0]) {
	*start = GetCurrLine(program);
	*end = *start;
    } else {
	*start = atoi(args[0]);

	if (!args[1] || !*args[1]) {
	    *end = *start;
	} else {
	    *end = atoi(args[1]);
	}
    }


    /* Check for -1 (special for last line), and overflow. */
    if ((*start == -1) || (*start > max_lines)) *start = max_lines;
    if ((*end == -1) || (*end > max_lines)) *end = max_lines;

    /* Check for legality. */
    if ((*start < 0) || (*end < 0)) {
	notify(player, "Negative arguments not allowed.");
	return 0;		/* Error. */
    }

    if (*start > *end) {
	notify(player, "Arguments don't make sense!");
	return 0;		/* Error. */
    }

    return 1;			/* Was ok. */
}


static void edit_def_macro(const dbref player, const char *macro)
{
    Buffer name;
    Buffer body;
    const char *p;

    /* Skip leading space. */
    for (p = macro; *p && isspace(*p); p++);

    /* Grab the name. */
    Bufcpy(&name, "");
    while (*p && !isspace(*p)) Bufcat_char(&name, *p++);

    /* Skip anymore space. */
    while (*p && isspace(*p)) p++;

    /* Grab the body, and nuke trailing spaces. */
    Bufcpy(&body, p);
    Bufstw(&body);

    /* Install it. */
    if (!new_macro(Buftext(&name), Buftext(&body), player)) {
	notify(player, "Oopsie!  That macro's already been defined.");
    } else {
	notify(player, "Entry created.");
    }
}


static void edit_insert_line(const dbref player, const dbref program, const char *line, int len)
{
    struct text *text;
    int lineno;

    if (!strcmp(line, EXIT_INSERT)) {
	SetOffFlag(player, INSERT_MODE);
	notify(player, "Exiting insert mode.");
	return;
    }

    text = get_text(program);	/* Text of program. */
    lineno = GetCurrLine(program); /* Property on program. */
    /* muf pre-insert -> text post-insert, so use lineno-1 */
    insert_text(text, line, len, lineno - 1);
    SetCurrLine(program, lineno + 1);
}


static void edit_insert(const dbref player, const dbref program, const char **args)
{
    int lineno, ignore;
    struct text *text;

    text = get_text(program);

    if (!parse_line_numbers(player, program, text_total_lines(text) + 1,
			    args, &lineno, &ignore))
	return;

    if (lineno == 0) {
	/* Emulate old bug where inserting at 0 is treated like end. */
	lineno = text_total_lines(text) + 1;
    }

    SetCurrLine(program, lineno);
    SetOnFlag(player, INSERT_MODE);

    notify(player, "Entering insert mode.");
}


static void edit_delete(const dbref player, const dbref program, const char **args)
{
    int start, end;
    struct text *text;

    text = get_text(program);

    if (!parse_line_numbers(player, program, text_total_lines(text),
			    args, &start, &end))
	return;

    if (start == 0) {
	notify(player, "No line to delete!");
	return;
    }

    SetCurrLine(program, start);
    delete_text(text, start, end);
    notify(player, "%d lines deleted.", end - start + 1);
}


static void edit_quit_internal(const dbref player, const dbref program)
{
    struct edit_info match;

    SetOffFlag(player, INSERT_MODE); /* In case he was inserting. */
    SetOffFlag(player, IN_EDITOR);

    if (program != NOTHING) {
	/* Program hasn't already been recycled.  Write it, and clean up. */
	write_program(program);
       
	SetOffFlag(program, EDIT_LOCK);

	match.program = program;
	clear_hash_entry(being_edited, &match);
    }
}


static void edit_quit(const dbref player, const dbref program, const char **ignore)
{
    edit_quit_internal(player, program);
    notify(player, "Editor exited.");
}


/* This gives you an external means of kicking a player out of the editor. */
void edit_quit_external(const dbref player)
{
	edit_quit_internal(player, GetEditPrg(player));
}


void edit_quit_recycle(const dbref program)
{
    struct edit_info match;

    match.program = program;
    clear_hash_entry(being_edited, &match);
}


static void edit_compile(const dbref player, const dbref program, const char **ignore)
{
    compile_text(player, program, get_text(program));
    notify(player, "Compiler done.");
}


static void edit_list(const dbref player, const dbref program, const char **args)
{
    int i, start, end;
    int ignore;			/* Holds length of each line being shown. */
    struct text *text;
    const char *line;

    text = get_text(program);

    if (!parse_line_numbers(player, program, text_total_lines(text),
			    args, &start, &end))
	return;			/* Some error w/ the line numbers. */

    if (start == 0) {
	notify(player, "Line not available for display.");
	return;
    }

    for (i = start; i <= end ; i++) {
	line = text_line(text, i, &ignore);
	if (HasFlag(player, LINE_NUMBERS)) {
	    /* Fix this back to %3d later. */
	    notify(player, "%d: %s", i, line);
	} else {
	    notify(player, "%s", line);
	}
    }

    i = end - start + 1;
    if (i > 1)
	notify(player, "%d lines displayed.", i);
}


static void edit_kill_macro(const dbref player, const dbref program, const char **args)
{
    if (!macro_expansion(args[0])) {
	notify(player, "Entry to remove not found.");
    } else if (kill_macro(player, args[0])) {
	notify(player, "Entry removed.");
    } else {
	notify(player, "I'm sorry, Dave, I can't let you do that.");
    }
}


static void edit_show_macros(const dbref player, const dbref program, const char **args)
{
    if (!args[0]) {
	show_macros("!", "~", 1, player);
    } else {
	show_macros(args[0], args[1], 1, player);
    }
}


static void edit_show_abbrev(const dbref player, const dbref program, const char **args)
{
    if (!args[0]) {
	show_macros("!", "~", 0, player);
    } else {
	show_macros(args[0], args[1], 0, player);
    }
}


static void edit_view(const dbref player, const dbref ign_prog, const char **args)
{
    int i;
    dbref program;
    const char *line;
    struct text *text;
    int ignore;

    if (!args[0]) {
	notify(player,
	       "I don't understand which header you're trying to look at.");
        return;
    }

    program = parse_dbref(args[0]);
    
    if (program < 0 || program >= db_top || Typeof(program) != TYPE_PROGRAM) {
	notify(player, "That isn't a program.");
	return;
    }

    if (!(controls(player, program, Wizard(player)) || Linkable(program))) {
	notify(player, "That's not a public program.");
	return;
    }
    
    text = read_program(program);

    /* Loop printing lines as long as they start w/ comment character. */
    for (i = 1; ; i++) {
	line = text_line(text, i, &ignore);
	if (!line || (*line != BEGINCOMMENT))
	    break;

	notify(player, line);
    }

    notify(player, "Done.");

    free_text(text);
}


static void edit_unassemble(const dbref player, const dbref program, const char **ignore)
{
    disassemble(player, program);
}


static void edit_linenos(const dbref player, const dbref program, const char **ignore)
{
    if (HasFlag(player, LINE_NUMBERS)) {
	SetOffFlag(player, LINE_NUMBERS);
	notify(player, "Line numbers off.");
    } else {
	SetOnFlag(player, LINE_NUMBERS);
	notify(player, "Line numbers on.");
    }
}


static void edit_help(const dbref player, const dbref program, const char **ignore)
{
  help(player, "editor-dict", MAN_FILE);
}


void begin_editing(const dbref player, const dbref program, const int list)
{
    if (HasFlag(program, EDIT_LOCK)) {
	notify(player, "Sorry, this program is currently being edited.  Try again later.");
	return;
    }

    if (HasFlag(player, IN_EDITOR)) {
	/* This shouldn't happen! */
	notify(player, "You are already editing a program.");
	return;
    }
    
    SetOnFlag(player, IN_EDITOR);
    SetEditPrg(player, program);

    SetOnFlag(program, EDIT_LOCK);

    make_edit_text(player, program, read_program(program));

    notify(player, "Entering editor.");
    if (list) { 
	const char *dummy_args[MAX_ARG];
	int i;

	for (i = 0; i < MAX_ARG; i++) dummy_args[i] = "";
	
	edit_list(player, program, dummy_args);
    }
}


static const char *parse_args(const char *input, const char **args)
{
    int i;
    char *command;
    char *p;
    static Buffer buf;

    /* Clear out the arg array. */
    for (i = 0; i < MAX_ARG; i++) {
	args[i] = "";
    }

    Bufcpy(&buf, input);
    Bufstw(&buf);		/* Strip any trailing whitespace. */

    /* Loop backwards from the end of the buffer trying to find the
       command name. */
    i = Buflen(&buf) - 1;	/* Can't be 0.  exit editor() if empty str. */
    command = Buftext(&buf) + i;
    while (i > 0 && !isspace(*command)) {
	command--;
	i--;
    }
    if (!isspace(*command)) {
	/* No arguments, just the command, return now. */
	return command;
    }

    /* Overshot by one, correct for it.  And stick in a NUL.*/
    *command++ = '\0';

    /* Collect up the arguments. */
    p = Buftext(&buf);
    for (i = 0; i < MAX_ARG; i++) {
	if (!*p) {
	    /* If arg is empty, then set it to previous arg. */
	    /* Note, can never get in here unless there is at least 1. */
	    args[i] = args[i-1];
	} else {
	    /* Set arg to start at current spot. */
	    args[i] = p;

	    /* Move up p past this arg, and stick in a NUL. */
	    while (*p && !isspace(*p)) p++;
	    if (*p) *p++ = '\0';
	    while (*p && isspace(*p)) p++; /* Skip whitespace. */
	}
    }

    return command;

}


/* External routine called in game.c whenever a player with an IN_EDITOR
   bit types something. */
void editor(const dbref player, const char *command, int len)
{
    comm_func func;
    const char *args[MAX_ARG];
    dbref program;

    program = GetEditPrg(player);
    if (program == NOTHING) {
	notify(player, "The program you were editing has been recycled.");
/*
	if (!muck_stricmp(GetName(player, "Jingoro")))
	    notify(player, "Dithering.");
*/
	edit_quit(player, NOTHING, NULL);
	return;
    }

    if (HasFlag(player, INSERT_MODE)) {
	/* The player is inserting lines. */
	edit_insert_line(player, program, command, len);
	return;
    }

    /* Eat some leading whitespace before we move on. */
    while (*command && isspace(*command)) command++;

    if (!*command)
	return;			/* Empty input, just return. */

    /* Special case handling of 'def' editor command. */
    if (muck_strprefix(command, "def ")) {
	edit_def_macro(player, command + 4); /* Skip 'def '. */
	return;
    }

    /* Divy up the command arguments. */
    command = parse_args(command, args);

    func = lookup_command(command);

    if (!func) {
	notify(player, "Illegal editor command.");
	return;
    }

    func(player, program, args);
}


void do_edit_list(const dbref player, const dbref program, const char **args)
{
    int i, start, end;
    struct text *text;
    const char *line;
    int ignore;

    text = read_program(program);

    if (!parse_line_numbers(player, program, text_total_lines(text),
			    args, &start, &end))
	return;			/* Some error w/ the line numbers. */

    if (start == 0) {
	notify(player, "Line not available for display.");
	return;
    }

    for (i = start; i <= end ; i++) {
	line = text_line(text, i, &ignore);
	if (HasFlag(player, LINE_NUMBERS)) {
	    /* Fix this back to %3d later. */
	    notify(player, "%d: %s", i, line);
	} else {
	    notify(player, "%s", line);
	}
    }

    i = end - start + 1;
    if (i > 1)
	notify(player, "%d lines displayed.", i);

    free_text(text);
}