BSet GetBSet(Window *window) {
    BSet set   = {window};
    set.view   = GetView(set.window->active_view);
    set.buffer = GetBuffer(set.view->active_buffer);
    return set;
}

BSet GetBSet(WindowID window_id) {
    Window *window = GetWindow(window_id);
    BSet    result = GetBSet(window);
    return result;
}

BSet GetConsoleSet() {
    BSet result = {};
    result.window = GetWindow(NullWindowID);
    result.view = GetView(NullViewID);
    result.buffer = GetBuffer(NullBufferID);
    return result;
}

String GetDir(Buffer *buffer) {
    if (buffer->is_dir) {
        return buffer->name;
    } else {
        return ChopLastSlash(buffer->name);
    }
}

String GetMainDir() {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    return GetDir(main.buffer);
}

void JumpGarbageBuffer(BSet *set, String buffer_name = "") {
    if (buffer_name.len == 0) {
        buffer_name = GetUniqueBufferName(GetDir(set->buffer), "temp");
    }
    set->view = WindowOpenBufferView(set->window, buffer_name);
    set->buffer = GetBuffer(set->view->active_buffer);
    set->buffer->garbage = true;
}

void MouseLoadWord(Event event, String meta = "") {
    Vec2I mouse  = MouseVec2I();
    BSet active = GetBSet(ActiveWindowID);

    bool mouse_in_document = AreOverlapping(mouse, active.window->document_rect);
    if (mouse_in_document) {
        Int p = ScreenSpaceToBufferPosErrorOutOfBounds(active.window, active.view, active.buffer, mouse);
        if (p != -1) {
            Range enclose = EncloseLoadWord(active.buffer, p);
            if (InBounds(active.view->carets[0].range, p)) enclose = active.view->carets[0].range;
            String16 string = GetString(active.buffer, enclose);

            active.view->carets.len = 1;
            active.view->carets[0]  = MakeCaret(p);
            Open(string, meta);
        }
    }
}

View *GetViewForFixingWhenBufferCommand(Buffer *buffer, bool *is_active = NULL) {
    View *view = NULL;
    if (is_active) {
        *is_active = false;
    }

    BSet active = GetBSet(ActiveWindowID);
    if (active.buffer->id == buffer->id) {
        if (is_active) {
            *is_active = true;
        }
        return active.view;
    }

    For(Views) {
        if (it->active_buffer != buffer->id) {
            continue;
        }
        view = it;
        break;
    }

    if (!view) {
        view = CreateView(buffer->id);
    }

    return view;
}

void ReplaceWithoutMovingCarets(Buffer *buffer, Range range, String16 string) {
    View *view = GetViewForFixingWhenBufferCommand(buffer);
    Array<Caret> carets = Copy(GetSystemAllocator(), view->carets);

    Scratch scratch;
    SelectRange(view, range);
    ReplaceEx(scratch, view, string);

    Dealloc(&view->carets);
    view->carets = carets;
}

// @todo: revamp interface since it scrolls ALL VIEWS??? or maybe not??
void Append(View *view, String16 string, bool scroll_to_end_if_cursor_on_last_line) {
    Scratch scratch;
    Buffer *buffer = GetBuffer(view->active_buffer);

    struct ViewInfo {
        View *view;
        Array<Caret> carets;
        bool scroll_to_end;
    };

    Array<ViewInfo> view_info = {scratch};
    ForItem(it_view, Views) {
        if (it_view->active_buffer != buffer->id) {
            continue;
        }

        ViewInfo vi = {it_view};
        if (scroll_to_end_if_cursor_on_last_line) {
            Int line = PosToLine(buffer, GetFront(it_view->carets[0]));
            if (line == buffer->line_starts.len - 1) {
                vi.scroll_to_end = true;
            }
        }

        if (!vi.scroll_to_end) {
            vi.carets = Copy(GetSystemAllocator(), it_view->carets);
        }

        Add(&view_info, vi);
    }

    SelectRange(view, GetBufferEndAsRange(buffer));
    Replace(view, string);

    For (view_info) {
        if (it.scroll_to_end) {
            it.view->carets[0] = MakeCaret(GetBufferEndAsRange(buffer).min);
        } else {
            Dealloc(&it.view->carets);
            it.view->carets = it.carets;
        }
    }
}

void Append(View *view, String string, bool scroll_to_end_if_cursor_on_last_line) {
    Scratch  scratch;
    String16 string16 = ToString16(scratch, string);
    Append(view, string16, scroll_to_end_if_cursor_on_last_line);
}

void Appendf(View *view, const char *fmt, ...) {
    Scratch scratch;
    STRING_FORMAT(scratch, fmt, string);
    Append(view, string, true);
}

void UIMessagef(const char *fmt, ...) {
    Scratch scratch;
    STRING_FORMAT(scratch, fmt, string);
    BSet main = GetBSet(LastActiveLayoutWindowID);
    JumpGarbageBuffer(&main);
    NextActiveWindowID = main.window->id;
    RawAppendf(main.buffer, "\n    %S\n", string);
}

void ReportErrorf(const char *fmt, ...) {
    Scratch scratch;
    STRING_FORMAT(scratch, fmt, string);
    View *view = GetView(NullViewID);
    if (view) {
        Appendf(view, "%S\n", string);
        NextActiveWindowID = NullWindowID;
    }
    UIMessagef("%S", string);
}

void ReportConsolef(const char *fmt, ...) {
    Scratch scratch;
    STRING_FORMAT(scratch, fmt, string);
    View *view = GetView(NullViewID);
    Appendf(view, "%S\n", string);
}

void ReportWarningf(const char *fmt, ...) {
    Scratch scratch;
    STRING_FORMAT(scratch, fmt, string);
    View *null_view = GetView(NullViewID);
    Appendf(null_view, "%S\n", string);
}

void ReportDebugf(const char *fmt, ...) {
    Scratch scratch;
    STRING_FORMAT(scratch, fmt, string);
    RawAppendf(TraceBuffer, "%S\n", string);
}

void MoveCursorByPageSize(Window *window, int direction, bool shift = false) {
    Assert(direction == DIR_UP || direction == DIR_DOWN);
    BSet set = GetBSet(window);

    Rect2I visible_cells_rect = GetVisibleCells(window);
    Int    y                  = GetSize(visible_cells_rect).y - 2;
    if (direction == DIR_UP) y = -y;

    For(set.view->carets) {
        XY xy = PosToXY(set.buffer, GetFront(it));
        if (direction == DIR_DOWN && xy.line == set.buffer->line_starts.len - 1) {
            Range line_range = GetLineRange(set.buffer, xy.line);
            xy.col           = line_range.max - line_range.min;
        } else if (direction == DIR_UP && xy.line == 0) {
            xy.col = 0;
        }
        xy.line += y;

        Int pos = XYToPos(set.buffer, xy);
        if (shift) {
            it = SetFront(it, pos);
        } else {
            it = MakeCaret(pos);
        }
    }
}

void TrimTrailingWhitespace(Buffer *buffer, bool trim_lines_with_caret = false) {
    Scratch scratch;

    bool is_active_view = false;
    View *view = GetViewForFixingWhenBufferCommand(buffer, &is_active_view);
    if (!is_active_view && !trim_lines_with_caret) {
        trim_lines_with_caret = true;
    }

    Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
    MergeCarets(buffer, &view->carets);

    Array<Range> lines_to_skip_triming = {};
    if (!trim_lines_with_caret) {
        lines_to_skip_triming = GetSelectedLinesSorted(scratch, view);
    }

    for (Int i = 0; i < buffer->line_starts.len; i += 1) {
        Int range_index = FindRangeByPos(&lines_to_skip_triming, i);
        if (range_index != -1) continue;

        Range range          = GetLineRangeWithoutNL(buffer, i);
        Int   whitespace_end = range.max;
        for (; whitespace_end > range.min; whitespace_end -= 1) {
            U16  w             = buffer->data[whitespace_end - 1];
            bool is_whitespace = w == ' ' || w == '\t' || w == '\v' || w == '\r';
            if (!is_whitespace) break;
        }

        Range whitespace_range = {whitespace_end, range.max};
        AddEdit(&edits, whitespace_range, u"");
    }

    EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION);
    view->update_scroll = false;
}

void ConvertLineEndingsToLF(Buffer *buffer, bool trim_lines_with_caret = false) {
    Scratch scratch;

    bool is_active_view = false;
    View *view = GetViewForFixingWhenBufferCommand(buffer, &is_active_view);
    if (!is_active_view && !trim_lines_with_caret) {
        trim_lines_with_caret = true;
    }

    Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
    MergeCarets(buffer, &view->carets);

    Array<Range> lines_to_skip_triming = {};
    if (!trim_lines_with_caret) {
        lines_to_skip_triming = GetSelectedLinesSorted(scratch, view);
    }

    for (Int i = 0; i < buffer->line_starts.len; i += 1) {
        Int range_index = FindRangeByPos(&lines_to_skip_triming, i);
        if (range_index != -1) continue;

        Range   range = GetLineRangeWithoutNL(buffer, i);
        char16_t cr   = GetChar(buffer, range.max - 1);
        char16_t lf   = GetChar(buffer, range.max);
        if (cr == u'\r' && lf == u'\n') {
            AddEdit(&edits, {range.max - 1, range.max}, u"");
        }
    }
    EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION);
    view->update_scroll = false;
}

void ApplyClangFormat(Buffer *buffer) {
    Scratch scratch;
    String  string      = AllocCharString(scratch, buffer);
    Buffer *temp_buffer = ExecAndWait(scratch, "clang-format", GetDir(buffer), string);
    ReplaceWithoutMovingCarets(buffer, GetRange(buffer), {temp_buffer->str, temp_buffer->len});
}

void GotoNextInList(Window *window, Int line_offset = 1) {
    Assert(line_offset == 1 || line_offset == -1);
    View *active_view = GetView(window->active_view);

    View *view_goto     = GetView(window->active_goto_list);
    window->active_view = view_goto->id;

    Buffer *buffer_goto = GetBuffer(view_goto->active_buffer);
    int64_t pos         = window->goto_list_pos;
    Int     line        = PosToLine(buffer_goto, pos);

    bool opened = false;
    for (Int i = line + line_offset; i >= 0 && i < buffer_goto->line_starts.len; i += line_offset) {
        Range    line_range   = GetLineRangeWithoutNL(buffer_goto, i);
        String16 line         = GetString(buffer_goto, line_range);
        view_goto->carets[0]  = MakeCaret(line_range.min);
        window->goto_list_pos = line_range.min;
        line                  = Trim(line);

        MergeCarets(buffer_goto, &view_goto->carets);
        IF_DEBUG(AssertRanges(view_goto->carets));
        if (line.len == 0) {
            continue;
        }

        BSet set = Open(line, "dont_error");
        if (set.window == NULL) {
            continue;
        }

        opened = true;
        break;
    }

    if (!opened) window->active_view = active_view->id;
}

void New(Window *window, String name = "") {
    View *view = GetView(window->active_view);
    Buffer *buffer = GetBuffer(view->active_buffer);

    Scratch scratch;
    String dir  = GetDir(buffer);
    if (name != "") {
        if (!IsAbsolute(name)) {
            name = Format(scratch, "%S/%S", dir, name);
        }
        name = GetAbsolutePath(scratch, name);
    } else {
        name = GetUniqueBufferName(dir, "new");
    }
    WindowOpenBufferView(window, name);
}

void NewDir(Window *window, String name = "") {
    View *view = GetView(window->active_view);
    Buffer *buffer = GetBuffer(view->active_buffer);

    Scratch scratch;
    String dir  = GetDir(buffer);
    if (name != "") {
        if (!IsAbsolute(name)) {
            name = Format(scratch, "%S/%S", dir, name);
        }
        name = GetAbsolutePath(scratch, name);
    } else {
        name = GetUniqueBufferName(dir, "new");
    }

    MakeDir(name);
    Open(name);
}

View *ExecHidden(String buffer_name, String cmd, String working_dir) {
    View *view = OpenBufferView(buffer_name);
    Buffer *buffer = GetBuffer(view->active_buffer);
    buffer->dont_try_to_save_in_bulk_ops = true;
    Exec(view->id, true, cmd, working_dir);
    return view;
}

BSet Exec(String cmd, String working_dir, bool set_active = true) {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    if (set_active) {
        NextActiveWindowID = main.window->id;
    }
    JumpGarbageBuffer(&main);
    Exec(main.view->id, true, cmd, working_dir);
    return main;
}

BSet ExecBuild(String cmd) {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    NextActiveWindowID = main.window->id;
    View *view = WindowOpenBufferView(main.window, BuildBuffer->name);
    ResetBuffer(BuildBuffer);
    Exec(view->id, true, cmd, WorkDir);
    main.window->active_goto_list = view->id;
    main.window->goto_list_pos = 0;
    return main;
}

void Command_Build() {
#if OS_WINDOWS
    ExecBuild("build.bat");
#else
    ExecBuild("sh build.sh");
#endif
} RegisterCommand(Command_Build, "f1");

void Command_GotoNextInList() {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    GotoNextInList(main.window, 1);
} RegisterCommand(Command_GotoNextInList, "ctrl-e");

void Command_GotoPrevInList() {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    GotoNextInList(main.window, -1);
} RegisterCommand(Command_GotoPrevInList, "alt-e");

bool IsOpenBoundary(char c) {
    bool result = c == 0 || IsParen(c) || IsBrace(c) || c == ':' || c == '\t' || c == '\n' || c == '"' || c == '\'';
    return result;
}

ResolvedOpen ResolveOpen(Allocator scratch, String path, String meta) {
    ResolvedOpen result = {};
    path = Trim(path);

    // Editor command
    {
        if (StartsWith(path, ":")) {
            result.kind = OpenKind_Command;
            result.path = Skip(path, 1);
            return result;
        }
    }

    // Shell
    {
        if (StartsWith(path, "!")) {
            result.kind = OpenKind_Exec;
            result.path = Skip(path, 1);
            return result;
        }
    }

    // Web
    {
        if (StartsWith(path, "https://") || StartsWith(path, "http://")) {
            result.path = Format(scratch, "%S %S", ConfigInternetBrowser, path);
            result.kind = OpenKind_BackgroundExec;
            return result;
        }
    }

    // Commit
    {
        if (StartsWith(path, "commit ")) {
            path = Skip(path, 7);
            result.path = Format(scratch, "git --no-pager show %S", path);
            result.kind = OpenKind_Exec;
            return result;
        }
    }

    {
        String p = NormalizePath(scratch, path);
        String pstart = p;

        bool is_absolute = false;
        if (IsAlphabetic(ToLowerCase(At(p, 0))) && At(p, 1) == ':' && At(p, 2) == '/') {
            is_absolute = true;
            p = Skip(p, 3);
        } else if (At(p, 0) == '/') {
            is_absolute = true;
            p = Skip(p, 1);
        }

        while (!IsOpenBoundary(At(p, 0))) {
            p = Skip(p, 1);
        }
        path = {pstart.data, (Int)(p.data - pstart.data)};

        if (At(p, 0) == ':') {
            p = Skip(p, 1);
            result.line = SkipNumber(&p);
            if (At(p, 0) == ':') {
                p = Skip(p, 1);
                Int b = SkipNumber(&p);
                result.col = b;
            }
        } else if (At(p, 0) == '(') {
            p = Skip(p, 1);
            result.line = SkipNumber(&p);
            if (At(p, 0) == ',') {
                p = Skip(p, 1);
                Int b = SkipNumber(&p);
                result.col = b;
            }
        }

        Buffer *existing_buffer = GetBuffer(path, NULL);
        if (existing_buffer != NULL) {
            result.path = path;
            result.kind = OpenKind_Goto;
            result.existing_buffer = true;
            return result;
        }

        if (is_absolute && FileExists(path)) {
            result.path = path;
            result.kind = OpenKind_Goto;
            return result;
        } else {
            String workspace_path = Format(scratch, "%S/%S", WorkDir, path);
            bool existing_buffer = GetBuffer(workspace_path, NULL);
            if (existing_buffer || FileExists(workspace_path)) {
                result.existing_buffer = existing_buffer;
                result.path = workspace_path;
                result.kind = OpenKind_Goto;
                return result;
            }

            String rel_path = Format(scratch, "%S/%S", GetMainDir(), path);
            existing_buffer = GetBuffer(rel_path, NULL);
            if (existing_buffer || FileExists(rel_path)) {
                result.existing_buffer = existing_buffer;
                result.path = rel_path;
                result.kind = OpenKind_Goto;
                return result;
            }
        }

    }

    if (meta == "dont_error") {
        result.path = path;
        result.kind = OpenKind_Skip;
        return result;
    }

    return result;
}

BSet Open(Window *window, String path, String meta, bool set_active = true) {
    Scratch scratch;
    BSet set = GetBSet(window);
    ResolvedOpen o = ResolveOpen(scratch, path, meta);
    if (o.kind == OpenKind_Goto) {
        if (set_active) {
            NextActiveWindowID = set.window->id;
        }
        if (IsDir(o.path)) {
            View *view = WindowOpenBufferView(set.window, o.path);
            Buffer *buffer = GetBuffer(view->active_buffer);
            ResetBuffer(buffer);
            RawAppendf(buffer, "..\n");
            for (FileIter it = IterateFiles(scratch, o.path); IsValid(it); Advance(&it)) {
                RawAppendf(buffer, "%S\n", it.filename);
            }
        } else {
            View *view = WindowOpenBufferView(set.window, o.path);
            Buffer *buffer = GetBuffer(view->active_buffer);
            if (o.line != -1) {
                if (o.col == -1) o.col = 1;
                Int pos         = XYToPos(buffer, {o.col - 1, o.line - 1});
                view->carets[0] = MakeCaret(pos);
            }
        }
        UpdateScroll(set.window, true);
    } else if (o.kind == OpenKind_Exec) {
        if (set_active) {
            NextActiveWindowID = set.window->id;
        }
        JumpGarbageBuffer(&set);
        Exec(set.view->id, true, o.path, GetMainDir());
    } else if (o.kind == OpenKind_BackgroundExec) {
        // this shouldn't change the focus/window/view
        Exec(NullViewID, true, o.path, GetMainDir());
    } else if (o.kind == OpenKind_Command) {
        EvalCommand(o.path);
    } else if (o.kind == OpenKind_Skip) {
        return {};
    } else {
        ReportErrorf("Failed to open: %S", path);
    }

    return GetBSet(window);
}

BSet Open(String path, String meta) {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    main = Open(main.window, path, meta);
    return main;
}

BSet Open(String16 path, String meta) {
    Scratch scratch;
    String  string = ToString(scratch, path);
    return Open(string, meta);
}

void Command_Save() {
    BSet active = GetBSet(LastActiveLayoutWindowID);
    SaveBuffer(active.buffer);
} RegisterCommand(Command_Save, "ctrl-s");

void Command_SaveAll() {
    For(Buffers) {
        if (it->file_mod_time) {
            SaveBuffer(it);
        }
    }
} RegisterCommand(Command_SaveAll, "ctrl-shift-s");

void Command_Reopen() {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    ReopenBuffer(main.buffer);
    NextActiveWindowID = main.window->id;
} RegisterCommand(Command_Reopen, "");

void Command_New() {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    New(main.window, "");
} RegisterCommand(Command_New, "ctrl-n");

void Command_ToggleFullscreen() {
    if (IsInFullscreen) {
        SDL_SetWindowSize(SDLWindow, FullScreenSizeX, FullScreenSizeY);
        SDL_SetWindowPosition(SDLWindow, FullScreenPositionX, FullScreenPositionY);
    } else {
        SDL_GetWindowSize(SDLWindow, &FullScreenSizeX, &FullScreenSizeY);
        SDL_GetWindowPosition(SDLWindow, &FullScreenPositionX, &FullScreenPositionY);

        SDL_DisplayID display = SDL_GetDisplayForWindow(SDLWindow);
        const SDL_DisplayMode *dm = SDL_GetCurrentDisplayMode(display);
        SDL_SetWindowSize(SDLWindow, dm->w, dm->h);
        SDL_SetWindowPosition(SDLWindow, 0, 0);
    }

    IsInFullscreen = !IsInFullscreen;
} RegisterCommand(Command_ToggleFullscreen, "f11");

void Command_SetWorkDir() {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    WorkDir = GetDir(main.buffer);
} RegisterCommand(Command_SetWorkDir, "");

String CodeSkipPatterns[] = {".git/", ".obj", ".o", ".pdb", ".exe"};
String Coro_OpenCodeDir;

void Coro_OpenCode(mco_coro *co) {
    Array<String> dirs = {CoCurr->arena};
    Add(&dirs, Coro_OpenCodeDir);
    int i = 0;
    for (int diri = 0; diri < dirs.len; diri += 1) {
        for (FileIter it = IterateFiles(CoCurr->arena, dirs[diri]); IsValid(it); Advance(&it)) {
            bool match = false;
            for (int endings_i = 0; endings_i < Lengthof(CodeSkipPatterns); endings_i += 1) {
                String ending = CodeSkipPatterns[endings_i];
                if (EndsWith(it.absolute_path, ending)) {
                    match = true;
                    break;
                }
            }
            if (match) continue;


            if (it.is_directory) {
                Add(&dirs, it.absolute_path);
            } else {
                BufferOpenFile(it.absolute_path);
            }

            CoYield(co);
        }
    }
}

void OpenCode(String dir) {
    Coro_OpenCodeDir = dir;
    CoData *data = CoAdd(Coro_OpenCode);
    data->dont_wait_until_resolved = true;
}

void Command_OpenCode() {
    OpenCode(WorkDir);
} RegisterCommand(Command_OpenCode, "");

void Command_KillProcess() {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    KillProcess(main.view);
} RegisterCommand(Command_KillProcess, "");

void Command_CloseWindow() {
    Close(LastActiveLayoutWindowID);
} RegisterCommand(Command_CloseWindow, "");

void AddHook(Array<CommandData> *arr, String name, String binding, Function *function) {
    CommandData n = {name, binding, function, ParseKeyCached(binding)};
    Add(arr, n);
}

// @todo: rename to temp buffer? but there is already a thing like that...
String Coro_YesNoCancel(mco_coro *co, BSet main, String question) {
    JumpGarbageBuffer(&main);
    NextActiveWindowID = main.window->id;

    RawAppendf(main.buffer, R"==(
%S

                :Yes    :No    :Cancel
)==", question);
    main.view->carets[0] = FindNext(main.buffer, u":Yes", MakeCaret(0));
    main.view->carets[0].range.min = main.view->carets[0].range.max;
    AddHook(&main.view->hooks, "Yes", "enter", [](){BSet active = GetBSet(ActiveWindowID); active.view->hook_cmd = "Yes";});
    AddHook(&main.view->hooks, "No", "", [](){BSet active = GetBSet(ActiveWindowID); active.view->hook_cmd = "No";});
    AddHook(&main.view->hooks, "Cancel", "escape", [](){BSet active = GetBSet(ActiveWindowID); active.view->hook_cmd = "Cancel";});
    String result = "Cancel";
    for (;;) {
        if (main.window->active_view != main.view->id || main.window->close) {
            break;
        }

        if (main.view->hook_cmd != "") {
            result = main.view->hook_cmd;
            break;
        }

        CoYield(co);
    }
    Close(main.buffer->id);
    return result;
}

void Coro_Close(mco_coro *co) {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    if (main.buffer->special || main.buffer->garbage) {
        Close(main.view->id);
        return;
    }

    bool ref = false;
    For (Views) {
        if (it->id == main.view->id) {
            continue;
        }
        if (it->active_buffer == main.buffer->id) {
            ref = true;
            break;
        }
    }

    if (ref) {
        Close(main.view->id);
        return;
    }

    if (!main.buffer->dirty) {
        Close(main.buffer->id);
        return;
    }

    String question = Format(CoCurr->arena, "Do you want to save [%S] before closing?", main.buffer->name);
    String result = Coro_YesNoCancel(co, main, question);
    if (result == "Yes") {
        SaveBuffer(main.buffer);
        Close(main.buffer->id);
    } else if (result == "No") {
        Close(main.buffer->id);
    } else if (result == "Cancel") {
        return;
    } ElseInvalidCodepath();
}

void Command_Close() {
    CoAdd(Coro_Close);
} RegisterCommand(Command_Close, "ctrl-w");

// Considerations with coroutines:
// 1. Does scratch memory leak across Yield boundary? Or interacts badly with Yield stuff in any way?
// 2. Are pointers and globals correct over time? Or might they get deleted etc.
// 3. Imagine a scenario where the coroutine gets deleted before completion, will the memory leak?
String Coro_CloseAllEx(mco_coro *co) {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    Array<BufferID> buffers = {CoCurr->arena};
    For (Buffers) Add(&buffers, it->id);
    ForItem (id, buffers) {
        Buffer *it = GetBuffer(id);
        if (it->special || !it->dirty) {
            continue;
        }
        if (it->garbage || it->dont_try_to_save_in_bulk_ops) {
            continue;
        }

        String question = Format(CoCurr->arena, "Do you want to save [%S] before closing?", it->name);
        String result = Coro_YesNoCancel(co, main, question);
        it = GetBuffer(id, NULL);
        if (it && result == "Yes") {
            SaveBuffer(it);
        } else if (result == "No") {
        } else if (result == "Cancel") {
            return "Cancel";
        } ElseInvalidCodepath();
    }

    For(Buffers) {
        Close(it->id);
    }
    return "Yes";
}

void Coro_Quit(mco_coro *co) {
    String res = Coro_CloseAllEx(co);
    if (res != "Cancel") {
        AppIsRunning = false;
    }
}

void Command_Quit() {
    CoAdd(Coro_Quit);
} RegisterCommand(Command_Quit, "");

void Coro_CloseAll(mco_coro *co) {
    Coro_CloseAllEx(co);
}

void Command_CloseAll() {
    CoAdd(Coro_CloseAll);
} RegisterCommand(Command_CloseAll, "");

void Command_JumpBack() {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    main.window->skip_checkpoint = true;
    JumpBack(main.window);
} RegisterCommand(Command_JumpBack, "alt-q | mousex1");

void Command_JumpForward() {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    main.window->skip_checkpoint = true;
    JumpForward(main.window);
} RegisterCommand(Command_JumpForward, "alt-shift-q | mousex2");

void Command_OpenUpFolder() {
    BSet main = GetBSet(LastActiveLayoutWindowID);
    String name = ChopLastSlash(main.buffer->name);
    Open(name);
} RegisterCommand(Command_OpenUpFolder, "ctrl-period");

void Command_EncloseLine() {
    BSet active = GetBSet(ActiveWindowID);
    EncloseLine(active.view);
} RegisterCommand(Command_EncloseLine, "ctrl-l");

void Command_SelectAll() {
    BSet active = GetBSet(ActiveWindowID);
    SelectEntireBuffer(active.view);
    active.view->update_scroll = false;
} RegisterCommand(Command_SelectAll, "ctrl-a");

void Command_Redo() {
    BSet active = GetBSet(ActiveWindowID);
    RedoEdit(active.buffer, &active.view->carets);
} RegisterCommand(Command_Redo, "ctrl-shift-z");

void Command_Undo() {
    BSet active = GetBSet(ActiveWindowID);
    UndoEdit(active.buffer, &active.view->carets);
} RegisterCommand(Command_Undo, "ctrl-z");

void Command_MakeFontLarger() {
    ConfigFontSize += 1;
    ReloadFont(ConfigFont, (U32)ConfigFontSize);
} RegisterCommand(Command_MakeFontLarger, "ctrl-equals");

void Command_MakeFontSmaller() {
    if (ConfigFontSize > 4) {
        ConfigFontSize -= 1;
        ReloadFont(ConfigFont, (U32)ConfigFontSize);
    }
} RegisterCommand(Command_MakeFontSmaller, "ctrl-minus");

void Command_Open() {
    BSet active = GetBSet(ActiveWindowID);
    if (active.window->id == CommandWindowID) {
        return;
    }
    Open(FetchLoadWord(active.view));
} RegisterCommand(Command_Open, "ctrl-q");

void Command_KillSelectedLines() {
    BSet active = GetBSet(ActiveWindowID);
    KillSelectedLines(active.view);
} RegisterCommand(Command_KillSelectedLines, "ctrl-shift-k");

void Command_IndentSelectedLines() {
    BSet active = GetBSet(ActiveWindowID);
    IndentSelectedLines(active.view);
} RegisterCommand(Command_IndentSelectedLines, "ctrl-rightbracket | tab");

void Command_DedentSelectedLines() {
    BSet active = GetBSet(ActiveWindowID);
    IndentSelectedLines(active.view, true);
} RegisterCommand(Command_DedentSelectedLines, "ctrl-leftbracket | shift-tab");

void Command_DuplicateLineDown() {
    BSet active = GetBSet(ActiveWindowID);
    DuplicateLine(active.view, DIR_DOWN);
} RegisterCommand(Command_DuplicateLineDown, "ctrl-alt-down");

void Command_CreateCursorDown() {
    BSet active = GetBSet(ActiveWindowID);
    CreateCursorVertical(active.view, DIR_DOWN);
} RegisterCommand(Command_CreateCursorDown, "alt-shift-down");

void Command_SelectDownToEmptyLine() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_DOWN, CTRL_PRESSED, SHIFT_PRESS);
} RegisterCommand(Command_SelectDownToEmptyLine, "ctrl-shift-down");

void Command_MoveLineDown() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCaretsLine(active.view, DIR_DOWN);
} RegisterCommand(Command_MoveLineDown, "alt-down");

void Command_MoveDownToEmptyLine() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_DOWN, CTRL_PRESSED);
} RegisterCommand(Command_MoveDownToEmptyLine, "ctrl-down");

void Command_SelectDown() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_DOWN, false, SHIFT_PRESS);
} RegisterCommand(Command_SelectDown, "shift-down");

void Command_MoveDown() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_DOWN);
} RegisterCommand(Command_MoveDown, "down");

void Command_DuplicateLineUp() {
    BSet active = GetBSet(ActiveWindowID);
    DuplicateLine(active.view, DIR_UP);
} RegisterCommand(Command_DuplicateLineUp, "ctrl-alt-up");

void Command_CreateCursorUp() {
    BSet active = GetBSet(ActiveWindowID);
    CreateCursorVertical(active.view, DIR_UP);
} RegisterCommand(Command_CreateCursorUp, "alt-shift-up");

void Command_SelectUpToEmptyLine() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_UP, CTRL_PRESSED, SHIFT_PRESS);
} RegisterCommand(Command_SelectUpToEmptyLine, "ctrl-shift-up");

void Command_MoveLineUp() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCaretsLine(active.view, DIR_UP);
} RegisterCommand(Command_MoveLineUp, "alt-up");

void Command_MoveUpToEmptyLine() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_UP, CTRL_PRESSED);
} RegisterCommand(Command_MoveUpToEmptyLine, "ctrl-up");

void Command_SelectUp() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_UP, false, SHIFT_PRESS);
} RegisterCommand(Command_SelectUp, "shift-up");

void Command_MoveUp() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_UP);
} RegisterCommand(Command_MoveUp, "up");

void Command_BoundarySelectLeft() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_LEFT, CTRL_PRESSED, SHIFT_PRESS);
} RegisterCommand(Command_BoundarySelectLeft, "ctrl-shift-left");

void Command_BoundaryMoveLeft() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_LEFT, CTRL_PRESSED);
} RegisterCommand(Command_BoundaryMoveLeft, "ctrl-left");

void Command_SelectLeft() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_LEFT, false, SHIFT_PRESS);
} RegisterCommand(Command_SelectLeft, "shift-left");

void Command_FocusLeftWindow() {
    NextActiveWindowID = SwitchWindow(DIR_LEFT)->id;
} RegisterCommand(Command_FocusLeftWindow, "alt-left");

void Command_MoveLeft() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_LEFT);
} RegisterCommand(Command_MoveLeft, "left");

void Command_BoundarySelectRight() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_RIGHT, CTRL_PRESSED, SHIFT_PRESS);
} RegisterCommand(Command_BoundarySelectRight, "ctrl-shift-right");

void Command_BoundaryMoveRight() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_RIGHT, CTRL_PRESSED);
} RegisterCommand(Command_BoundaryMoveRight, "ctrl-right");

void Command_SelectRight() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_RIGHT, false, SHIFT_PRESS);
} RegisterCommand(Command_SelectRight, "shift-right");

void Command_FocusRightWindow() {
    NextActiveWindowID = SwitchWindow(DIR_RIGHT)->id;
} RegisterCommand(Command_FocusRightWindow, "alt-right");

void Command_MoveRight() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCarets(active.view, DIR_RIGHT);
} RegisterCommand(Command_MoveRight, "right");

void Command_MoveUpAPage() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCursorByPageSize(active.window, DIR_UP);
} RegisterCommand(Command_MoveUpAPage, "pageup");

void Command_SelectUpPage() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCursorByPageSize(active.window, DIR_UP, SHIFT_PRESS);
} RegisterCommand(Command_SelectUpPage, "shift-pageup");

void Command_MoveToStart() {
    BSet active = GetBSet(ActiveWindowID);
    SelectRange(active.view, MakeRange(0));
} RegisterCommand(Command_MoveToStart, "ctrl-pageup");

void Command_SelectDownPage() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCursorByPageSize(active.window, DIR_DOWN, SHIFT_PRESS);
} RegisterCommand(Command_SelectDownPage, "shift-pagedown");

void Command_MoveToEnd() {
    BSet active = GetBSet(ActiveWindowID);
    SelectRange(active.view, MakeRange(active.buffer->len));
} RegisterCommand(Command_MoveToEnd, "ctrl-pagedown");

void Command_MoveDownPage() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCursorByPageSize(active.window, DIR_DOWN);
} RegisterCommand(Command_MoveDownPage, "pagedown");

void Command_SelectToLineStart() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCursorToSide(active.view, DIR_LEFT, SHIFT_PRESS);
} RegisterCommand(Command_SelectToLineStart, "shift-home");

void Command_MoveToLineStart() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCursorToSide(active.view, DIR_LEFT);
} RegisterCommand(Command_MoveToLineStart, "home");

void Command_MoveToLineEnd() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCursorToSide(active.view, DIR_RIGHT);
} RegisterCommand(Command_MoveToLineEnd, "end");

void Command_SelectToLineEnd() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCursorToSide(active.view, DIR_RIGHT, SHIFT_PRESS);
} RegisterCommand(Command_SelectToLineEnd, "shift-end");

void Command_Delete() {
    BSet active = GetBSet(ActiveWindowID);
    Delete(active.view, DIR_LEFT);
} RegisterCommand(Command_Delete, "shift-backspace | backspace");

void Command_DeleteBoundary() {
    BSet active = GetBSet(ActiveWindowID);
    Delete(active.view, DIR_LEFT, SHIFT_PRESS);
} RegisterCommand(Command_DeleteBoundary, "ctrl-backspace");

void Command_DeleteForward() {
    BSet active = GetBSet(ActiveWindowID);
    Delete(active.view, DIR_RIGHT);
} RegisterCommand(Command_DeleteForward, "shift-delete | delete");

void Command_DeleteForwardBoundary() {
    BSet active = GetBSet(ActiveWindowID);
    Delete(active.view, DIR_RIGHT, SHIFT_PRESS);
} RegisterCommand(Command_DeleteForwardBoundary, "ctrl-delete");

void Command_InsertNewLineUp() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCursorToSide(active.view, DIR_LEFT);
    IdentedNewLine(active.view);
    MoveCarets(active.view, DIR_UP);
} RegisterCommand(Command_InsertNewLineUp, "ctrl-shift-enter");

void Command_InsertNewLineDown() {
    BSet active = GetBSet(ActiveWindowID);
    MoveCursorToSide(active.view, DIR_RIGHT);
    IdentedNewLine(active.view);
} RegisterCommand(Command_InsertNewLineDown, "ctrl-enter");

void Command_NewLine() {
    if (ActiveWindowID == CommandWindowID || ActiveWindowID == SearchWindowID) {
        return;
    }
    BSet active = GetBSet(ActiveWindowID);
    IdentedNewLine(active.view);
} RegisterCommand(Command_NewLine, "enter");

void Command_CreateCaretOnNextFind() {
    BSet active = GetBSet(ActiveWindowID);
    String16 string = GetString(active.buffer, active.view->carets[0].range);
    Caret    caret  = FindNext(active.buffer, string, active.view->carets[0]);
    Insert(&active.view->carets, caret, 0);
    MergeCarets(active.buffer, &active.view->carets);
} RegisterCommand(Command_CreateCaretOnNextFind, "ctrl-d");

void Command_FocusWindow1() {
    NextActiveWindowID = GetOverlappingWindow({0,0}, GetWindow(ActiveWindowID))->id;
} RegisterCommand(Command_FocusWindow1, "ctrl-1");

void Command_FocusWindow2() {
    Window *first = GetOverlappingWindow({0,0}, GetWindow(ActiveWindowID));
    Vec2I p = GetSideOfWindow(first, DIR_RIGHT);
    NextActiveWindowID = GetOverlappingWindow(p, GetWindow(ActiveWindowID))->id;
} RegisterCommand(Command_FocusWindow2, "ctrl-2");

void Command_FocusWindow3() {
    Window *first = GetOverlappingWindow({0,0});
    if (first) {
        Window *second = GetOverlappingWindow(GetSideOfWindow(first, DIR_RIGHT));
        if (second) {
            Window *third = GetOverlappingWindow(GetSideOfWindow(second, DIR_RIGHT));
            if (third) {
                NextActiveWindowID = third->id;
            }
        }
    }
} RegisterCommand(Command_FocusWindow3, "ctrl-3");

void Command_NewWindow() {
    CreateWind();
} RegisterCommand(Command_NewWindow, "ctrl-backslash");

void Command_ClearCarets() {
    BSet active = GetBSet(ActiveWindowID);
    active.view->carets.len = 1;
    active.view->carets[0] = MakeCaret(GetFront(active.view->carets[0]));

    if (active.window->lose_focus_on_escape && active.window->id == ActiveWindowID) {
        if (active.window->layout) {
            //
        } else {
            NextActiveWindowID = LastActiveLayoutWindowID;
        }
    }

    For (Windows) {
        if (it->lose_visibility_on_escape && it->visible) {
            it->visible = false;
        }
    }
} RegisterCommand(Command_ClearCarets, "escape");
