//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 static cvar_t alloweditor = SCVAR("alloweditor", "1"); //disallow loading editor for stepbystep debugging. static cvar_t editstripcr = SCVAR("edit_stripcr", "1"); //remove \r from eols (on load). static cvar_t editaddcr = SCVAR("edit_addcr", "1"); //make sure that each line ends with a \r (on save). static cvar_t edittabspacing = SCVAR("edit_tabsize", "4"); cvar_t debugger = SCVAR("debugger", "1"); #undef pr_trace static 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 static fileblock_t *cursorblock, *firstblock, *executionblock, *viewportystartblock; static 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; } static 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) static char OpenEditorFile[256]; qboolean editoractive; //(export) qboolean editormodal; //doesn't return. (export) static qboolean editorblocking; static qboolean madechanges; static qboolean insertkeyhit=true; static qboolean useeval; static char evalstring[256]; static int executionlinenum; //step by step debugger static int cursorlinenum, cursorx; static int viewportx; static int viewporty; static int VFS_GETC(vfsfile_t *fp) { unsigned char c; VFS_READ(fp, &c, 1); return c; } //newsize = number of chars, EXCLUDING terminator. static 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; } static int positionacross; static 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; } static 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; } static void CloseEditor(void) { fileblock_t *b; key_dest = key_console; editoractive = false; editprogfuncs = NULL; if (!firstblock) return; OpenEditorFile[0] = '\0'; for (b = firstblock; b;) { firstblock = b; b=b->next; E_Free(firstblock); } madechanges = false; editormodal = false; firstblock = NULL; executionlinenum = -1; } static 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.ival) { 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; } static void EditorNewFile(void) { 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; } static void EditorOpenFile(char *name) { int i; char line[8192]; int len, flen, pos=0; vfsfile_t *F; fileblock_t *b; CloseEditor(); strcpy(OpenEditorFile, name); if (!(F = FS_OpenVFS(OpenEditorFile, "rb", FS_GAME))) { Q_snprintfz(OpenEditorFile, sizeof(OpenEditorFile), "src/%s", name); if (!(F = FS_OpenVFS(OpenEditorFile, "rb", FS_GAME))) { 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; flen = VFS_GETLEN(F); while(pos < flen) { len = 0; for(;;) { if (pos+len >= flen || len > sizeof(line) - 16) break; line[len] = VFS_GETC(F); if (line[len] == '\n') break; len++; } pos+=len; pos++; //and a \n if (editstripcr.ival) { 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 (editprogfuncs) { if (editprogfuncs->ToggleBreak(editprogfuncs, OpenEditorFile, i, 3)) { firstblock->flags |= FB_BREAK; } } else { if (svprogfuncs) { if (svprogfuncs->ToggleBreak(svprogfuncs, OpenEditorFile, i, 3)) { firstblock->flags |= FB_BREAK; } } } i++; } if (firstblock == NULL) { GETBLOCK(10, firstblock); } else for (; firstblock->prev; firstblock=firstblock->prev); VFS_CLOSE(F); cursorblock = firstblock; cursorx = 0; viewportystartblock = NULL; madechanges = false; executionlinenum = -1; key_dest = key_editor; editoractive = true; } void Editor_Key(int key, int unicode) { 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 && key != K_F5) { switch(key) { case K_ESCAPE: if (editprogfuncs) *editprogfuncs->pr_trace = 0; useeval = false; break; case K_F3: 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: if (unicode) { i = strlen(evalstring); evalstring[i] = unicode; 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_MWHEELUP: case K_UPARROW: case K_PGUP: GetCursorpos(); { int a; if (key == K_PGUP) a =(vid.height/8)/2; else if (key == K_MWHEELUP) a = 5; else a = 1; while(a) { a--; if (cursorblock->prev) { cursorblock = cursorblock->prev; cursorlinenum--; } } } SetCursorpos(); break; case K_MWHEELDOWN: case K_DOWNARROW: case K_PGDN: GetCursorpos(); { int a; if (key == K_PGDN) a =(vid.height/8)/2; else if (key == K_MWHEELDOWN) a = 5; else a = 1; 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: if (editprogfuncs) useeval = true; break; case K_F4: EditorSaveFile(OpenEditorFile); break; case K_F5: /*stop debugging*/ editormodal = false; if (editprogfuncs) *editprogfuncs->pr_trace = false; break; case K_F6: if (editprogfuncs) PR_StackTrace(editprogfuncs); break; case K_F7: /*save+recompile*/ EditorSaveFile(OpenEditorFile); if (editprogfuncs) Cbuf_AddText("compile\n", RESTRICT_LOCAL); break; case K_F8: /*move execution point to here - I hope you move to the same function!*/ executionlinenum = cursorlinenum; executionblock = cursorblock; break; case K_F9: /*set breakpoint*/ { int f = 0; if (editprogfuncs) { if (editprogfuncs->ToggleBreak(editprogfuncs, OpenEditorFile, cursorlinenum, 2)) f |= 1; else f |= 2; } #ifndef CLIENTONLY else if (svprogfuncs) { if (svprogfuncs->ToggleBreak(svprogfuncs, OpenEditorFile, cursorlinenum, 2)) f |= 1; else f |= 2; } #endif if (f & 1) cursorblock->flags |= FB_BREAK; else cursorblock->flags &= ~FB_BREAK; } break; case K_F10: //save+apply changes, supposedly EditorSaveFile(OpenEditorFile); Cbuf_AddText("applycompile\n", RESTRICT_LOCAL); break; case K_F11: //single step editormodal = false; break; // case K_STOP: case K_ESCAPE: if (editprogfuncs) editprogfuncs->AbortStack(editprogfuncs); 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_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->datalength += 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; } break; case K_ENTER: { fileblock_t *b = cursorblock; cursorlinenum=-1; madechanges = true; GETBLOCK(b->datalength - 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; cursorblock->datalength = b->datalength - cursorx; memcpy(cursorblock->data, b->data+cursorx, cursorblock->datalength); b->datalength = cursorx; cursorx = 0; } break; case K_INS: insertkeyhit = insertkeyhit?false:true; break; default: if (unicode < ' ' && unicode != '\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) = unicode; } else //over write a char { MakeNewSize(cursorblock, cursorblock->datalength+5); //not really needed cursorblock->data[cursorx] = unicode; cursorx++; if (cursorx > cursorblock->datalength) cursorblock->datalength = cursorx; } break; } } static void Draw_Line(int x, int y, fileblock_t *b, int cursorx) { int nx = 0, nnx; qbyte *d = b->data; qbyte *c; int i; int colour=COLOR_WHITE; int ts = edittabspacing.value; if (cursorx >= 0) c = d + cursorx; else c = NULL; Font_BeginString(font_conchar, x, y, &x, &y); if (ts < 1) ts = 4; ts*=8; if (b->flags & (FB_BREAK)) colour = COLOR_RED; //red if (executionblock == b) { if (colour) //break point too colour = COLOR_GREEN; //green else colour = COLOR_YELLOW; //yellow } nx = x; for (i = 0; i < b->datalength; i++) { if (*d == '\t') { if (d == c) Font_DrawChar(nx, y, (int)11 | (CON_WHITEMASK)); nx+=ts; nx-=(nx - x)%ts; d++; continue; } if (nx < (int)vid.pixelwidth) nnx = Font_DrawChar(nx, y, (int)*d | (colour<= d) Font_DrawChar(nx, y, (int)11 | (CON_WHITEMASK)); Font_EndString(font_conchar); } static 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; int c; fileblock_t *b; if (key_dest != key_console) key_dest = key_editor; if ((editoractive && cls.state == ca_disconnected) || editormodal) R2D_EditorBackground(); 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_FunString (vid.width - 8, 0, "!"); if (!insertkeyhit) Draw_FunString (vid.width - 16, 0, "O"); Draw_FunString(0, 0, va("%6i:%4i:%s", cursorlinenum, cursorx+1, OpenEditorFile)); if (useeval) { if (!editprogfuncs) useeval = false; else { char *eq, *term; Draw_FunString(0, 8, evalstring); eq = strchr(evalstring, '='); if (eq) { term = strchr(eq, ';'); if (!term) term = strchr(eq, '\n'); if (!term) term = strchr(eq, '\r'); if (term) { *term = '\0'; eq = NULL; } else *eq = '\0'; } Draw_FunString(vid.width/2, 8, editprogfuncs->EvaluateDebugString(editprogfuncs, evalstring)); if (eq) *eq = '='; } y = 16; } else y=8; b = firstline(); for (; b; b=b->next) { c = -1; if (b == cursorblock) if ((int)(Sys_DoubleTime()*4.0) & 1) c = cursorx; Draw_Line(x, y, b, c); 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(progfuncs_t *prfncs, char *filename, int line, int nump, char **parms) { char *f1, *f2; if (editormodal || line < 0 || !debugger.ival) return line; //whoops if (qrenderer == QR_NONE) { int i; char buffer[8192]; char *r; vfsfile_t *f; if (line == -1) return -1; f = FS_OpenVFS(filename, "rb", FS_GAME); if (!f) Con_Printf("%s - %i\n", filename, line); else { for (i = 0; i < line; i++) { VFS_GETS(f, buffer, sizeof(buffer)); } if ((r = strchr(buffer, '\r'))) { r[0] = '\n';r[1]='\0';} Con_Printf("%s", buffer); VFS_CLOSE(f); } //PF_break(NULL); return line; } editprogfuncs = prfncs; f1 = OpenEditorFile; f2 = filename; if (!strncmp(f1, "src/", 4)) f1 += 4; if (!strncmp(f2, "src/", 4)) f2 += 4; if (!editoractive || strcmp(f1, f2)) { 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 && editprogfuncs) { // key_dest = key_editor; scr_disabled_for_loading=false; SCR_UpdateScreen(); Sys_SendKeyEvents(); S_ExtraUpdate(); NET_Sleep(100, false); //any os. } editormodal = false; } return line; } void Editor_ProgsKilled(progfuncs_t *dead) { if (editprogfuncs == dead) { editprogfuncs = NULL; editormodal = false; } } static void Editor_f(void) { if (Cmd_Argc() != 2) { Con_Printf("edit \n"); return; } editprogfuncs = NULL; useeval = false; 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"); Cvar_Register(&debugger, "Text editor"); } #endif