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