//***************************************************************************** // // editor.c // // one of the problems that came up with the origional text editor what that // it started becoming rather kludgy and hackish as we started adding more // features to it, like script editing capabilities. It was difficult to // coordinate when we wanted certain options and when we didn't. This is an // attempt to make things a bit easier to scale up with. // //***************************************************************************** #include "../mud.h" #include "../utils.h" #include "../auxiliary.h" #include "../socket.h" #include "editor.h" //***************************************************************************** // auxiliary data for sockets //***************************************************************************** typedef struct editor_aux_data { EDITOR *editor; // the editor we're using BUFFER *buf; // the buffer we're editing BUFFER *working_buf; // the buffer where we do our work } EDITOR_AUX_DATA; EDITOR_AUX_DATA *newEditorAuxData() { EDITOR_AUX_DATA *data = malloc(sizeof(EDITOR_AUX_DATA)); bzero(data, sizeof(EDITOR_AUX_DATA)); return data; // the buffer, working buffer, and editor are null 'til needed } void deleteEditorAuxData(EDITOR_AUX_DATA *data) { // if we have a working buf, free it. Don't touch anything else. that // stuff will be needed by the rest of the program if(data->working_buf) deleteBuffer(data->working_buf); free(data); } void clearEditorAuxData(EDITOR_AUX_DATA *data) { if(data->working_buf) deleteBuffer(data->working_buf); data->working_buf = NULL; data->editor = NULL; data->buf = NULL; } //***************************************************************************** // local structures, functions, and defines //***************************************************************************** // a basic text editor for use by other modules EDITOR *text_editor = NULL; // an editor for dialog. Essentially, text without newlines EDITOR *dialog_editor = NULL; struct editor_data { HASHTABLE *cmds; // mappings from commands to their functions and descs void (* prompt)(SOCKET_DATA *sock); // the prompt display void (* init)(SOCKET_DATA *sock); // reset variables for using the editor void (* append)(SOCKET_DATA *sock, char *arg, BUFFER *buf); // append text to the buffer }; // data for a command in the editor typedef struct command_data { // the function this command calls void (* func)(SOCKET_DATA *sock, char *arg, BUFFER *buf); char *desc; // the one-line helpfile description bool reserved; // is this command protected from being written over? } ECMD_DATA; ECMD_DATA * newEditorCommand(const char *desc, void func(SOCKET_DATA *sock, char *arg, BUFFER *buf), bool reserved) { ECMD_DATA *cmd = malloc(sizeof(ECMD_DATA)); cmd->desc = strdup(desc ? desc : ""); cmd->func = func; cmd->reserved = reserved; return cmd; } void deleteEditorCommand(ECMD_DATA *cmd) { if(cmd->desc) free(cmd->desc); free(cmd); } // // the default prompt display for editors // void editorDefaultPrompt(SOCKET_DATA *sock) { text_to_buffer(sock, "] "); } // // the default header display for editors // void editorDefaultHeader(SOCKET_DATA *sock) { text_to_buffer(sock, "==========================================================================\r\n" "Begin editing. /q on a new line to quit, /a to abort. /h for help \r\n" "==========================================================================\r\n" ); } // // the default function for appending text to the working buffer // void editorDefaultAppend(SOCKET_DATA *sock, char *arg, BUFFER *buf) { bufferCat(buf, arg); bufferCat(buf, "\r\n"); } // // the function for appending text to a dialog buffer. // void editorDialogAppend(SOCKET_DATA *sock, char *arg, BUFFER *buf) { // if the buffer isn't empty, cat a space if(bufferLength(buf) > 0) bufferCat(buf, " "); bufferCat(buf, arg); } // // The function that takes in a new command and figures out what to // do with it // void editorInputHandler(SOCKET_DATA *sock, char *arg) { EDITOR_AUX_DATA *data = socketGetAuxiliaryData(sock, "editor_aux_data"); // is it a command? if(*arg == '/') { // separate the command and the argument char buf[SMALL_BUFFER]; arg = one_arg(arg, buf); // pull up the command ECMD_DATA *cmd = hashGet(data->editor->cmds, buf+1); if(cmd == NULL) text_to_buffer(sock, "Invalid command.\r\n"); else cmd->func(sock, arg, data->working_buf); } else data->editor->append(sock, arg, data->working_buf); } //***************************************************************************** // implementation of basic editor commands //***************************************************************************** void editorQuit(SOCKET_DATA *sock, char *arg, BUFFER *buf) { // save the current changes EDITOR_AUX_DATA *data = socketGetAuxiliaryData(sock, "editor_aux_data"); bufferCopyTo(buf, data->buf); clearEditorAuxData(data); text_to_buffer(sock, "Saved and quit.\r\n"); // and then pop the input handler socketPopInputHandler(sock); } void editorAbort(SOCKET_DATA *sock, char *arg, BUFFER *buf) { EDITOR_AUX_DATA *data = socketGetAuxiliaryData(sock, "editor_aux_data"); clearEditorAuxData(data); text_to_buffer(sock, "Editor aborted.\r\n"); socketPopInputHandler(sock); } void editorDisplayHelp(SOCKET_DATA *sock, char *arg, BUFFER *buf) { const char *key = NULL; ECMD_DATA *val = NULL; HASH_ITERATOR *hash_i = newHashIterator(socketGetEditor(sock)->cmds); // print out all of the commands and their descriptions ITERATE_HASH(key, val, hash_i) send_to_socket(sock, "/%-3s %s\r\n", key, val->desc); deleteHashIterator(hash_i); } void editorDeleteLine(SOCKET_DATA *sock, char *arg, BUFFER *buf) { char tmp[SMALL_BUFFER]; arg = one_arg(arg, tmp); int line = atoi(tmp); if(!isdigit(*tmp) || !bufferRemove(buf, line)) text_to_buffer(sock, "Line does not exist.\r\n"); else text_to_buffer(sock, "Line deleted.\r\n"); } void editorEditLine(SOCKET_DATA *sock, char *arg, BUFFER *buf) { char tmp[SMALL_BUFFER]; arg = one_arg(arg, tmp); int line = atoi(tmp); if(!isdigit(*tmp) || !bufferReplaceLine(buf, arg, line)) text_to_buffer(sock, "Line does not exist.\r\n"); else text_to_buffer(sock, "Line replaced.\r\n"); } void editorInsertLine(SOCKET_DATA *sock, char *arg, BUFFER *buf) { char tmp[SMALL_BUFFER]; arg = one_arg(arg, tmp); int line = atoi(tmp); if(!isdigit(*tmp) || !bufferInsert(buf, arg, line)) text_to_buffer(sock, "Insertion failed.\r\n"); else text_to_buffer(sock, "Line inserted.\r\n"); } void editorListDialogBuffer(SOCKET_DATA *sock, char *arg, BUFFER *buf) { if(*bufferString(buf)) send_to_socket(sock, "%s\r\n", bufferString(buf)); } void editorListBuffer(SOCKET_DATA *sock, char *arg, BUFFER *buf) { if(*bufferString(buf)) text_to_buffer(sock, bufferString(buf)); } void editorReplace(SOCKET_DATA *sock, char *arg, BUFFER *buf, bool all) { char *a; char *b; if(count_letters(arg, '\'', strlen(arg)) != 4) { text_to_buffer(sock, "arguments must take the form: 'to replace' 'replacement'\r\n"); return; } // pull the first argument a = strtok(arg, "\'"); if(a == NULL) { text_to_buffer(sock, "format is: /r[a] 'to replace' 'replacement'\r\n"); return; } // kill the leading the leading ' of b strtok(NULL, "\'"); b = strtok(NULL, "\'"); if(b == NULL) b = "\0"; // "\0" will get deleted at the end of this block int replaced = bufferReplace(buf, a, b, all); send_to_socket(sock, "%d occurence%s of '%s' replaced with '%s'.\r\n", replaced, (replaced == 1 ? "" : "s"), a, b); } void editorReplaceString(SOCKET_DATA *sock, char *arg, BUFFER *buf) { editorReplace(sock, arg, buf, FALSE); } void editorReplaceAllString(SOCKET_DATA *sock, char *arg, BUFFER *buf) { editorReplace(sock, arg, buf, TRUE); } void editorClear(SOCKET_DATA *sock, char *arg, BUFFER *buf) { bufferClear(buf); text_to_buffer(sock, "Buffer cleared.\r\n"); } void editorFormatBuffer(SOCKET_DATA *sock, char *arg, BUFFER *buf) { bufferFormat(buf, SCREEN_WIDTH, PARA_INDENT); text_to_buffer(sock, "Buffer formatted.\r\n"); } //***************************************************************************** // implementation of editor.h //***************************************************************************** void init_editor() { // install the editor components auxiliariesInstall("editor_aux_data", newAuxiliaryFuncs(AUXILIARY_TYPE_SOCKET, newEditorAuxData, deleteEditorAuxData, NULL, NULL, NULL, NULL)); text_editor = newEditor(); dialog_editor = newEditor(); editorSetAppend(dialog_editor, editorDialogAppend); editorRemoveCommand(dialog_editor, "f"); editorAddCommand(dialog_editor, "l", " List the current buffer contents", editorListDialogBuffer); } EDITOR *newEditor() { EDITOR *editor = malloc(sizeof(EDITOR)); // set up the default commands editor->cmds = newHashtable(); hashPut(editor->cmds, "q", newEditorCommand(" Quit editor and save changes", editorQuit, TRUE)); hashPut(editor->cmds, "a", newEditorCommand(" Quit editor and don't save", editorAbort, TRUE)); hashPut(editor->cmds, "h", newEditorCommand(" Display editor commands", editorDisplayHelp, TRUE)); hashPut(editor->cmds, "c", newEditorCommand(" Clear the contents of the buffer", editorClear, TRUE)); hashPut(editor->cmds, "l", newEditorCommand(" List the current buffer contents", editorListBuffer, FALSE)); hashPut(editor->cmds, "d", newEditorCommand("# Delete line with the specified number", editorDeleteLine, FALSE)); hashPut(editor->cmds, "e", newEditorCommand("# <txt> Sets the text at the specified line to the new text", editorEditLine, FALSE)); hashPut(editor->cmds, "i", newEditorCommand("# <txt> Insert new text at the specified line number", editorInsertLine, FALSE)); hashPut(editor->cmds, "f", newEditorCommand(" Formats your text into a paragraph", editorFormatBuffer, FALSE)); hashPut(editor->cmds, "r", newEditorCommand("'a' 'b' replace first occurence of 'a' with 'b'", editorReplaceString, FALSE)); hashPut(editor->cmds, "ra", newEditorCommand("'a' 'b' repalce all occurences of 'a' with 'b'", editorReplaceAllString, FALSE)); // set up the default prompt, header, and appending function editor->prompt = editorDefaultPrompt; editor->append = editorDefaultAppend; editor->init = NULL; return editor; } void editorSetPrompt(EDITOR *editor, void prompt(SOCKET_DATA *sock)) { editor->prompt = prompt; } void editorSetInit(EDITOR *editor, void init(SOCKET_DATA *sock)) { editor->init = init; } void editorSetAppend(EDITOR *editor, void append(SOCKET_DATA *sock, char *arg, BUFFER *buf)) { editor->append = append; } void editorAddCommand(EDITOR *editor, const char *cmd, const char *desc, void func(SOCKET_DATA *sock, char *arg, BUFFER *buf)) { ECMD_DATA *old_cmd = hashGet(editor->cmds, cmd); // make sure we're not trying to replace a reserved command if(!old_cmd || !old_cmd->reserved) { hashPut(editor->cmds, cmd, newEditorCommand(desc, func, FALSE)); if(old_cmd) deleteEditorCommand(old_cmd); } } void editorRemoveCommand(EDITOR *editor, const char *cmd) { ECMD_DATA *old_cmd = hashGet(editor->cmds, cmd); // make sure the command isn't reserved if(old_cmd && !old_cmd->reserved) { hashRemove(editor->cmds, cmd); deleteEditorCommand(old_cmd); } } void socketStartEditor(SOCKET_DATA *sock, EDITOR *editor, BUFFER *buf) { EDITOR_AUX_DATA *data = socketGetAuxiliaryData(sock, "editor_aux_data"); data->working_buf = bufferCopy(buf); data->buf = buf; data->editor = editor; if(editor->init) editor->init(sock); editorDefaultHeader(sock); // if we have a "list" command, execute it. Otherwise, cat the buf ECMD_DATA *list = NULL; if((list = hashGet(editor->cmds, "l")) != NULL) list->func(sock, "", buf); else text_to_buffer(sock, bufferString(buf)); socketPushInputHandler(sock, editorInputHandler, editor->prompt); } EDITOR *socketGetEditor(SOCKET_DATA *sock) { EDITOR_AUX_DATA *data = socketGetAuxiliaryData(sock, "editor_aux_data"); return data->editor; }