//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 step over. F11 will step into. */ #include "quakedef.h" #ifdef TEXTEDITOR #include "pr_common.h" #include "shader.h" //#if defined(ANDROID) || defined(SERVERONLY) #define debugger_default "0" //#else //#define debugger_default "1" //#endif static cvar_t editstripcr = CVARD("edit_stripcr", "1", "remove \\r from eols (on load)"); static cvar_t editaddcr = CVARD("edit_addcr", "", "make sure that each line ends with a \\r (on save). Empty will be assumed to be 1 on windows and 0 otherwise."); //static cvar_t edittabspacing = CVARD("edit_tabsize", "4", "How wide tab alignment is"); cvar_t pr_debugger = CVARAFD("pr_debugger", debugger_default, "debugger", CVAR_SAVE, "When enabled, QC errors and debug events will enable step-by-step tracing."); extern cvar_t pr_sourcedir; static pubprogfuncs_t *editprogfuncs; qboolean editoractive; //(export) console_t *editormodal; //doesn't return. (export) int editorstep; //execution resumption type static qboolean stepasm; //debugging with (generated) asm. static int executionlinenum; //debugging execution line. #if defined(CSQC_DAT) && !defined(SERVERONLY) extern world_t csqc_world; #endif #if defined(MENU_DAT) && !defined(SERVERONLY) extern world_t menu_world; #endif void Editor_Draw(void) { R2D_EditorBackground(); Key_Dest_Add(kdm_cwindows); } qboolean Editor_Key(int key, int unicode) { if (editormodal) { if (key >= K_F1 && key <= K_F12) return editormodal->redirect(editormodal, unicode, key); } return false; } void Editor_Demodalize(void) { if (editormodal && editormodal->highlightline) { editormodal->highlightline->flags &= ~CONL_EXECUTION; editormodal->highlightline = NULL; } editormodal = NULL; } int Con_Editor_GetLine(console_t *con, conline_t *line) { int linenum = 1; conline_t *l; for (l = con->oldest; l; l = l->newer, linenum++) { if (l == line) return linenum; } return 0; } conline_t *Con_Editor_FindLine(console_t *con, int line) { conline_t *l; for (l = con->oldest; l; l = l->newer) { if (--line == 0) return l; } return NULL; } int Con_Editor_Evaluate(console_t *con, const char *evalstring) { char *eq, *term; 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'; } Con_Footerf(con, false, "%s", evalstring); if (eq) { *eq = '='; Con_Footerf(con, true, " = %s", editprogfuncs->EvaluateDebugString(editprogfuncs, evalstring)); } else Con_Footerf(con, true, " == %s", editprogfuncs->EvaluateDebugString(editprogfuncs, evalstring)); con->linebuffered = NULL; return true; } //creates a new line following an existing line by splitting the previous conline_t *Con_EditorSplit(console_t *con, conline_t *orig, int offset) { conline_t *l; l = BZ_Malloc(sizeof(*l)+(orig->length-offset)*sizeof(conchar_t)); *l = *orig; l->length = l->maxlength = orig->length-con->useroffset; memcpy(l+1, (conchar_t*)(orig+1)+offset, l->length*sizeof(conchar_t)); orig->length = offset; //truncate the old line l->older = orig; l->flags &= ~(CONL_EXECUTION|CONL_BREAKPOINT); orig->newer = l; if (con->current == orig) con->current = l; else l->newer->older = l; if (con->display == orig) con->display = l; con->linecount++; con->selendline = con->selstartline = NULL; return l; } conline_t *Con_EditorMerge(console_t *con, conline_t *first, conline_t *second) { conline_t *l; l = Con_ResizeLineBuffer(con, first, first->length+second->length); //unlink the second line l->newer = second->newer; if (l->newer) l->newer->older = l; //heal references to the second to point to the first if (con->selstartline == second) { con->selstartline = l; con->selstartoffset += l->length; } if (con->selendline == second) { con->selendline = l; con->selendoffset += l->length; } if (con->display == second) con->display = l; if (con->oldest == second) con->oldest = l; if (con->current == second) con->current = l; if (con->userline == second) { con->userline = l; con->useroffset += l->length; } if (con->highlightline == second) { con->highlightline = l; con->highlightline->flags |= CONL_EXECUTION; } //copy over the chars memcpy((conchar_t*)(l+1)+l->length, (conchar_t*)(second+1), second->length*sizeof(conchar_t)); l->length += second->length; //and that line is now dead. con->linecount--; BZ_Free(second); return l; } static void Con_Editor_DeleteSelection(console_t *con) { conline_t *n; con->flags &= ~CONF_KEEPSELECTION; if (con->selstartline) { if (con->selstartline == con->selendline) { memmove((conchar_t*)(con->selstartline+1)+con->selstartoffset, (conchar_t*)(con->selendline+1)+con->selendoffset, sizeof(conchar_t)*(con->selendline->length - con->selendoffset)); con->selendline->length = con->selstartoffset + (con->selendline->length - con->selendoffset); } else { con->selstartline->length = con->selstartoffset; for(n = con->selstartline;;) { n = n->newer; if (!n) break; //shouldn't happen if (n == con->selendline) { //this is the last line, we need to keep the end of the string but not the start. memmove(n+1, (conchar_t*)(n+1)+con->selendoffset, sizeof(conchar_t)*(n->length - con->selendoffset)); n->length = n->length - con->selendoffset; n = Con_EditorMerge(con, con->selstartline, n); break; } //truncate and merge n->length = 0; n = Con_EditorMerge(con, con->selstartline, n); } } } con->userline = con->selstartline; con->useroffset = con->selstartoffset; } static void Con_Editor_DoPaste(void *ctx, char *utf8) { console_t *con = ctx; if (utf8) { conchar_t buffer[8192], *end; char *s, *nl; if (*utf8 && (con->flags & CONF_KEEPSELECTION)) Con_Editor_DeleteSelection(con); for(s = utf8; ; ) { nl = strchr(s, '\n'); if (nl) *nl = 0; end = COM_ParseFunString(CON_WHITEMASK, s, buffer, sizeof(buffer), PFS_FORCEUTF8); if (Con_InsertConChars(con, con->userline, con->useroffset, buffer, end-buffer)) con->useroffset += end-buffer; if (nl) { con->userline = Con_EditorSplit(con, con->userline, con->useroffset); con->useroffset = 0; s = nl+1; } else break; } } } static void Con_Editor_Paste(console_t *con) { Sys_Clipboard_PasteText(CBT_CLIPBOARD, Con_Editor_DoPaste, con); } static void Con_Editor_Save(console_t *con) { vfsfile_t *file; conline_t *line; FS_CreatePath(con->name, FS_GAMEONLY); file = FS_OpenVFS(con->name, "wb", FS_GAMEONLY); if (file) { for (line = con->oldest; line; line = line->newer) { conchar_t *cl = (conchar_t*)(line+1); conchar_t *el = cl + line->length; char buffer[65536]; char *bend = COM_DeFunString(cl, el, buffer, sizeof(buffer)-2, true, !!(con->parseflags & PFS_FORCEUTF8)); if (editaddcr.ival #ifdef _WIN32 || !*editaddcr.string #endif ) *bend++ = '\r'; *bend++ = '\n'; VFS_WRITE(file, buffer, bend-buffer); } VFS_CLOSE(file); Q_snprintfz(con->title, sizeof(con->title), "SAVED: %s", con->name); if (!Q_strncasecmp(con->name, "scripts/", 8)) Shader_NeedReload(true); } } qboolean Con_Editor_MouseOver(struct console_s *con, char **out_tiptext, shader_t **out_shader) { char *mouseover = Con_CopyConsole(con, true, false, false); if (mouseover) { if (editprogfuncs && editprogfuncs->EvaluateDebugString) *out_tiptext = editprogfuncs->EvaluateDebugString(editprogfuncs, mouseover); else { #ifndef SERVERONLY #ifdef CSQC_DAT if (csqc_world.progs && csqc_world.progs->EvaluateDebugString && !*out_tiptext) *out_tiptext = csqc_world.progs->EvaluateDebugString(csqc_world.progs, mouseover); #endif #ifdef MENU_DAT if (menu_world.progs && menu_world.progs->EvaluateDebugString && !*out_tiptext) *out_tiptext = menu_world.progs->EvaluateDebugString(menu_world.progs, mouseover); #endif #endif #ifndef CLIENTONLY if (sv.world.progs && sv.world.progs->EvaluateDebugString && !*out_tiptext) *out_tiptext = sv.world.progs->EvaluateDebugString(sv.world.progs, mouseover); #endif } Z_Free(mouseover); } return true; } void Con_EditorMoveCursor(console_t *con, conline_t *newline, int newoffset, qboolean shiftheld, qboolean moveprior) { if (!shiftheld) con->flags &= ~CONF_KEEPSELECTION; else { if (!(con->flags & CONF_KEEPSELECTION) || (con->selendline == con->selstartline && con->selendoffset == con->selstartoffset)) { con->flags |= CONF_KEEPSELECTION; if (moveprior) { con->selstartline = newline; con->selstartoffset = newoffset; con->selendline = con->userline; con->selendoffset = con->useroffset; } else { con->selstartline = con->userline; con->selstartoffset = con->useroffset; con->selendline = newline; con->selendoffset = newoffset; } } else { if (con->selendline == con->userline && con->selendoffset == con->useroffset) { if (con->selstartline != con->selendline && con->selstartline == newline && moveprior) { //inverted con->selendline = con->selstartline; con->selendoffset = con->selstartoffset; con->selstartline = newline; con->selstartoffset = newoffset; } else { con->selendline = newline; con->selendoffset = newoffset; } } else if (con->selstartline == con->userline && con->selstartoffset == con->useroffset) { if (con->selstartline == con->selendline && con->selstartline != newline && !moveprior) { //inverted con->selstartline = con->selendline; con->selstartoffset = con->selendoffset; con->selendline = newline; con->selendoffset = newoffset; } else { con->selstartline = newline; con->selstartoffset = newoffset; } } } } if (con->userline == con->display && !moveprior) con->display = newline; con->userline = newline; con->useroffset = newoffset; } static conchar_t *Con_Editor_Equals(conchar_t *start, conchar_t *end, const char *match) { conchar_t *n; unsigned int ccode, flags; for (; start < end; start = n) { n = Font_Decode(start, &flags, &ccode); if (*match) { if (ccode != *(unsigned char*)match++) return NULL; } else if (ccode == ' ' || ccode == '\t') break; //found whitespace after the token, its complete. else return NULL; } if (*match) return NULL; //truncated //and skip any trailing whitespace, because we can. for (; start < end; start = n) { n = Font_Decode(start, &flags, &ccode); if (ccode == ' ' || ccode == '\t') continue; else break; } return start; } static conchar_t *Con_Editor_SkipWhite(conchar_t *start, conchar_t *end) { conchar_t *n; unsigned int ccode, flags; for (; start < end; start = n) { n = Font_Decode(start, &flags, &ccode); if (ccode == ' ' || ccode == '\t') continue; else break; } return start; } static void Con_Editor_LineChanged_Shader(conline_t *line) { static const char *maplines[] = {"map", "clampmap"}; size_t i; conchar_t *start = (conchar_t*)(line+1), *end = start + line->length, *n; start = Con_Editor_SkipWhite(start, end); line->flags &= ~(CONL_BREAKPOINT|CONL_EXECUTION); for (i = 0; i < countof(maplines); i++) { n = Con_Editor_Equals(start, end, maplines[i]); if (n) { char mapname[8192]; char fname[MAX_QPATH]; flocation_t loc; unsigned int flags; image_t img; memset(&img, 0, sizeof(img)); img.ident = mapname; COM_DeFunString(n, end, mapname, sizeof(mapname), true, true); while(*img.ident == '$') { if (!Q_strncasecmp(img.ident, "$lightmap", 9)) return; //lightmaps don't need to load from disk if (!Q_strncasecmp(img.ident, "$rt:", 4)) return; //render targets never come from disk if (!Q_strncasecmp(img.ident, "$clamp:", 7) || !Q_strncasecmp(img.ident, "$3d:", 4) || !Q_strncasecmp(img.ident, "$cube:", 6) || !Q_strncasecmp(img.ident, "$nearest:", 9) || !Q_strncasecmp(img.ident, "$linear:", 8)) img.ident = strchr(img.ident, ':')+1; else break; } if (!Image_LocateHighResTexture(&img, &loc, fname, sizeof(fname), &flags)) line->flags |= CONL_BREAKPOINT; return; } } } static void Con_Editor_LineChanged(console_t *con, conline_t *line) { if (!Q_strncasecmp(con->name, "scripts/", 8)) Con_Editor_LineChanged_Shader(line); } qboolean Con_Editor_Key(console_t *con, unsigned int unicode, int key) { extern qboolean keydown[K_MAX]; qboolean altdown = keydown[K_LALT] || keydown[K_RALT]; qboolean ctrldown = keydown[K_LCTRL] || keydown[K_RCTRL]; qboolean shiftdown = keydown[K_LSHIFT] || keydown[K_RSHIFT]; if (key == K_MOUSE1) { con->flags &= ~CONF_KEEPSELECTION; con->buttonsdown = CB_SELECT; return true; } if (key == K_MOUSE2) { con->flags &= ~CONF_KEEPSELECTION; con->buttonsdown = CB_SCROLL; return true; } if (!con->userline) return false; if (con->linebuffered) return false; switch(key) { case K_BACKSPACE: if (con->flags & CONF_KEEPSELECTION) Con_Editor_DeleteSelection(con); else if (con->useroffset == 0) { if (con->userline->older) Con_EditorMerge(con, con->userline->older, con->userline); } else { con->useroffset--; memmove((conchar_t*)(con->userline+1)+con->useroffset, (conchar_t*)(con->userline+1)+con->useroffset+1, (con->userline->length - con->useroffset)*sizeof(conchar_t)); con->userline->length -= 1; Con_Editor_LineChanged(con, con->userline); } return true; case K_DEL: if (con->flags & CONF_KEEPSELECTION) Con_Editor_DeleteSelection(con); else if (con->useroffset == con->userline->length) { if (con->userline->newer) Con_EditorMerge(con, con->userline, con->userline->newer); } else { memmove((conchar_t*)(con->userline+1)+con->useroffset, (conchar_t*)(con->userline+1)+con->useroffset+1, (con->userline->length - con->useroffset)*sizeof(conchar_t)); con->userline->length -= 1; Con_Editor_LineChanged(con, con->userline); } break; case K_ENTER: /*split the line into two, selecting the new line*/ if (con->flags & CONF_KEEPSELECTION) Con_Editor_DeleteSelection(con); con->userline = Con_EditorSplit(con, con->userline, con->useroffset); con->useroffset = 0; break; case K_HOME: if (ctrldown) con->display = con->oldest; else Con_EditorMoveCursor(con, con->userline, 0, shiftdown, true); return true; case K_END: if (ctrldown) con->display = con->current; else Con_EditorMoveCursor(con, con->userline, con->userline->length, shiftdown, false); return true; case K_UPARROW: case K_KP_UPARROW: case K_GP_DPAD_UP: if (con->userline->older) { if (con->useroffset > con->userline->older->length) Con_EditorMoveCursor(con, con->userline->older, con->userline->older->length, shiftdown, true); else Con_EditorMoveCursor(con, con->userline->older, con->useroffset, shiftdown, true); } return true; case K_DOWNARROW: case K_KP_DOWNARROW: case K_GP_DPAD_DOWN: if (con->userline->newer) { if (con->useroffset > con->userline->newer->length) Con_EditorMoveCursor(con, con->userline->newer, con->userline->newer->length, shiftdown, false); else Con_EditorMoveCursor(con, con->userline->newer, con->useroffset, shiftdown, false); } return true; case K_LEFTARROW: case K_KP_LEFTARROW: case K_GP_DPAD_LEFT: if (con->useroffset == 0) { if (con->userline->older) Con_EditorMoveCursor(con, con->userline->older, con->userline->older->length, shiftdown, true); } else Con_EditorMoveCursor(con, con->userline, con->useroffset-1, shiftdown, true); return true; case K_RIGHTARROW: case K_KP_RIGHTARROW: case K_GP_DPAD_RIGHT: if (con->useroffset == con->userline->length) { if (con->userline->newer) Con_EditorMoveCursor(con, con->userline->newer, 0, shiftdown, false); } else Con_EditorMoveCursor(con, con->userline, con->useroffset+1, shiftdown, false); return true; case K_INS: if (shiftdown) { if (con->flags & CONF_KEEPSELECTION) Con_Editor_DeleteSelection(con); Con_Editor_Paste(con); break; } if (ctrldown && (con->flags & CONF_KEEPSELECTION)) { char *buffer = Con_CopyConsole(con, true, false, true); //don't keep markup if we're copying to the clipboard if (buffer) { Sys_SaveClipboard(CBT_CLIPBOARD, buffer); Z_Free(buffer); } break; } return false; case K_LSHIFT: case K_RSHIFT: case K_LCTRL: case K_RCTRL: case K_LALT: case K_RALT: return true; //these non-printable chars generally should not be allowed to trigger bindings. case K_F1: Con_Printf( "Editor help:\n" "F1: Show help\n" "F2: Open file named on cursor line\n" "F3: Toggle expression evaluator\n" "CTRL+S: Save file\n" "F5: Stop tracing (continue running)\n" "F6: Print stack trace\n" "F8: Change current point of execution\n" "F9: Set breakpoint\n" "ALT+F10: save+recompile\n" "F10: Step Over (skipping children)\n" "SHIFT+F11: Step Out\n" "F11: Step Into\n" // "F12: Go to definition\n" ); Cbuf_AddText("toggleconsole\n", RESTRICT_LOCAL); return true; 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, false); }*/ return true; case K_F3: if (editprogfuncs) { con->linebuffered = Con_Editor_Evaluate; } return true; case K_F5: //stop debugging if (editormodal) { Editor_Demodalize(); editorstep = DEBUG_TRACE_OFF; } return true; case K_F6: if (editprogfuncs) { PR_StackTrace(editprogfuncs, 2); Key_Dest_Add(kdm_console); return true; } return false; case K_F8: //move execution point to here - I hope you move to the same function! if (editprogfuncs && con->userline) { int l = Con_Editor_GetLine(con, con->userline); if (l) { conline_t *n = Con_Editor_FindLine(con, l); if (n) { if (con->highlightline) { con->highlightline->flags &= ~CONL_EXECUTION; con->highlightline = NULL; } executionlinenum = l; con->highlightline = n; n->flags |= CONL_EXECUTION; } } } return true; case K_F9: /*set breakpoint*/ { conline_t *cl; char *fname = con->name; int mode; int line; if (!strncmp(fname, pr_sourcedir.string, strlen(pr_sourcedir.string)) && fname[strlen(pr_sourcedir.string)] == '/') fname += strlen(pr_sourcedir.string)+1; else if (!strncmp(fname, "src/", 4)) fname += 4; else if (!strncmp(fname, "source/", 7)) fname += 7; else if (!strncmp(fname, "qcsrc/", 6)) fname += 6; else if (!strncmp(fname, "progs/", 6)) fname += 6; cl = con->userline; line = Con_Editor_GetLine(con, cl); if (cl->flags & CONL_BREAKPOINT) mode = 0; else mode = 1; #ifndef SERVERONLY #ifdef CSQC_DAT if (csqc_world.progs && csqc_world.progs->ToggleBreak) csqc_world.progs->ToggleBreak(csqc_world.progs, fname, line, mode); #endif #ifdef MENU_DAT if (menu_world.progs && menu_world.progs->ToggleBreak) menu_world.progs->ToggleBreak(menu_world.progs, fname, line, mode); #endif #endif #ifndef CLIENTONLY if (sv.world.progs && sv.world.progs->ToggleBreak) sv.world.progs->ToggleBreak(sv.world.progs, fname, line, mode); #endif if (mode) cl->flags |= CONL_BREAKPOINT; else cl->flags &= ~CONL_BREAKPOINT; } return true; case K_F10: if (altdown) { Con_Editor_Save(con); if (!editprogfuncs) Cbuf_AddText("compile; toggleconsole\n", RESTRICT_LOCAL); return true; } //if (editormodal) //careful of autorepeat { Editor_Demodalize(); editorstep = DEBUG_TRACE_OVER; return true; } return false; case K_F11: //single step //if (editormodal) //careful of auto-repeat { Editor_Demodalize(); editorstep = shiftdown?DEBUG_TRACE_OUT:DEBUG_TRACE_INTO; return true; } return false; default: if (ctrldown && key =='s') { Con_Editor_Save(con); return true; } if (ctrldown && key =='v') { if (con->flags & CONF_KEEPSELECTION) Con_Editor_DeleteSelection(con); Con_Editor_Paste(con); break; } if (ctrldown && key =='c' && (con->flags & CONF_KEEPSELECTION)) { char *buffer = Con_CopyConsole(con, true, false, true); //don't keep markup if we're copying to the clipboard if (buffer) { Sys_SaveClipboard(CBT_CLIPBOARD, buffer); Z_Free(buffer); } break; } if (unicode) { conchar_t c[2]; int l = 0; if (unicode > 0xffff) c[l++] = CON_LONGCHAR | (unicode>>16); c[l++] = CON_WHITEMASK | (unicode&0xffff); if (con->flags & CONF_KEEPSELECTION) Con_Editor_DeleteSelection(con); if (con->userline && Con_InsertConChars(con, con->userline, con->useroffset, c, l)) { con->useroffset += l; Con_Editor_LineChanged(con, con->userline); } break; } return false; } Q_snprintfz(con->title, sizeof(con->title), "MODIFIED: %s", con->name); return true; } void Con_Editor_CloseCallback(void *ctx, int op) { console_t *con = ctx; if (con != con_curwindow) //ensure that it still exists (lame only-active-window check) return; if (op == 0) Con_Editor_Save(con); if (op != -1) //-1 == cancel Con_Destroy(con); } qboolean Con_Editor_Close(console_t *con, qboolean force) { if (!force) { if (!strncmp(con->title, "MODIFIED: ", 10)) { M_Menu_Prompt(Con_Editor_CloseCallback, con, va("Save changes?\n%s\n", con->name), "Yes", "No", "Cancel"); return false; } } if (con == editormodal) { Editor_Demodalize(); editorstep = DEBUG_TRACE_OFF; } return true; } void Con_Editor_GoToLine(console_t *con, int line) { con->userline = con->oldest; while (line --> 1) { if (!con->userline->newer) break; con->userline = con->userline->newer; } con->useroffset = 0; con->display = con->userline; //FIXME: we REALLY need to support top-down style consoles. line = con->wnd_h / 8; line /= 2; while (con->display->newer && line --> 0) { con->display = con->display->newer; } } console_t *Con_TextEditor(const char *fname, const char *line, qboolean newfile) { static int editorcascade; console_t *con; conline_t *l; con = Con_FindConsole(fname); if (con) { Con_SetActive(con); if (line && con->redirect == Con_Editor_Key) Con_Editor_GoToLine(con, atoi(line)); if (con->close != Con_Editor_Close) con = NULL; } else { con = Con_Create(fname, 0); if (con) { vfsfile_t *file; Q_snprintfz(con->title, sizeof(con->title), "EDIT: %s", con->name); /*make it a console window thing*/ con->flags |= CONF_ISWINDOW; con->wnd_x = (editorcascade & 1)?vid.width/2:0; #ifdef ANDROID con->wnd_y = 0; #else con->wnd_y = (editorcascade & 2)?vid.height/2:0; #endif con->wnd_w = vid.width/2; con->wnd_h = vid.height/2; editorcascade++; con->flags |= CONF_NOWRAP; //disable line wrapping. yay editors. con->flags |= CONF_KEEPSELECTION; //only change the selection if we ask for it. con->parseflags = PFS_FORCEUTF8; con->userdata = NULL; con->linebuffered = NULL; con->redirect = Con_Editor_Key; con->mouseover = Con_Editor_MouseOver; con->close = Con_Editor_Close; con->maxlines = 0x7fffffff; //line limit is effectively unbounded, for a 31-bit process. if (!newfile) { file = FS_OpenVFS(fname, "rb", FS_GAME); if (file) { char buffer[65536]; while (VFS_GETS(file, buffer, sizeof(buffer))) { Con_PrintCon(con, buffer, PFS_FORCEUTF8|PFS_KEEPMARKUP|PFS_NONOTIFY); Con_PrintCon(con, "\n", PFS_FORCEUTF8|PFS_KEEPMARKUP|PFS_NONOTIFY); } VFS_CLOSE(file); } for (l = con->oldest; l; l = l->newer) Con_Editor_LineChanged(con, l); } con->display = con->oldest; con->selstartline = con->selendline = con->oldest; //put the cursor at the start of the file con->selstartoffset = con->selendoffset = 0; if (line) Con_Editor_GoToLine(con, atoi(line)); else Con_Editor_GoToLine(con, 1); Con_Footerf(con, false, " ^2%i lines", con->linecount); Con_SetActive(con); } } return con; } void Con_TextEditor_f(void) { char *fname = Cmd_Argv(1); char *line = strrchr(fname, ':'); if (line) *line++ = 0; if (!*fname) { Con_Printf("%s [filename[:line]]: edit a file\n", Cmd_Argv(0)); return; } Con_TextEditor(fname, line, false); } int QCLibEditor(pubprogfuncs_t *prfncs, const char *filename, int *line, int *statement, char *reason, pbool fatal) { char newname[MAX_QPATH]; console_t *edit; if (!strncmp(filename, "./", 2)) filename+=2; stepasm = !line || *line < 0; //we can cope with no line info by displaying asm if (editormodal || (stepasm && !statement)) { if (fatal) return DEBUG_TRACE_ABORT; return DEBUG_TRACE_OFF; //whoops } if (!pr_debugger.ival) { Con_Printf("Set %s to trace\n", pr_debugger.name); if (fatal) return DEBUG_TRACE_ABORT; return DEBUG_TRACE_OFF; //get lost } if (qrenderer == QR_NONE || stepasm) { //just dump the line of code that's being execed onto the console. int i; char buffer[8192]; char *r; vfsfile_t *f; if (stepasm) { prfncs->GenerateStatementString(prfncs, *statement, buffer, sizeof(buffer)); Con_Printf("%s", buffer); return DEBUG_TRACE_INTO; } if (!line) { //please don't crash if (fatal) return DEBUG_TRACE_ABORT; return DEBUG_TRACE_OFF; //whoops } 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); } return DEBUG_TRACE_OUT; //only display the line itself. } editprogfuncs = prfncs; if (!stepasm && *filename && !COM_FCheckExists(filename)) { //people generally run their qcc from $mod/src/ or so, so paths are usually relative to that instead of the mod directory. //this means we now need to try and guess what the user used. if (filename != newname && *pr_sourcedir.string) { Q_snprintfz(newname, sizeof(newname), "%s/%s", pr_sourcedir.string, filename); if (COM_FCheckExists(newname)) filename = newname; } if (filename != newname) { Q_snprintfz(newname, sizeof(newname), "src/%s", filename); if (COM_FCheckExists(newname)) filename = newname; } if (filename != newname) { Q_snprintfz(newname, sizeof(newname), "source/%s", filename); if (COM_FCheckExists(newname)) filename = newname; } if (filename != newname) { Q_snprintfz(newname, sizeof(newname), "qcsrc/%s", filename); if (COM_FCheckExists(newname)) filename = newname; } if (filename != newname) { //some people are fucked in the head Q_snprintfz(newname, sizeof(newname), "progs/%s", filename); if (COM_FCheckExists(newname)) filename = newname; } if (filename != newname) { stepasm = true; if (fatal) Con_Printf(CON_ERROR "Unable to find file \"%s\"\n", filename); else Con_Printf(CON_WARNING "Unable to find file \"%s\"\n", filename); } } if (stepasm) { if (fatal) return DEBUG_TRACE_ABORT; return DEBUG_TRACE_OFF; //whoops } else { edit = Con_TextEditor(filename, NULL, false); if (!edit) return DEBUG_TRACE_OFF; Con_Editor_GoToLine(edit, *line); } executionlinenum = *line; { double oldrealtime = realtime; Editor_Demodalize(); editormodal = edit; editorstep = DEBUG_TRACE_OFF; if (edit->userline) { edit->highlightline = edit->userline; edit->highlightline->flags |= CONL_EXECUTION; } while(editormodal && editprogfuncs) { realtime = Sys_DoubleTime(); scr_disabled_for_loading=false; SCR_UpdateScreen(); Sys_SendKeyEvents(); IN_Commands (); S_ExtraUpdate(); #ifdef CLIENTONLY Sys_Sleep(20/1000.0); #else NET_Sleep(20/1000.0, false); //any os. #endif } realtime = oldrealtime; Editor_Demodalize(); } if (stepasm) { if (line) *line = 0; *statement = executionlinenum; } else if (line) *line = executionlinenum; return editorstep; } void Editor_ProgsKilled(pubprogfuncs_t *dead) { if (editprogfuncs == dead) { editprogfuncs = NULL; Editor_Demodalize(); editorstep = DEBUG_TRACE_OFF; } } void Editor_Init(void) { Cmd_AddCommand("edit", Con_TextEditor_f); Cvar_Register(&editstripcr, "Text editor"); Cvar_Register(&editaddcr, "Text editor"); // Cvar_Register(&edittabspacing, "Text editor"); Cvar_Register(&pr_debugger, "Text editor"); } #endif