//DMW /* F1 will return to progs.src F2 will try to open a file with the name of which is on that line. (excluding comments/tabs). Needs conditions. F3 will give a prompt for typing in a value name to see the value. F4 will save F5 will run (unbreak). F6 will list the stack. F7 will compile. F8 will move execution F9 will set a break point. F10 will apply code changes. F11 will step through. */ #include "quakedef.h" #ifdef TEXTEDITOR cvar_t alloweditor = {"alloweditor", "1"}; //disallow loading editor for stepbystep debugging. cvar_t editstripcr = {"edit_stripcr", "1"}; //remove \r from eols (on load). cvar_t editaddcr = {"edit_addcr", "1"}; //make sure that each line ends with a \r (on save). cvar_t edittabspacing = {"edit_tabsize", "4"}; #undef pr_trace progfuncs_t *editprogfuncs; typedef struct fileblock_s { struct fileblock_s *next; struct fileblock_s *prev; int allocatedlength; int datalength; int flags; char *data; } fileblock_t; #define FB_BREAK 1 fileblock_t *cursorblock, *firstblock, *executionblock, *viewportystartblock; void *E_Malloc(int size) { char *mem; mem = Z_Malloc(size); if (!mem) Sys_Error("Failed to allocate enough mem for editor\n"); return mem; } void E_Free(void *mem) { Z_Free(mem); } #define GETBLOCK(s, ret) ret = (void *)E_Malloc(sizeof(fileblock_t) + s);ret->allocatedlength = s;ret->data = (char *)ret + sizeof(fileblock_t) char OpenEditorFile[256]; qboolean editoractive; //(export) qboolean editormodal; //doesn't return. (export) qboolean editorblocking; qboolean madechanges; qboolean insertkeyhit; qboolean useeval; char evalstring[256]; int executionlinenum; //step by step debugger int cursorlinenum, cursorx; int viewportx; int viewporty; //newsize = number of chars, EXCLUDING terminator. void MakeNewSize(fileblock_t *block, int newsize) //this is used to resize a block. It allocates a new one, copys the data frees the old one and links it into the right place //it is called when the user is typing { fileblock_t *newblock; newsize = (newsize + 4)&~3; //We allocate a bit extra, so we don't need to keep finding a new block for each and every character. if (block->allocatedlength >= newsize) return; //Ignore. This block is too large already. Don't bother resizeing, cos that's pretty much pointless. GETBLOCK(newsize, newblock); memcpy(newblock->data, block->data, block->datalength); newblock->prev = block->prev; newblock->next = block->next; if (newblock->prev) newblock->prev->next = newblock; if (newblock->next) newblock->next->prev = newblock; newblock->datalength = block->datalength; newblock->flags = block->flags; E_Free(block); if (firstblock == block) firstblock = newblock; if (cursorblock == block) cursorblock = newblock; } int positionacross; void GetCursorpos(void) { int a; char *s; int ts = edittabspacing.value; if (ts < 1) ts = 4; for (a=0,positionacross=0,s=cursorblock->data;a < cursorx && *s;s++,a++) { if (*s == '\t') { positionacross += ts; positionacross -= cursorx%ts; } else positionacross++; } // positionacross = cursorofs; } void SetCursorpos(void) { int a=0; char *s; int ts = edittabspacing.value; if (ts < 1) ts = 4; for (cursorx=0,s=cursorblock->data;cursorx < positionacross && *s;s++,a++) { if (*s == '\t') { cursorx += ts; cursorx -= cursorx%ts; } else cursorx++; } cursorx=a; //just in case if (cursorx > cursorblock->datalength) cursorx = cursorblock->datalength; } void CloseEditor(void) { fileblock_t *b; key_dest = key_console; editoractive = false; if (!firstblock) return; OpenEditorFile[0] = '\0'; for (b = firstblock; b;) { firstblock = b; b=b->next; E_Free(firstblock); } madechanges = false; firstblock = NULL; executionlinenum = -1; } qboolean EditorSaveFile(char *s) //returns true if succesful { // FILE *F; fileblock_t *b; int len=0; int pos=0; char *data; for (b = firstblock; b; b = b->next) //find total length required. { len += b->datalength; len+=2; //extra for \n } data = Hunk_TempAlloc(len); for (b = firstblock; b; b = b->next) //find total length required. { memcpy(data + pos, b->data, b->datalength); pos += b->datalength; if (editaddcr.value) { data[pos]='\r'; pos++; } data[pos]='\n'; pos++; } COM_WriteFile(s, data, len); /* F = fopen(s, "wt"); if (!F) return false; for (b = firstblock; b; b = b->next) { fprintf(F, "%s\n", b->data); } fclose(F); */ madechanges = false; executionlinenum = -1; return true; } void EditorNewFile() { GETBLOCK(64, firstblock); GETBLOCK(64, firstblock->next); firstblock->next->prev = firstblock; cursorblock = firstblock; cursorlinenum = 0; cursorx = 0; viewportystartblock = NULL; madechanges = true; executionlinenum = -1; key_dest = key_editor; editoractive = true; } void EditorOpenFile(char *name) { int i; char line[8192]; int len, flen, pos=0; FILE *F; fileblock_t *b; CloseEditor(); strcpy(OpenEditorFile, name); if ((flen=COM_FOpenFile(OpenEditorFile, &F)) == -1) { sprintf(OpenEditorFile, "src/%s", name); if ((flen=COM_FOpenFile(OpenEditorFile, &F)) == -1) { F = fopen(OpenEditorFile, "rb"); if (F) flen = COM_filelength(F); else { Con_Printf("Couldn't open file \"%s\"\nA new file will be created\n", name); strcpy(OpenEditorFile, name); key_dest = key_console; EditorNewFile(); return; } } } i=1; while(pos < flen) { len = 0; for(;;) { if (pos+len >= flen) break; line[len] = fgetc(F); if (line[len] == '\n') break; len++; } pos+=len; pos++; //and a \n if (editstripcr.value) { if (line[len-1] == '\r') len--; } b = firstblock; GETBLOCK(len+1, firstblock); firstblock->prev = b; if (b) b->next = firstblock; firstblock->datalength = len; memcpy(firstblock->data, line, len); if (svprogfuncs) if (svprogfuncs->ToggleBreak(svprogfuncs, OpenEditorFile+strlen(com_gamedir)+1, i, 3)) { firstblock->flags |= FB_BREAK; } i++; } if (firstblock == NULL) { GETBLOCK(10, firstblock); } else for (; firstblock->prev; firstblock=firstblock->prev); fclose(F); cursorblock = firstblock; cursorx = 0; viewportystartblock = NULL; madechanges = false; executionlinenum = -1; key_dest = key_editor; editoractive = true; } void Editor_Key(int key) { int i; if (keybindings[key][0]) if (!strcmp(keybindings[key][0], "toggleconsole")) { key_dest = key_console; return; } /* if (CmdAfterSave) { switch (key) { case 'Y': case 'y': if (!EditorSaveFile(OpenEditorFile)) { Con_Printf("Couldn't save file \"%s\"\n", OpenEditorFile); key_dest = key_console; } else if (!CmdAfterSaveCalled) { CmdAfterSaveCalled=true; (*CmdAfterSave)(); CmdAfterSaveCalled=false; } CmdAfterSave = NULL; break; case 'N': case 'n': (*CmdAfterSave)(); CmdAfterSave = NULL; break; case 'C': case 'c': CmdAfterSave = NULL; break; } return; } */ if (key == K_SHIFT) return; if (useeval && key != K_F11) { switch(key) { case K_ESCAPE: useeval = false; break; case K_DEL: evalstring[0] = '\0'; break; case K_BACKSPACE: i = strlen(evalstring); if (i < 1) break; evalstring[i-1] = '\0'; break; default: i = strlen(evalstring); evalstring[i] = key; evalstring[i+1] = '\0'; break; } return; } /* if (ctrl_down && (key == 'c' || key == K_INS)) key = K_F9; if ((ctrl_down && key == 'v') || (shift_down && key == K_INS)) key = K_F10; */ switch (key) { case K_SHIFT: break; case K_ALT: break; case K_CTRL: break; case K_PGUP: GetCursorpos(); {int a=(vid.height/8)/2; while(a) {a--; if (cursorblock->prev) { cursorblock = cursorblock->prev; cursorlinenum--; } } } SetCursorpos(); break; case K_PGDN: GetCursorpos(); {int a=(vid.height/8)/2; while(a) { a--; if (cursorblock->next) { cursorblock = cursorblock->next; cursorlinenum++; } } } SetCursorpos(); break; // case K_BACK: case K_F1: // Editor_f(); break; // case K_FORWARD: case K_F2: { char file[1024]; char *s; Q_strncpyz(file, cursorblock->data, sizeof(file)); s = file; while (*s) { if ((*s == '/' && s[1] == '/') || (*s == '\t')) { *s = '\0'; break; } s++; } if (*file) EditorOpenFile(file); } break; case K_F3: useeval = true; break; case K_F4: EditorSaveFile(OpenEditorFile); break; case K_F5: editormodal = false; if (editprogfuncs) *editprogfuncs->pr_trace = false; break; case K_F6: if (editprogfuncs) editprogfuncs->PR_StackTrace(editprogfuncs); break; case K_F7: EditorSaveFile(OpenEditorFile); Cbuf_AddText("compile\n", RESTRICT_LOCAL); break; case K_F8: executionlinenum = cursorlinenum; executionblock = cursorblock; break; case K_F9: { int f = 0; #ifndef CLIENTONLY if (svprogfuncs) { if (svprogfuncs->ToggleBreak(svprogfuncs, OpenEditorFile+strlen(com_gamedir)+1, cursorlinenum, 2)) f |= 1; else f |= 2; } #endif if (f & 1) cursorblock->flags |= FB_BREAK; else cursorblock->flags &= ~FB_BREAK; } break; case K_F10: EditorSaveFile(OpenEditorFile); Cbuf_AddText("applycompile\n", RESTRICT_LOCAL); break; case K_F11: editormodal = false; break; // case K_STOP: case K_ESCAPE: CloseEditor(); break; case K_HOME: cursorx = 0; break; case K_END: cursorx = cursorblock->datalength; break; case K_LEFTARROW: cursorx--; if (cursorx < 0) cursorx = 0; break; case K_RIGHTARROW: cursorx++; if (cursorx > cursorblock->datalength) cursorx = cursorblock->datalength; break; case K_MWHEELUP: case K_UPARROW: GetCursorpos(); if (cursorblock->prev) { cursorblock = cursorblock->prev; cursorlinenum--; } SetCursorpos(); break; case K_MWHEELDOWN: case K_DOWNARROW: GetCursorpos(); if (cursorblock->next) { cursorblock = cursorblock->next; cursorlinenum++; } SetCursorpos(); break; case K_BACKSPACE: cursorx--; if (cursorx < 0) { fileblock_t *b = cursorblock; if (b == firstblock) //no line above to remove to { cursorx=0; break; } cursorlinenum-=1; madechanges = true; cursorblock = b->prev; MakeNewSize(cursorblock, b->datalength + cursorblock->datalength+5); cursorx = cursorblock->datalength; memcpy(cursorblock->data + cursorblock->datalength, b->data, b->datalength); cursorblock->next = b->next; if (b->next) b->next->prev = cursorblock; // cursorblock->prev->next = cursorblock->next; // cursorblock->next->prev = cursorblock->prev; E_Free(b); // cursorblock = b; break; } case K_DEL: //bksp falls through. { int a; fileblock_t *b; cursorlinenum=-1; madechanges = true; //FIXME: does this work right? if (!cursorblock->datalength && cursorblock->next && cursorblock->prev) //blank line { b = cursorblock; if (b->next) b->next->prev = b->prev; if (b->prev) b->prev->next = b->next; if (cursorblock->next) cursorblock = cursorblock->next; else cursorblock = cursorblock->prev; E_Free(b); } else { for (a = cursorx; a < cursorblock->datalength;a++) cursorblock->data[a] = cursorblock->data[a+1]; cursorblock->datalength--; } if (cursorx >= cursorblock->datalength) cursorx = cursorblock->datalength-1; } break; case K_ENTER: { fileblock_t *b = cursorblock; cursorlinenum=-1; madechanges = true; GETBLOCK(strlen(b->data+cursorx), cursorblock); cursorblock->next = b->next; cursorblock->prev = b; b->next = cursorblock; if (cursorblock->next) cursorblock->next->prev = cursorblock; if (cursorblock->prev) cursorblock->prev->next = cursorblock; strcpy(cursorblock->data, b->data+cursorx); b->data[cursorx] = '\0'; cursorx = 0; } break; case K_INS: insertkeyhit = insertkeyhit?false:true; break; default: if ((key < ' ' || key > 127) && key != '\t') //we deem these as unprintable break; if (insertkeyhit) //insert a char { char *s; madechanges = true; MakeNewSize(cursorblock, cursorblock->datalength+5); //allocate a bit extra, so we don't need to keep resizing it and shifting loads a data about s = cursorblock->data + cursorblock->datalength; while (s >= cursorblock->data + cursorx) { s[1] = s[0]; s--; } cursorx++; cursorblock->datalength++; *(s+1) = key; } else //over write a char { MakeNewSize(cursorblock, cursorblock->datalength+5); //not really needed cursorblock->data[cursorx] = key; cursorx++; } break; } } void Draw_CursorLine(int ox, int y, fileblock_t *b) { int x=0; qbyte *d = b->data; int cx; int a = 0, i; int colour=0; int ts = edittabspacing.value; if (ts < 1) ts = 4; ts*=8; if (b->flags & (FB_BREAK)) colour = 1; //red if (executionblock == b) { if (colour) //break point too colour = 2; //green else colour = 3; //yellow } if (cursorx <= strlen(d)+1 && (int)(realtime*4.0) & 1) cx = -1; else cx = cursorx; for (i = 0; i < b->datalength; i++) { if (*d == '\t') { if (a == cx) Draw_ColouredCharacter (x+ox, y, 11); x+=ts; x-=x%ts; d++; a++; continue; } if (x+ox< vid.width) { if (a == cx) Draw_ColouredCharacter (x+ox, y, 11); else Draw_ColouredCharacter (x+ox, y, (int)*d | (colour<<8)); } d++; x += 8; a++; } if (a == cx) Draw_ColouredCharacter (x+ox, y, 11); } void Draw_NonCursorLine(int x, int y, fileblock_t *b) { int nx = 0; qbyte *d = b->data; int i; int colour=0; int ts = edittabspacing.value; if (ts < 1) ts = 4; ts*=8; if (b->flags & (FB_BREAK)) colour = 1; //red if (executionblock == b) { if (colour) //break point too colour = 2; //green else colour = 3; //yellow } for (i = 0; i < b->datalength; i++) { if (*d == '\t') { nx+=ts; nx-=nx%ts; d++; continue; } if (x+nx < vid.width) Draw_ColouredCharacter (x+nx, y, (int)*d | (colour<<8)); d++; nx += 8; } } fileblock_t *firstline(void) { int lines; fileblock_t *b; lines = (vid.height/8)/2-1; b = cursorblock; if (!b) return NULL; while (1) { if (!b->prev) return b; b = b->prev; lines--; if (lines <= 0) return b; } return NULL; } void Editor_Draw(void) { int x; int y; fileblock_t *b; if (key_dest != key_console) key_dest = key_editor; if ((editoractive && cls.state == ca_disconnected) || editormodal) Draw_EditorBackground (vid.height); if (cursorlinenum < 0) //look for the cursor line num { cursorlinenum = 0; for (b = firstblock; b; b=b->next) { cursorlinenum++; if (b == cursorblock) break; } } if (!viewportystartblock) //look for the cursor line num { y = 0; for (viewportystartblock = firstblock; viewportystartblock; viewportystartblock=viewportystartblock->prev) { y++; if (y == viewporty) break; } } x=0; for (y = 0; y < cursorx; y++) { if (cursorblock->data[y] == '\0') break; else if (cursorblock->data[y] == '\t') { x+=32; x&=~31; } else x+=8; } x=-x + vid.width/2; if (x > 0) x = 0; if (madechanges) Draw_Character (vid.width - 8, 0, '!'|128); if (!insertkeyhit) Draw_Character (vid.width - 16, 0, 'O'|128); Draw_String(0, 0, va("%6i:%4i:%s", cursorlinenum, cursorx+1, OpenEditorFile)); if (useeval) { if (!editprogfuncs) useeval = false; else { Draw_String(0, 8, evalstring); Draw_String(vid.width/2, 8, editprogfuncs->EvaluateDebugString(editprogfuncs, evalstring)); } y = 16; } else y=8; b = firstline(); for (; b; b=b->next) { if (b == cursorblock) Draw_CursorLine(x, y, b); else Draw_NonCursorLine(x, y, b); y+=8; if (y > vid.height) break; } /* if (CmdAfterSave) { if (madechanges) { M_DrawTextBox (0, 0, 36, 5); M_PrintWhite (16, 12, OpenEditorFile); M_PrintWhite (16, 28, "Do you want to save the open file?"); M_PrintWhite (16, 36, "[Y]es/[N]o/[C]ancel"); } else { if (!CmdAfterSaveCalled) { CmdAfterSaveCalled = true; (*CmdAfterSave) (); CmdAfterSaveCalled = false; } CmdAfterSave = NULL; } } */ } int QCLibEditor(char *filename, int line, int nump, char **parms) { if (editormodal || !developer.value) return line; //whoops if (!strncmp(OpenEditorFile, "src/", 4)) { if (!editoractive || strcmp(OpenEditorFile+4, filename)) { if (editoractive) EditorSaveFile(OpenEditorFile); EditorOpenFile(filename); } } else { if (!editoractive || strcmp(OpenEditorFile, filename)) { if (editoractive) EditorSaveFile(OpenEditorFile); EditorOpenFile(filename); } } for (cursorlinenum = 1, cursorblock = firstblock; cursorlinenum < line && cursorblock->next; cursorlinenum++) cursorblock=cursorblock->next; executionblock = cursorblock; if (!parms) { editormodal = true; while(editormodal && editoractive) { key_dest = key_editor; scr_disabled_for_loading=false; SCR_UpdateScreen(); Sys_SendKeyEvents(); NET_Sleep(100, false); //any os. } editormodal = false; } return line; } void Editor_f(void) { if (editoractive) EditorSaveFile(OpenEditorFile); EditorOpenFile(Cmd_Argv(1)); // EditorNewFile(); } void Editor_Init(void) { Cmd_AddCommand("edit", Editor_f); Cvar_Register(&alloweditor, "Text editor"); Cvar_Register(&editstripcr, "Text editor"); Cvar_Register(&editaddcr, "Text editor"); Cvar_Register(&edittabspacing, "Text editor"); } #endif