/* - [x] list_functions.sh and rg in general in the color mode is prinitng differently from terminal as well as output is truncated! - [ ] Syntax for executing commands from root of project, or maybe commands should be executed from root of the CurrentDirectory and there should be a cd command instead of OpenProject - [ ] Fuzzy search over executed command ouput - [ ] When inserting parenthesis and selection is there, put the parens on both sides? - [ ] KillProcess in console !!! - should also kill all the children ........... - [x] ctrl-e with these short main.c:290: breaks a little, need to first click ctrl-e and then alt-e to jump - [ ] Use command window without special fuzzy search features to type commands and stuff for executing shell etc.. - [ ] I noticed WordComplete getting busted and returning cutdown words but not sure how to repro ... ## Monaco like design for familiarity - [x] Ctrl+Alt+- and Ctrl+Shift+- Jump back, jump forward - [x] ctrl-shift-l select all occurences of a string - [x] ctrl-alt-r open containing folder (file explorer system) - [x] ctrl-alt-MouseMove should do a box select with multiple cursors thing - [ ] ctrl-tab - switch file lister with instant hold release semantics? - [ ] SearchAndReplace how to do better? - [ ] ctrl-t find workspace symbols? how can we do it? - [ ] Snippet design? - [ ] Make a platform layer and separate SDL stuff out - [x] ReplaceAll - heap-use-after-free address, how to debug? I think would be nice to iterate all buffer ids and their addresses along with the state - [x] BRO, the caret teleports on linux when I press the arrow for too long - [ ] Report SDL newest vs SDL previous version on wayland - [ ] Ctrl+Shift+ArrowDown at the end of buffer, doesn't capture characters on last line without new line - [ ] Remove -lbacktrace and add my backtrace library thing - [ ] Refactor build.sh to accept commands and remove build_web.sh - [ ] Cleanups - [ ] How to enable framerate to be unlimited and not break scrolling? - [x] When dragging a file into the editor, would be nice if the file opened in the window user dropped the file into. Not the active window. - [ ] When 2 views of same buffer are open, the view with caret below the caret which modifies the view - starts moving and getting messed up - [ ] Reduce number of created buffers to one per window, should be enough - [ ] GetWindowZOrder to IterateWindowsInZOrder - [ ] Rework history API, tagging modification blocks with carets? - [ ] The lexing / parsing code for config / bindings appears sloppy would be nice to clean it up but I don't have any ideas - [ ] Directory tree doesn't make much sense! Maybe just consolidate into one folder? create nice names - the raddbg idea didn't pan out well here - [ ] Test BlockArena correctnsess - random allocations, writes and undos, try to crash - [ ] General parser / data description thing - [ ] Rewrite other parsers using this - [ ] Rewrite Env handling (to not be OS specific) - New error mechanism - we were losing errors when ReportError was called multiple times, I still want that but I don't want to lose errors, so turn it into a summary list of errors - [ ] BeginLog EndLog, and then show all logs as a list in the UI thing - [ ] Undo kinds (to enable history in fuzzy buffers) - [ ] Add undo kind. Snapshot kind, so that history is possible in weird buffers without paying a huge memory cost. The idea is that we would store the exact buffer state to replace with, editor would just save history of first line etc. - [x] Macros - [ ] Regex - [ ] ctags based indexing - [ ] WordComplete - [ ] Rewrite WordComplete to use CoroutineCreate, maybe try reducing globals to just the coroutine itself - [ ] More bounded? seems like it might be problematic on a bigger project but so far it isn't (even for SDL or raddbg) - [ ] Shell / terminal buffer plugin (keep the shell alive and talk with it) - [ ] Directory Navigation - [ ] Remake lister when files change on disk - [ ] When saving apply all the modifications instead (like deleting files, renaming etc.) or maybe that's not even needed considering we are integrating shell commands - [ ] OpenCode - [ ] Hangs the editor on big files - [ ] Open - [ ] Way to bind "open" commands to keys from config - [ ] Ability to access and set clipboard as well as affect selection in the open scripts - [ ] QueryFile - [ ] Indicate to user that he is choosing a file - [ ] Define clear rules for opt out (like switching to different window) and kill or views that were for choosing? */ #define PLUGIN_PROFILER 1 #include "plugin_profiler.h" #include "basic.h" #include "basic.cpp" #include "SDL3/SDL.h" #include "external/glad/glad.c" #include "external/glad/glad.h" #include "external/stb_truetype.h" #include "external/stb_truetype.c" #if OS_LINUX #define MCO_USE_UCONTEXT #endif #define MINICORO_IMPL #include "external/minicoro.h" #include "render_generated_font.cpp" #include "render_font.cpp" #include "render_opengl.cpp" #define PLUGIN_CONFIG 1 #define PLUGIN_SEARCH_WINDOW 1 #define PLUGIN_PROJECT_MANAGEMENT 1 #define PLUGIN_WINDOW_MANAGEMENT 1 #define PLUGIN_DIRECTORY_NAVIGATION 1 #define PLUGIN_SEARCH_OPEN_BUFFERS 1 #define PLUGIN_PROJECT_MANAGEMENT 1 #define PLUGIN_BASIC_COMMANDS 1 #define PLUGIN_COMMAND_WINDOW 1 #define PLUGIN_SEARCH_WINDOW 1 #define PLUGIN_STATUS_WINDOW 1 #define PLUGIN_BUILD_WINDOW 1 #define PLUGIN_DEBUG_WINDOW 1 #define PLUGIN_RECORD_GC 1 #define PLUGIN_RECORD_EVENTS 1 #define PLUGIN_DIRECTORY_NAVIGATION 1 #define PLUGIN_LOAD_VCVARS OS_WINDOWS #define PLUGIN_REMEDYBG OS_WINDOWS #define PLUGIN_FILE_COMMANDS 1 #define PLUGIN_WORD_COMPLETE 1 #define PLUGIN_TESTS 1 #include "plugin_directory_navigation.h" #include "plugin_search_window.h" #include "plugin_project_management.h" #include "plugin_config.h" #include "text_editor.h" #include "globals.cpp" #include "coroutines.cpp" #include "data_desc.cpp" #include "buffer.cpp" #include "view.cpp" #include "window.cpp" #include "fuzzy_search_view.cpp" #include "process.cpp" #include "event.cpp" #include "config.cpp" #include "ui.cpp" #include "commands.cpp" #include "commands_clipboard.cpp" #include "scratch.cpp" #include "draw.cpp" #include "plugin_config.cpp" #include "plugin_window_management.cpp" #include "plugin_directory_navigation.cpp" #include "plugin_search_open_buffers.cpp" #include "plugin_project_management.cpp" #include "plugin_basic_commands.cpp" #include "plugin_command_window.cpp" #include "plugin_search_window.cpp" #include "plugin_status_window.cpp" #include "plugin_build_window.cpp" #include "plugin_debug_window.cpp" #include "plugin_record_gc.cpp" #include "plugin_record_events.cpp" #include "plugin_load_vcvars.cpp" #include "plugin_remedybg.cpp" #include "plugin_profiler.cpp" #include "plugin_file_commands.cpp" #include "plugin_word_complete.cpp" #include "plugin_tests.cpp" #if OS_WASM EM_JS(void, JS_SetMouseCursor, (const char *cursor_str), { document.getElementById("canvas").style.cursor = UTF8ToString(cursor_str); }) void SetMouseCursor(SDL_SystemCursor id) { if (id == SDL_SYSTEM_CURSOR_EW_RESIZE) { JS_SetMouseCursor("ew-resize"); } else if (id == SDL_SYSTEM_CURSOR_NS_RESIZE) { JS_SetMouseCursor("ns-resize"); } else if (id == SDL_SYSTEM_CURSOR_DEFAULT) { JS_SetMouseCursor("default"); } else if (id == SDL_SYSTEM_CURSOR_TEXT) { JS_SetMouseCursor("text"); } else if (id == SDL_SYSTEM_CURSOR_MOVE) { JS_SetMouseCursor("all-scroll"); } else { InvalidCodepath(); } } #else void SetMouseCursor(SDL_SystemCursor id) { if (SDL_MouseCursor == NULL || SDL_MouseCursorLastID != id) { if (SDL_MouseCursor != NULL) { SDL_DestroyCursor(SDL_MouseCursor); } SDL_MouseCursor = SDL_CreateSystemCursor(id); SDL_SetCursor(SDL_MouseCursor); SDL_MouseCursorLastID = id; } } #endif void SetMouseCursor(Event event) { ProfileFunction(); Scratch scratch; Array order = GetWindowZOrder(scratch); Vec2I mouse = MouseVec2I(); if (ResizerSelected.id != -1) { Window *window = GetWindow(ResizerSelected); if (window->primary) { SetMouseCursor(SDL_SYSTEM_CURSOR_EW_RESIZE); } else { SetMouseCursor(SDL_SYSTEM_CURSOR_NS_RESIZE); } return; } if (ResizerHover.id != -1) { Window *window = GetWindow(ResizerHover); if (window->primary) { SetMouseCursor(SDL_SYSTEM_CURSOR_EW_RESIZE); } else { SetMouseCursor(SDL_SYSTEM_CURSOR_NS_RESIZE); } return; } if (MouseMiddleAnchor.x != 0 && MouseMiddleAnchor.y != 0) { SetMouseCursor(SDL_SYSTEM_CURSOR_MOVE); return; } For(order) { if (!it->visible) continue; bool mouse_in_total = AreOverlapping(mouse, it->total_rect); bool mouse_in_scrollbar = AreOverlapping(mouse, it->scrollbar_rect); if (!IsDocumentSelectionValid() && (mouse_in_scrollbar || IsScrollbarSelectionValid())) { SetMouseCursor(SDL_SYSTEM_CURSOR_DEFAULT); return; } else if (mouse_in_total || IsDocumentSelectionValid()) { SetMouseCursor(SDL_SYSTEM_CURSOR_TEXT); return; } } SetMouseCursor(SDL_SYSTEM_CURSOR_DEFAULT); } void CMD_QuitWithoutSaving() { AppIsRunning = false; } RegisterCommand(CMD_QuitWithoutSaving, "", "Self explanatory"); void ShowQuitAppUI(mco_coro *co) { UIAction res = ShowCloseAllUI(co); if (res != UIAction_Cancel) { CMD_QuitWithoutSaving(); } } void CMD_Quit() { RemoveCoroutine("ShowQuitAppUI"); CCtx *data = AddCoroutine(ShowQuitAppUI); ResumeCoroutine(data); } RegisterCommand(CMD_Quit, "", "Ask user which files he would like to save and exit"); void OnCommand(Event event) { ProfileFunction(); // // Window cursor setting // Scratch scratch; Array order = GetWindowZOrder(scratch); // Handle wheel scrolling if (event.xwheel || event.ywheel) { Vec2I mouse = MouseVec2I(); For (order) { if (!it->visible) { continue; } bool mouse_in_window = AreOverlapping(mouse, it->total_rect); if (mouse_in_window) { View *view = GetView(it->active_view); view->scroll.y -= (Int)(event.ywheel * 48); view->scroll.x += (Int)(event.xwheel * 48); } } } // Handle selected window scrollbar // @note: the order here assumes that we won't run this code on the // same event as the scroll was pressed if (IsScrollbarSelectionValid() && Mouse(LEFT_UP)) { Assert(DocumentSelected.id == -1); ScrollbarSelected.id = -1; } else if (IsScrollbarSelectionValid()) { // :ScrollbarImprovement // @todo: it generally works ok but it moves the scrollbar a bit on click // when mouse is not even moving Assert(DocumentSelected.id == -1); Window *window = GetWindow(ScrollbarSelected); View *view = GetView(window->active_view); Vec2 mouse_vec2 = MouseVec2(); Scroller s = ComputeScrollerRect(window); double size_y = (double)GetSize(window->scrollbar_rect).y; double p = mouse_vec2.y - window->scrollbar_rect.min.y; double v = p / size_y; v = v + (window->mouse_scroller_offset); view->scroll.y = (Int)(v * (double)s.line_count * (double)window->font->line_spacing); } if (DocumentSelected != ActiveWindowID) { DocumentSelected.id = -1; } else if (IsDocumentSelectionValid() && MouseUp()) { Assert(ScrollbarSelected.id == -1); DocumentSelected.id = -1; } else if (IsDocumentSelectionValid()) { Assert(ScrollbarSelected.id == -1); BSet selected = GetBSet(DocumentSelected); Vec2I mouse = MouseVec2I(); // Special case for full-screen where we can have document // aligned with monitor screen in which case mouse cursor cannot // be smaller then 0 which means we cannot scroll if (mouse.y == 0 && selected.window->document_rect.min.y == 0) { float x, y; SDL_GetGlobalMouseState(&x, &y); x = roundf(DPIScale * x); y = roundf(DPIScale * y); if (y == 0) { mouse.y = -10; } } Int p = ScreenSpaceToBufferPos(selected.window, selected.view, selected.buffer, mouse); Caret &caret = selected.view->carets[0]; caret = SetFrontWithAnchor(caret, DocumentAnchor, p); if (event.alt && event.shift) { Int front = GetFront(DocumentAnchor); XY from = PosToXY(selected.buffer, front); XY to = ScreenSpaceToXY(selected.window, selected.view, mouse); Int min_line = Min(from.y, to.y); Int max_line = Max(from.y, to.y); Int min_col = Min(from.x, to.x); Int max_col = Max(from.x, to.x); selected.view->carets.len = 0; for (Int line = min_line; line <= max_line; line += 1) { XY left_xy = {min_col, line}; XY right_xy = {max_col, line}; Int left = XYToPosWithoutNL(selected.buffer, left_xy); Int right = XYToPosWithoutNL(selected.buffer, right_xy); Caret new_selection = MakeCaret(left, right); Add(&selected.view->carets, new_selection); } MergeCarets(selected.buffer, &selected.view->carets); } } if (ResizerSelected.id != -1 && Mouse(LEFT_UP)) { Assert(DocumentSelected.id == -1); Assert(ScrollbarSelected.id == -1); ResizerSelected.id = {-1}; } else if (ResizerSelected.id != -1) { Window *window = GetWindow(ResizerSelected); if (window->primary) { Vec2I mouse = MouseVec2I(); Int offx = mouse.x - window->resizer_rect.min.x; window->weight += (double)offx / (double)WindowCalcEvenResizerValue(event.xwindow); window->weight = Clamp(window->weight, 0.1, 100.0); } } else { ResizerHover = {-1}; For(Windows) { Vec2I mouse = MouseVec2I(); bool mouse_in_rect = AreOverlapping(mouse, it->resizer_rect); if (mouse_in_rect) { ResizerHover = it->id; if (Mouse(LEFT)) { ResizerSelected = it->id; } } } } // Set active window on click if (MousePress() || event.kind == EVENT_DROP_FILE) { Vec2I mouse = MouseVec2I(); For(order) { if (!it->visible) { continue; } bool mouse_in_document = AreOverlapping(mouse, it->document_rect); if (mouse_in_document) { NextActiveWindowID = ActiveWindowID = it->id; if (event.kind == EVENT_DROP_FILE) { WindowOpenBufferView(it, event.text); } break; } } } if (event.ctrl && Mouse(RIGHT)) { Vec2I mouse = MouseVec2I(); BSet active = GetBSet(ActiveWindowID); bool mouse_in_document = AreOverlapping(mouse, active.window->document_rect); if (mouse_in_document) { Int p = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse); Int saved_front = -1; IterRemove(active.view->carets) { IterRemovePrepare(active.view->carets); if (InBounds(it.range, p)) { String16 string = GetString(active.buffer, it.range); SaveStringInClipboard(string); remove_item = true; saved_front = GetFront(it); } } if (active.view->carets.len == 0) Add(&active.view->carets, MakeCaret(saved_front)); if (saved_front == -1) { Int line = PosToLine(active.buffer, p); Range line_range = GetLineRangeWithoutNL(active.buffer, line); String16 string = GetString(active.buffer, line_range); SaveStringInClipboard(string); } } } else if (Mouse(RIGHT)) { MouseLoadWord(event); } { Vec2I mouse = MouseVec2I(); if (Mouse(MIDDLE)) { MouseMiddleAnchor = mouse; } else if (Mouse(MIDDLE_UP)) { MouseMiddleAnchor = {0, 0}; } if (MouseMiddleAnchor.x != 0) { BSet active = GetBSet(ActiveWindowID); active.view->scroll += mouse - MouseMiddleAnchor; } } if (event.ctrl && Mouse(LEFT)) { MouseLoadWord(event); } else if (Mouse(LEFT)) { // Uses Alt and shift Vec2I mouse = MouseVec2I(); { Assert(ScrollbarSelected.id == -1); Assert(DocumentSelected.id == -1); BSet active = GetBSet(ActiveWindowID); // using next to make sure mouse works on first click after switching the window bool mouse_in_document = AreOverlapping(mouse, active.window->document_rect); bool mouse_in_line_numbers = AreOverlapping(mouse, active.window->line_numbers_rect); if (mouse_in_document || mouse_in_line_numbers) { DocumentSelected = active.window->id; Int p = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse); if (event.alt) { Insert(&active.view->carets, MakeCaret(p, p), 0); } else if (!event.alt && !event.shift) { active.view->carets.len = 1; } Caret &caret = active.view->carets[0]; if (event.shift) { if (p <= caret.range.min) { caret.range.min = p; caret.ifront = 0; } else if (p >= caret.range.max) { caret.range.max = p; caret.ifront = 1; } } else if (event.clicks >= 2 && InBounds({caret.range.min - 1, caret.range.max + 1}, p)) { Range range = EncloseWord(active.buffer, p); if (event.clicks >= 3) { range = EncloseFullLine(active.buffer, p); } caret = MakeCaret(range.max, range.min); } else { caret = MakeCaret(p); } MergeCarets(active.buffer, &active.view->carets); DocumentAnchor = caret; } } // Figure out scrollbar click // :ScrollbarImprovement // @todo: it generally works ok but it moves the scrollbar a bit on click // when mouse is not even moving For(order) { if (!it->visible) continue; bool mouse_in_scrollbar = AreOverlapping(mouse, it->scrollbar_rect); if (mouse_in_scrollbar) { ScrollbarSelected = it->id; View *view = GetView(it->active_view); Vec2 mouse_vec2 = MouseVec2(); Scroller s = ComputeScrollerRect(it); double size_y = (double)GetSize(it->scrollbar_rect).y; double p = mouse_vec2.y - it->scrollbar_rect.min.y; if (mouse_vec2.y < s.rect.min.y || mouse_vec2.y > s.rect.max.y) { view->scroll.y = (Int)(p / size_y * (double)s.line_count * (double)it->font->line_spacing); it->mouse_scroller_offset = -(double)GetSize(s.rect).y / 2.0 / size_y; } else { it->mouse_scroller_offset = (s.rect.min.y - p) / size_y; } break; } } } BSet active = GetBSet(ActiveWindowID); bool executed = false; For (active.view->commands) { if (it.trigger && MatchEvent(it.trigger, &event)) { ProfileScopeEx(it.name); it.function(); executed = true; break; } } if (executed == false) { For (GlobalCommands) { if (it.trigger && MatchEvent(it.trigger, &event)) { ProfileScopeEx(it.name); it.function(); } } } if (event.kind == EVENT_TEXT_INPUT) { String16 string16 = ToString16(scratch, event.text); Replace(active.view, string16); } if (event.kind == EVENT_QUIT) { CMD_Quit(); } if (event.kind == EVENT_KEY_PRESS && event.key == SDLK_ESCAPE && event.ctrl == false && event.shift == false && event.alt == false && event.super == false) { if (active.window->lose_focus_on_escape && active.window->id == ActiveWindowID) { if (active.window->primary) { // } else { NextActiveWindowID = PrimaryWindowID; } } For (Windows) { if (it->lose_visibility_on_escape && it->visible) { it->visible = false; } } } #if SLOW_BUILD BSet main = GetBSet(PrimaryWindowID); AssertRanges(main.view->carets); AssertRanges(active.view->carets); #endif } void EvalCommand(String command) { BSet active = GetBSet(ActiveWindowID); For (active.view->commands) { if (it.name == command) { ProfileScopeEx(it.name); it.function(); return; } } For (GlobalCommands) { if (it.name == command) { ProfileScopeEx(it.name); it.function(); return; } } ReportErrorf("Failed to match with any of the commands: %S", command); } void EvalCommand(String16 command) { Scratch scratch; EvalCommand(ToString(scratch, command)); } void GarbageCollect() { if (RunGCThisFrame == false) { return; } RunGCThisFrame = false; ProfileFunction(); Allocator sys_allocator = GetSystemAllocator(); IterRemove(Views) { IterRemovePrepare(Views); if (it->close && it->id.id == 0) { InvalidCodepath(); } Buffer *buffer = GetBuffer(it->active_buffer, NULL); if (buffer == NULL || buffer->close) { it->close = true; } if (!it->close) { if (!buffer->temp) { continue; } bool ref = ViewIsReferenced(it); if (ref) { continue; } if (ProcessIsActive(it->id)) { continue; } } #if PLUGIN_RECORD_GC RawAppendf(GCInfoBuffer, "View %d %S\n", (int)it->id.id, buffer ? buffer->name : String{"NULL"}); #endif Assert(it->special == false); Dealloc(&it->commands); Dealloc(it); remove_item = true; } IterRemove(Buffers) { IterRemovePrepare(Buffers); if (it->close && it->id.id == 0) { InvalidCodepath(); } if (!it->close) { if (!it->temp) { continue; } bool ref = BufferIsReferenced(it); if (ref) { continue; } } #if PLUGIN_RECORD_GC RawAppendf(GCInfoBuffer, "Buff %d %S\n", (int)it->id.id, it->name); #endif Assert(it->special == false); remove_item = true; DeallocBuffer(it); } IterRemove(Windows) { IterRemovePrepare(Windows); if (it->close && it->id.id == 0) { InvalidCodepath(); } if (it->close) { #if PLUGIN_RECORD_GC RawAppendf(GCInfoBuffer, "Wind %d %d %d %d %d\n", (int)it->id.id, (int)it->total_rect.min.x, (int)it->total_rect.min.y, (int)it->total_rect.max.x, (int)it->total_rect.max.y); #endif Dealloc(&it->goto_history); Dealloc(&it->goto_redo); Dealloc(&it->commands); Dealloc(sys_allocator, it); remove_item = true; } else { View *view = GetView(it->active_view, NULL); if (!view) { JumpToLastValidView(it); } } } } void LayoutWindows(int16_t wx, int16_t wy) { ProfileFunction(); Rect2I screen_rect = RectI0Size(wx, wy); #if PLUGIN_STATUS_WINDOW LayoutStatusWindow(&screen_rect, wx, wy); #endif #if PLUGIN_SEARCH_WINDOW LayoutSearchWindow(&screen_rect, wx, wy); #endif #if PLUGIN_COMMAND_WINDOW LayoutCommandWindow(&screen_rect, wx, wy); #endif #if PLUGIN_DEBUG_WINDOW LayoutDebugWindow(&screen_rect, wx, wy); #endif #if PLUGIN_BUILD_WINDOW LayoutBuildWindow(&screen_rect, wx, wy); #endif // Column layout Int c = 0; double size = WindowCalcEvenResizerValue(wx, &c); if (c == 0) { return; } int i = 0; ForItem(n, Windows) { if (!n->primary) { continue; } n->total_rect = n->document_rect = CutLeft(&screen_rect, (Int)(size * n->weight)); if (i != (c - 1)) { Int resizer_size = (Int)(PrimaryFont.char_spacing*0.5f); n->resizer_rect = CutRight(&n->document_rect, resizer_size); } else { n->resizer_rect = {}; } CalcNiceties(n); i += 1; } } void Update(Event event) { ProfileFunction(); LayoutWindows(event.xwindow, event.ywindow); { Scratch scratch; Array order = GetWindowZOrder(scratch); For(order) { if (!it->visible) { continue; } View *view = GetView(it->active_view); view->main_caret_on_begin_frame = view->carets[0]; view->update_scroll = true; } } For (Windows) { if (it->jump_history) { View *view = GetView(it->active_view); it->begin_frame_crumb = {it->active_view, view->carets[0], GetTimeSeconds()}; } } OnCommand(event); #if PLUGIN_DEBUG_WINDOW UpdateDebugWindow(); #endif #if PLUGIN_STATUS_WINDOW UpdateStatusWindow(); #endif #if PLUGIN_SEARCH_WINDOW UpdateSearchWindow(); #endif // ActiveViewHook { BSet set = GetBSet(ActiveWindowID); if (set.view->update_hook) { set.view->update_hook(); } } UpdateProcesses(); UpdateCoroutines(&event); For (Windows) { if (!it->visible) continue; View *view = GetView(it->active_view); UpdateScroll(it, !AreEqual(view->main_caret_on_begin_frame, view->carets[0]) && view->update_scroll); } { ProfileScope(UpdateWindows); For (Windows) { if (it->jump_history) { View *view = GetView(it->active_view); bool should_checkpoint = true; if (it->skip_checkpoint) { should_checkpoint = false; } if (should_checkpoint && it->begin_frame_crumb.view_id == it->active_view) { if (AreEqual(view->carets[0], it->begin_frame_crumb.caret)) { should_checkpoint = false; } Buffer *buffer = GetBuffer(view->active_buffer); if (PosToLine(buffer, GetFront(view->main_caret_on_begin_frame)) == PosToLine(buffer, GetFront(view->carets[0]))) { should_checkpoint = false; } } if (should_checkpoint) { Add(&it->goto_history, it->begin_frame_crumb); it->goto_redo.len = 0; } it->skip_checkpoint = false; } if (it->sync_visibility_with_focus) { if (it->id == NextActiveWindowID) { it->visible = true; } else { it->visible = false; } } } { ActiveWindowID = NextActiveWindowID; Window *window = GetWindow(ActiveWindowID, NULL); if (window == NULL || window->visible == false) { ActiveWindowID = NextActiveWindowID = PrimaryWindowID; window = GetWindow(ActiveWindowID, NULL); if (window == NULL) { ActiveWindowID = NullWindowID; Assert(GetWindow(ActiveWindowID, NULL)); } } } // Behavior where these windows cannot be visible at the same time { WindowID id[] = { {-1}, // This is just so that we have a valid array if all windows are disabled #if PLUGIN_BUILD_WINDOW BuildWindowID, #endif #if PLUGIN_COMMAND_WINDOW CommandWindowID, #endif #if PLUGIN_SEARCH_WINDOW SearchWindowID, #endif }; for (int i = 1; i < Lengthof(id); i += 1) { if (ActiveWindowID == id[i]) { for (int j = 1; j < Lengthof(id); j += 1) { if (i == j) continue; Window *window = GetWindow(id[j]); window->visible = false; } } } } if (ActiveWindowID != PrimaryWindowID) { Window *window = GetWindow(ActiveWindowID, NULL); if (window->primary) { PrimaryWindowID = ActiveWindowID; } } } // IS THIS ENOUGH? Previously reopened everything For (Windows) { BSet set = GetBSet(it); TryReopeningWhenModified(set.buffer); } GarbageCollect(); } Array FrameEvents; void MainLoop() { ProfileFunction(); FrameID += 1; FrameEvents.len = 0; GetEventsForFrame(&FrameEvents); For (FrameEvents) { if (RecordingMacro) { Add(&MacroPlayback, it); } #if PLUGIN_RECORD_EVENTS if (it.kind != EVENT_UPDATE && !Testing) { Serialize(EventBuffer, &it); } #endif if (it.xwindow == 0 || it.ywindow == 0) { int xwindow, ywindow; SDL_GetWindowSizeInPixels(SDLWindow, &xwindow, &ywindow); it.xwindow = xwindow; it.ywindow = ywindow; } Update(it); } bool dont_wait_until_resolved = false; For (ActiveCoroutines) { if (it.dont_wait_until_resolved) { dont_wait_until_resolved = true; break; } } WaitForEventsState = WaitForEvents; if (IsDocumentSelectionValid() || IsScrollbarSelectionValid() || ActiveProcesses.len || dont_wait_until_resolved || MouseMiddleAnchor.x != 0) { WaitForEventsState = false; } // This shouldn't matter to the state of the program, only appearance for // the user { Scratch scratch; ProfileScope(SetWindowTitle); Window *window = GetActiveWind(); View *view = GetView(window->active_view); Buffer *buffer = GetBuffer(view->active_buffer); const char *dirty = buffer->dirty ? " !" : ""; String string = Format(scratch, "%S%s", buffer->name, dirty); SDL_SetWindowTitle(SDLWindow, string.data); } Event *event = GetLast(FrameEvents); SetMouseCursor(*event); LayoutWindows(event->xwindow, event->ywindow); // This is here to render changes in title bar size without a frame of delay BeginFrameRender(event->xwindow, event->ywindow); { Scratch scratch; Array order = GetWindowZOrder(scratch); For(IterateInReverse(&order)) { if (!it->visible) continue; DrawWindow(it, *GetLast(FrameEvents)); } } EndFrameRender(event->xwindow, event->ywindow, BackgroundColor); SDL_GL_SwapWindow(SDLWindow); } #if OS_WINDOWS int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) #else extern char **environ; int main(int argc, char **argv, char **envp) #endif { #if OS_WINDOWS int argc = __argc; char **argv = __argv; AttachConsole(ATTACH_PARENT_PROCESS); #endif InitOS(ReportErrorf); InitScratch(); ProjectFolder = GetWorkingDir(Perm); HomeFolder = SDL_GetUserFolder(SDL_FOLDER_HOME); { String sdl_config_path = SDL_GetPrefPath("krzosa", "text_editor"); if (sdl_config_path.len && sdl_config_path.data[sdl_config_path.len - 1] == '\\') { sdl_config_path = Chop(sdl_config_path, 1); // chop '/' } if (sdl_config_path.len && sdl_config_path.data[sdl_config_path.len - 1] == '/') { sdl_config_path = Chop(sdl_config_path, 1); // chop '/' } ConfigFolder = NormalizePath(Perm, sdl_config_path); SDL_free(sdl_config_path.data); } #if OS_WINDOWS { wchar_t *p = GetEnvironmentStringsW(); for (;p && p[0];) { String16 string16((char16_t *)p); String string = ToString(Perm, string16); Add(&ProcessEnviroment, string); p += string16.len + 1; } // FreeEnvironmentStringsW(p); // I get a trap here? why? } #else char **env = envp; if (!env || !env[0]) { env = environ; } if (env && env[0]) { for (int i = 0; env[i]; i += 1) { Add(&ProcessEnviroment, Copy(Perm, env[i])); } } else { ReportErrorf("No environment variables found (envp/environ empty)"); } #endif if (1) { RunArenaTest(); For (TestFunctions) { it.function(); } // ReportErrorf("Testing DONE\n"); // return 0; } if (!SDL_Init(SDL_INIT_VIDEO)) { ReportErrorf("Couldn't initialize SDL! %s", SDL_GetError()); return 1; } SDL_EnableScreenSaver(); SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); #if OS_WASM SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #else SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5); #endif SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); // int w8 = (int)(display_mode->w * 0.8); // int h8 = (int)(display_mode->h * 0.8); #if DEBUG_BUILD int whalf = 1000; int hhalf = 1000; int xhalf = 100; int yhalf = 100; #else SDL_DisplayID primary_display_id = SDL_GetPrimaryDisplay(); const SDL_DisplayMode *display_mode = SDL_GetCurrentDisplayMode(primary_display_id); int whalf = (int)(display_mode->w * 0.5) - 10; int hhalf = (int)(display_mode->h) - 120; int xhalf = whalf; int yhalf = 30; #endif Uint32 window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY; SDLWindow = SDL_CreateWindow("Text editor", whalf, hhalf, window_flags); if (SDLWindow == NULL) { ReportErrorf("Couldn't create window! %s", SDL_GetError()); return 1; } SDL_SetWindowPosition(SDLWindow, xhalf, yhalf); SDL_WindowGLContext = SDL_GL_CreateContext(SDLWindow); SDL_GL_MakeCurrent(SDLWindow, SDL_WindowGLContext); SDL_ShowWindow(SDLWindow); // Set icon { uint32_t data = 0xddddddff; SDL_Surface *surface = SDL_CreateSurfaceFrom(1, 1, SDL_PIXELFORMAT_RGBA8888, &data, sizeof(uint32_t)); SDL_SetWindowIcon(SDLWindow, surface); SDL_DestroySurface(surface); } if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) { ReportErrorf("Couldn't load OpenGL! %s", SDL_GetError()); return 1; } SDL_StartTextInput(SDLWindow); SDL_SetEventEnabled(SDL_EVENT_DROP_FILE, true); SDL_GL_SetSwapInterval(1); // vsync { float scale = SDL_GetWindowDisplayScale(SDLWindow); if (scale != 1.0f) DPIScale = scale; } // InitBuffers { Allocator sys_allocator = GetSystemAllocator(); Scratch scratch; Buffer *null_buffer = CreateBuffer(sys_allocator, Format(scratch, "%S/scratch", ProjectFolder)); null_buffer->special = true; View *null_view = CreateView(null_buffer->id); null_view->special = true; Assert(null_buffer->id == NullBufferID && null_view->id == NullViewID); Buffer *logs_buffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectFolder, "logs", "")); logs_buffer->special = true; View *logs_view = CreateView(logs_buffer->id); logs_view->special = true; LogBuffer = logs_buffer; LogView = logs_view; #if PLUGIN_RECORD_GC GCInfoBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectFolder, "gc")); GCInfoBuffer->special = true; GCInfoBuffer->no_history = true; #endif #if PLUGIN_RECORD_EVENTS EventBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectFolder, "events")); EventBuffer->no_history = true; EventBuffer->special = true; #endif } InitRender(); ReloadFont(PathToFont, (U32)FontSize); CreateWind(); ReopenBuffer(GetBuffer(NullBufferID)); InitOS(ReportErrorf); For (GlobalCommands) { if (it.binding.len != 0) { it.trigger = ParseKeyCached(it.binding); } } EnterKey = ParseKeyCached("enter"); EscapeKey = ParseKeyCached("escape"); OpenKeySet = ParseKeyCached("ctrl-q | enter | f12"); EnterOrEscapeKeySet = ParseKeyCached("escape | enter"); AltEnterKeySet = ParseKeyCached("alt-enter"); ShiftEnterKeySet = ParseKeyCached("shift-enter"); CheckKeybindingColission(); #if PLUGIN_CONFIG { Scratch scratch; GlobalConfigBufferID = LoadConfig(Format(scratch, "%S/config.te", ConfigFolder)); } #endif for (int i = 1; i < argc; i += 1) { String it = argv[i]; #if PLUGIN_CONFIG if (EndsWith(it, ".te")) { LoadConfig(it); } #endif Open(argv[i]); } #if PLUGIN_LOAD_VCVARS LoadVCVars(); #endif #if PLUGIN_STATUS_WINDOW InitStatusWindow(); #endif #if PLUGIN_COMMAND_WINDOW InitCommandWindow(); #endif #if PLUGIN_DEBUG_WINDOW InitDebugWindow(); #endif #if PLUGIN_BUILD_WINDOW InitBuildWindow(); #endif #if PLUGIN_SEARCH_WINDOW InitSearchWindow(); #endif #if OS_WASM emscripten_set_main_loop(MainLoop, 0, 1); #else while (AppIsRunning) { MainLoop(); } #endif #if PLUGIN_REMEDYBG QuitDebugger(); #endif CleanupRender(); SDL_DestroyWindow(SDLWindow); SDL_Quit(); return 0; }