/* 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);
}