1133 lines
36 KiB
C++
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;
|
|
}
|