937 lines
30 KiB
C++
937 lines
30 KiB
C++
#include "basic/basic.h"
|
|
#include "basic/basic.cpp"
|
|
#include "profiler.h"
|
|
|
|
#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"
|
|
|
|
#include "buffer.h"
|
|
#include "view.h"
|
|
#include "window.h"
|
|
#include "text_editor.h"
|
|
|
|
#include "globals.cpp"
|
|
#include "coroutines.cpp"
|
|
#include "buffer.cpp"
|
|
#include "view.cpp"
|
|
#include "window.cpp"
|
|
#include "window_command.cpp"
|
|
#include "window_debug.cpp"
|
|
#include "window_status.cpp"
|
|
#include "window_search.cpp"
|
|
#include "window_build.cpp"
|
|
|
|
#include "process.cpp"
|
|
#include "event.cpp"
|
|
#include "config.cpp"
|
|
|
|
#include "commands.cpp"
|
|
#include "commands_clipboard.cpp"
|
|
|
|
#include "scratch.cpp"
|
|
#include "draw.cpp"
|
|
#include "test/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) {
|
|
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 UpdateScroll(Window *window, bool update_caret_scrolling) {
|
|
ProfileFunction();
|
|
BSet set = GetBSet(window);
|
|
|
|
// Scrolling with caret
|
|
if (update_caret_scrolling) {
|
|
Caret c = set.view->carets[0];
|
|
Int front = GetFront(c);
|
|
XY xy = PosToXY(set.buffer, front);
|
|
|
|
Rect2I visible = GetVisibleCells(window);
|
|
Vec2I visible_cells = GetSize(visible);
|
|
Vec2I visible_size = visible_cells * Vec2I{window->font->char_spacing, window->font->line_spacing};
|
|
Vec2I rect_size = GetSize(window->document_rect);
|
|
|
|
if (xy.line >= visible.max.y - 2) {
|
|
Int set_view_at_line = xy.line - (visible_cells.y - 1);
|
|
Int cut_off_y = Max((Int)0, visible_size.y - rect_size.y);
|
|
set.view->scroll.y = (set_view_at_line * window->font->line_spacing) + cut_off_y;
|
|
}
|
|
|
|
if (xy.line < visible.min.y + 1) {
|
|
set.view->scroll.y = xy.line * window->font->line_spacing;
|
|
}
|
|
|
|
if (xy.col >= visible.max.x - 1) {
|
|
Int set_view_at_line = xy.col - (visible_cells.x - 1);
|
|
Int cut_off_x = Max((Int)0, visible_size.x - rect_size.x);
|
|
set.view->scroll.x = (set_view_at_line * window->font->char_spacing) + cut_off_x;
|
|
}
|
|
|
|
if (xy.col <= visible.min.x) {
|
|
set.view->scroll.x = xy.col * window->font->char_spacing;
|
|
}
|
|
}
|
|
|
|
// Clip scroll
|
|
{
|
|
Int last_line = LastLine(set.buffer);
|
|
set.view->scroll.y = Clamp(set.view->scroll.y, (Int)0, Max((Int)0, (last_line - 1) * window->font->line_spacing));
|
|
|
|
// @note:
|
|
// GetCharCountOfLongestLine is a bottleneck, there is probably an algorithm for
|
|
// calculating this value incrementally but do we even need X scrollbar or x clipping?
|
|
set.view->scroll.x = ClampBottom(set.view->scroll.x, (Int)0);
|
|
}
|
|
}
|
|
|
|
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()) {
|
|
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;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (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 (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 (Alt()) Insert(&active.view->carets, MakeCaret(p, p), 0);
|
|
if (!Alt() && !Shift()) active.view->carets.len = 1;
|
|
|
|
Caret &caret = active.view->carets[0];
|
|
if (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 = EncloseLoadWord(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 main = GetBSet(PrimaryWindowID);
|
|
BSet active = GetBSet(ActiveWindowID);
|
|
|
|
bool executed = false;
|
|
For (active.view->hooks) {
|
|
if (it.trigger && MatchEvent(it.trigger, &event)) {
|
|
ProfileScopeEx(it.name);
|
|
it.function();
|
|
LastExecutedManualCommand = it.function;
|
|
executed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (executed == false) {
|
|
For (CommandFunctions) {
|
|
if (it.trigger && MatchEvent(it.trigger, &event)) {
|
|
ProfileScopeEx(it.name);
|
|
it.function();
|
|
LastExecutedManualCommand = it.function;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (event.kind == EVENT_DROP_FILE) {
|
|
WindowOpenBufferView(active.window, event.text);
|
|
}
|
|
|
|
if (event.kind == EVENT_TEXT_INPUT) {
|
|
String16 string16 = ToString16(scratch, event.text);
|
|
Replace(active.view, string16);
|
|
}
|
|
|
|
if (event.kind == EVENT_QUIT) {
|
|
CMD_Quit();
|
|
}
|
|
|
|
IF_DEBUG(AssertRanges(main.view->carets));
|
|
IF_DEBUG(AssertRanges(active.view->carets));
|
|
}
|
|
|
|
void EvalCommand(String command) {
|
|
BSet active = GetBSet(ActiveWindowID);
|
|
For (active.view->hooks) {
|
|
if (it.name == command) {
|
|
ProfileScopeEx(it.name);
|
|
it.function();
|
|
return;
|
|
}
|
|
}
|
|
For (CommandFunctions) {
|
|
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();
|
|
|
|
For(Buffers) {
|
|
if (it->file_mod_time) {
|
|
int64_t new_file_mod_time = GetFileModTime(it->name);
|
|
if (it->file_mod_time != new_file_mod_time) {
|
|
it->changed_on_disk = true;
|
|
if (it->dirty == false) {
|
|
ReopenBuffer(it);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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->id);
|
|
if (ref) {
|
|
continue;
|
|
}
|
|
|
|
if (ProcessIsActive(it->id)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
RawAppendf(GCInfoBuffer, "View %d %S\n", (int)it->id.id, buffer ? buffer->name : String{"NULL"});
|
|
remove_item = true;
|
|
Dealloc(it);
|
|
}
|
|
|
|
IterRemove(Buffers) {
|
|
IterRemovePrepare(Buffers);
|
|
|
|
if (it->close && it->id.id == 0) {
|
|
InvalidCodepath();
|
|
}
|
|
|
|
if (!it->close) {
|
|
if (!it->temp) {
|
|
continue;
|
|
}
|
|
|
|
bool ref = BufferIsReferenced(it->id);
|
|
if (ref) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
RawAppendf(GCInfoBuffer, "Buff %d %S\n", (int)it->id.id, it->name);
|
|
remove_item = true;
|
|
DeallocBuffer(it);
|
|
}
|
|
|
|
IterRemove(Windows) {
|
|
IterRemovePrepare(Windows);
|
|
if (it->close && it->id.id == 0) {
|
|
InvalidCodepath();
|
|
}
|
|
|
|
if (it->close) {
|
|
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);
|
|
Dealloc(&it->goto_history);
|
|
Dealloc(&it->goto_redo);
|
|
Dealloc(sys_allocator, it);
|
|
remove_item = true;
|
|
} else {
|
|
View *view = FindView(it->active_view, NULL);
|
|
if (!view) {
|
|
JumpToLastValidView(it);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
StatusWindowUpdate();
|
|
DebugWindowUpdate();
|
|
FuzzySearchViewUpdate();
|
|
SearchWindowUpdate();
|
|
UpdateProcesses();
|
|
CoUpdate(&event);
|
|
|
|
For(IterateInReverse(&order)) {
|
|
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);
|
|
}
|
|
|
|
// We update it here despite the name to make it sure that all the possible changes are
|
|
// included albeit with delayed response. If we did this at the beginning of the frame
|
|
// and the DebugWindowUpdated we wouldnt get to know that in the OnCommand.
|
|
For (Buffers) {
|
|
it->begin_frame_change_id = it->change_id;
|
|
}
|
|
|
|
{
|
|
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[] = {BuildWindowID, CommandWindowID, SearchWindowID};
|
|
for (int i = 0; i < Lengthof(id); i += 1) {
|
|
if (ActiveWindowID == id[i]) {
|
|
for (int j = 0; 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
GarbageCollect();
|
|
}
|
|
|
|
void Windows_SetupVCVarsall(mco_coro *co) {
|
|
View *view = NULL;
|
|
{
|
|
Scratch scratch;
|
|
String working_dir = WorkDir;
|
|
String buffer_name = GetUniqueBufferName(working_dir, "vcvarsall-");
|
|
String cmd = Format(scratch, "\"%S\" && set", WindowsVCVarsPathToLoadDevEnviroment);
|
|
view = ExecHidden(buffer_name, cmd, working_dir);
|
|
}
|
|
for (;;) {
|
|
if (!ProcessIsActive(view->id)) {
|
|
break;
|
|
}
|
|
CoYield(co);
|
|
}
|
|
|
|
Scratch scratch;
|
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
|
String16 string16 = GetString(buffer);
|
|
String string = ToString(scratch, string16);
|
|
Array<String> lines = Split(scratch, string, "\n");
|
|
For (lines) {
|
|
String s = Trim(it);
|
|
Array<String> values = Split(scratch, s, "=");
|
|
if (values.len == 1) continue;
|
|
Add(&ProcessEnviroment, Copy(GetSystemAllocator(), s));
|
|
}
|
|
}
|
|
|
|
void MainLoop() {
|
|
ProfileFunction();
|
|
Scratch scratch;
|
|
FrameID += 1;
|
|
Array<Event> frame_events = GetEventsForFrame(scratch);
|
|
Serializer ser = {EventBuffer};
|
|
For(frame_events) {
|
|
if (it.kind != 1) {
|
|
if (!Testing) Serialize(&ser, &it);
|
|
}
|
|
|
|
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
|
|
{
|
|
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(frame_events);
|
|
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);
|
|
|
|
Array<Window *> order = GetWindowZOrder(scratch);
|
|
For(IterateInReverse(&order)) {
|
|
if (!it->visible) continue;
|
|
DrawWindow(it, *GetLast(frame_events));
|
|
}
|
|
EndFrameRender(event->xwindow, event->ywindow, BackgroundColor);
|
|
SDL_GL_SwapWindow(SDLWindow);
|
|
}
|
|
|
|
#if _WIN32
|
|
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
|
|
#else
|
|
extern char **environ;
|
|
int main(int argc, char **argv)
|
|
#endif
|
|
{
|
|
InitScratch();
|
|
InitOS((OSErrorReport *)printf);
|
|
#if _WIN32
|
|
int argc = __argc;
|
|
char **argv = __argv;
|
|
AttachConsole(ATTACH_PARENT_PROCESS);
|
|
#endif
|
|
BeginProfiler();
|
|
|
|
if (1) {
|
|
RunArenaTest();
|
|
For (TestFunctions) {
|
|
it.function();
|
|
}
|
|
|
|
// ReportErrorf("Testing DONE\n");
|
|
// return 0;
|
|
}
|
|
|
|
#if !OS_WINDOWS
|
|
for (int i = 0; environ[i]; i += 1) {
|
|
Add(&ProcessEnviroment, Copy(Perm, environ[i]));
|
|
}
|
|
#endif
|
|
|
|
WorkDir = GetWorkingDir(Perm);
|
|
{
|
|
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 '/'
|
|
}
|
|
ConfigDir = NormalizePath(Perm, sdl_config_path);
|
|
SDL_free(sdl_config_path.data);
|
|
}
|
|
|
|
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);
|
|
|
|
SDL_DisplayID primary_display_id = SDL_GetPrimaryDisplay();
|
|
const SDL_DisplayMode *display_mode = SDL_GetCurrentDisplayMode(primary_display_id);
|
|
|
|
// int w8 = (int)(display_mode->w * 0.8);
|
|
// int h8 = (int)(display_mode->h * 0.8);
|
|
|
|
int whalf = (int)(display_mode->w * 0.5) - 10;
|
|
int hhalf = (int)(display_mode->h) - 120;
|
|
int xhalf = whalf;
|
|
int yhalf = 30;
|
|
|
|
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();
|
|
InitRender();
|
|
ReloadFont(PathToFont, (U32)FontSize);
|
|
InitWindows();
|
|
InitOS(ReportWarningf);
|
|
|
|
For (CommandFunctions) {
|
|
if (it.binding.len != 0) {
|
|
it.trigger = ParseKeyCached(it.binding);
|
|
}
|
|
}
|
|
|
|
String project_config = {};
|
|
for (int i = 1; i < argc; i += 1) {
|
|
String it = argv[i];
|
|
if (EndsWith(it, ".te")) {
|
|
project_config = it;
|
|
}
|
|
Open(argv[i]);
|
|
}
|
|
|
|
Scratch scratch;
|
|
GlobalConfigBufferID = LoadConfig(Format(scratch, "%S/config.te", GetExeDir(scratch)));
|
|
if (project_config.len) {
|
|
LoadConfig(project_config);
|
|
}
|
|
|
|
ReportConsolef(":Set WorkDir '%S'", WorkDir);
|
|
if (Testing) InitTests();
|
|
#if OS_WINDOWS
|
|
CoRemove("Windows_SetupVCVarsall");
|
|
CoData *co_data = CoAdd(Windows_SetupVCVarsall);
|
|
co_data->dont_wait_until_resolved = true;
|
|
CoResume(co_data);
|
|
#endif
|
|
#if OS_WASM
|
|
emscripten_set_main_loop(MainLoop, 0, 1);
|
|
#else
|
|
while (AppIsRunning) {
|
|
MainLoop();
|
|
}
|
|
#endif
|
|
|
|
SDL_DestroyWindow(SDLWindow);
|
|
SDL_Quit();
|
|
|
|
EndProfiler();
|
|
|
|
return 0;
|
|
}
|