Files
text_editor/src/text_editor.cpp

1133 lines
36 KiB
C++

/*
- [x] BRO, the caret teleports on linux when I press the arrow for too long
- [ ] Report SDL newest vs SDL previous version on wayland
- [ ] 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.
- [ ] 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
#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"
#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) {
static SDL_Cursor *SDL_MouseCursor;
static SDL_SystemCursor last_id;
if (SDL_MouseCursor == NULL || last_id != id) {
if (SDL_MouseCursor != NULL) {
SDL_DestroyCursor(SDL_MouseCursor);
}
SDL_MouseCursor = SDL_CreateSystemCursor(id);
SDL_SetCursor(SDL_MouseCursor);
last_id = id;
}
}
#endif
void SetMouseCursor(Event event) {
ProfileFunction();
Scratch scratch;
Array<Window *> 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() {
#if PLUGIN_REMEDYBG
QuitDebugger();
#endif
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<Window *> 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 (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);
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<Window *> 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<Event> FrameEvents;
void MainLoop() {
ProfileFunction();
FrameID += 1;
FrameEvents.len = 0;
GetEventsForFrame(&FrameEvents);
For (FrameEvents) {
#if PLUGIN_RECORD_EVENTS
if (it.kind != EVENT_UPDATE) {
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<Window *> 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
InitScratch();
InitOS(ReportErrorf);
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_GLContext gl_context = SDL_GL_CreateContext(SDLWindow);
SDL_GL_MakeCurrent(SDLWindow, gl_context);
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 (Testing) InitTests();
#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
SDL_DestroyWindow(SDLWindow);
SDL_Quit();
return 0;
}