//***************************************************************************** // // 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" //***************************************************************************** // mandatory modules //***************************************************************************** #include "../scripts/scripts.h" #include "../scripts/pyplugs.h" //***************************************************************************** // auxiliary data for sockets //***************************************************************************** typedef struct editor_aux_data { EDITOR *editor; // the editor we're using BUFFER *buf; // the buffer we're editing (if any) BUFFER *working_buf; // the buffer where we do our work PyObject *py_complete; // python function to call on completion of editing void (* on_complete)(SOCKET_DATA *sock, const char *str); } 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); if(data->py_complete) { Py_DECREF(data->py_complete); } free(data); } void clearEditorAuxData(EDITOR_AUX_DATA *data) { if(data->working_buf) deleteBuffer(data->working_buf); if(data->py_complete) { Py_DECREF(data->py_complete); } data->working_buf = NULL; data->editor = NULL; data->buf = NULL; data->on_complete = NULL; data->py_complete = 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"); if(data->on_complete) data->on_complete(sock, bufferString(buf)); else if(data->py_complete) { PyObject *ret = PyObject_CallFunction(data->py_complete, "Os", socketGetPyFormBorrowed(sock), bufferString(buf)); if(ret == NULL) log_pyerr("Error quitting the buffer editor."); Py_XDECREF(ret); } //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 dflt_editor_complete(SOCKET_DATA *sock, const char *str) { EDITOR_AUX_DATA *data = socketGetAuxiliaryData(sock, "editor_aux_data"); bufferClear(data->buf); bufferCat(data->buf, str); } void begin_editor(SOCKET_DATA *sock) { EDITOR_AUX_DATA *data = socketGetAuxiliaryData(sock, "editor_aux_data"); if(data->editor->init) data->editor->init(sock); editorDefaultHeader(sock); // if we have a "list" command, execute it. Otherwise, cat the buf ECMD_DATA *list = NULL; if((list = hashGet(data->editor->cmds, "l")) != NULL) list->func(sock, "", data->working_buf); else text_to_buffer(sock, bufferString(data->working_buf)); socketPushInputHandler(sock, editorInputHandler, data->editor->prompt, "text editor"); } void socketStartPyEditorFunc(SOCKET_DATA *sock, EDITOR *editor,const char *dflt, void *py_complete) { EDITOR_AUX_DATA *data = socketGetAuxiliaryData(sock, "editor_aux_data"); data->working_buf = newBuffer(1); bufferCat(data->working_buf, dflt); data->editor = editor; data->py_complete = py_complete; Py_XINCREF(data->py_complete); begin_editor(sock); } void socketStartEditorFunc(SOCKET_DATA *sock, EDITOR *editor, const char *dflt, void (* on_complete)(SOCKET_DATA *, const char *)) { EDITOR_AUX_DATA *data = socketGetAuxiliaryData(sock, "editor_aux_data"); data->working_buf = newBuffer(1); bufferCat(data->working_buf, dflt); data->editor = editor; data->on_complete = on_complete; if(editor->init) editor->init(sock); begin_editor(sock); } void socketStartEditor(SOCKET_DATA *sock, EDITOR *editor, BUFFER *buf) { EDITOR_AUX_DATA *data = socketGetAuxiliaryData(sock, "editor_aux_data"); data->buf = buf; socketStartEditorFunc(sock, editor, bufferString(buf), dflt_editor_complete); } EDITOR *socketGetEditor(SOCKET_DATA *sock) { EDITOR_AUX_DATA *data = socketGetAuxiliaryData(sock, "editor_aux_data"); return data->editor; }