Files
text_editor/src/text_editor/commands.cpp
2025-12-31 08:27:22 +01:00

1234 lines
39 KiB
C++

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 {
String result = ChopLastSlash(buffer->name);
return result;
}
}
String GetMainDir() {
BSet main = GetBSet(LastActiveLayoutWindowID);
return GetDir(main.buffer);
}
void JumpTempBuffer(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->temp = 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);
JumpTempBuffer(&main);
NextActiveWindowID = main.window->id;
RawAppendf(main.buffer, "\n %S\n :Close\n", string);
main.view->carets[0] = FindNext(main.buffer, u":Close", MakeCaret(0));
AddHook(&main.view->hooks, "Close", "escape", [](){
BSet active = GetBSet(ActiveWindowID);
Close(active.buffer->id);
});
}
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;
}
JumpTempBuffer(&main);
Exec(main.view->id, true, cmd, working_dir);
return main;
}
BSet ExecBuild(String cmd) {
BSet build = GetBSet(BuildWindowID);
BSet main = GetBSet(LastActiveLayoutWindowID);
SelectRange(build.view, Range{});
ResetBuffer(build.buffer);
Exec(build.view->id, true, cmd, WorkDir);
main.window->active_goto_list = build.view->id;
main.window->goto_list_pos = 0;
return build;
}
void Command_SaveAll() {
For(Buffers) {
// NOTE: file_mod_time is only set when buffer got read or written to disk already so should be saved
if (it->file_mod_time) {
SaveBuffer(it);
}
}
} RegisterCommand(Command_SaveAll, "ctrl-shift-s");
void Command_Build() {
Command_SaveAll();
#if OS_WINDOWS
ExecBuild("build.bat");
#else
ExecBuild("sh build.sh");
#endif
BSet main = GetBSet(BuildWindowID);
main.window->visible = true;
} 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 alo, 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_BackgroundExec;
result.path = Skip(path, 2);
return result;
}
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(alo, "%S %S", ConfigInternetBrowser, path);
result.kind = OpenKind_BackgroundExec;
return result;
}
}
// Commit
{
if (StartsWith(path, "commit ")) {
path = Skip(path, 7);
result.path = Format(alo, "git --no-pager show %S", path);
result.kind = OpenKind_Exec;
return result;
}
}
{
String p = NormalizePath(alo, path);
if (p.len == 2 && IsAlphabetic(ToLowerCase(At(p, 0))) && At(p, 1) == ':') {
p = Format(alo, "%S/", p);
}
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(alo, "%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(alo, "%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);
SetFuzzy(view);
Buffer *buffer = GetBuffer(view->active_buffer);
ResetBuffer(buffer);
RawAppendf(buffer, "\n..\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;
}
JumpTempBuffer(&set);
Exec(set.view->id, false, o.path, GetMainDir());
} else if (o.kind == OpenKind_BackgroundExec) {
// this shouldn't change the focus/window/view
Exec(NullViewID, false, 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_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);
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);
}
void Coro_Rename(mco_coro *co) {
BSet main = GetBSet(LastActiveLayoutWindowID);
Buffer *buffer = main.buffer;
JumpTempBuffer(&main);
NextActiveWindowID = main.window->id;
RawAppendf(main.buffer, "Rename and click enter to submit: [%S]\n :Rename :Cancel", buffer->name);
main.view->carets[0] = FindNext(main.buffer, u"]", MakeCaret(0));
main.view->carets[0].range.max = main.view->carets[0].range.min;
AddHook(&main.view->hooks, "Rename", "enter", [](){BSet active = GetBSet(ActiveWindowID); active.view->hook_cmd = "Rename";});
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->id != LastActiveLayoutWindowID && main.window->id != ActiveWindowID) || main.window->close) {
break;
}
if (main.view->hook_cmd != "") {
result = main.view->hook_cmd;
break;
}
CoYield(co);
}
Close(main.buffer->id);
if (result == "Rename") {
Caret a = FindNext(main.buffer, u"[", MakeCaret(-1));
Caret b = FindNext(main.buffer, u"]", MakeCaret(-1));
if (a.range.min == -1 || b.range.max == -1) {
ReportErrorf("Failed to extract the name for the Rename operation from the buffer");
return;
}
String16 string16 = GetString(main.buffer, {a.range.max + 1, b.range.max});
String string = ToString(CoCurr->arena, string16);
buffer->name = Intern(&GlobalInternTable, string);
}
}
void Command_Rename() {
CoAdd(Coro_Rename);
} RegisterCommand(Command_Rename, "");
String Coro_YesNoCancel(mco_coro *co, BSet main, String question) {
JumpTempBuffer(&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->id != LastActiveLayoutWindowID && main.window->id != ActiveWindowID) || 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->temp) {
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, NULL);
if (it == NULL || it->special || !it->dirty) {
continue;
}
if (it->temp || 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_JumpPrev() {
BSet main = GetBSet(LastActiveLayoutWindowID);
JumpToLastValidView(main.window);
NextActiveWindowID = main.window->id;
} RegisterCommand(Command_JumpPrev, "ctrl-tab");
void Command_Prev() {
BSet main = GetBSet(LastActiveLayoutWindowID);
main.window->skip_checkpoint = true;
JumpBack(main.window);
NextActiveWindowID = main.window->id;
} RegisterCommand(Command_Prev, "alt-q | mousex1");
void Command_Next() {
BSet main = GetBSet(LastActiveLayoutWindowID);
main.window->skip_checkpoint = true;
JumpForward(main.window);
NextActiveWindowID = main.window->id;
} RegisterCommand(Command_Next, "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);
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() {
BSet active = GetBSet(ActiveWindowID);
IdentedNewLine(active.view);
} RegisterCommand(Command_NewLine, "enter | shift-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");