// MAAAAAAAAAAAAN I DONT LIKE THIS CODE, BUT HOPE IT WORKS // @todo: potentially bad that we are not checking against end! lexer->at[0] == 0 check is not enough struct Lexer2 { char16_t *at; }; Lexer2 BeginLexing(String16 data) { Lexer2 result = {data.data}; return result; } String16 Next(Lexer2 *lexer) { while (lexer->at[0] != 0 && IsNonWord(lexer->at[0])) { lexer->at += 1; } String16 result = {lexer->at, 0}; while (lexer->at[0] != 0 && IsWord(lexer->at[0])) { lexer->at += 1; result.len += 1; } return result; } struct StringAndDistance { String16 string; Int distance; }; Int FindValueIndex(Array *arr, String16 string) { for (int64_t i = 0; i < arr->len; i += 1) { if (arr->data[i].string == string) { return i; } } return -1; } inline bool MergeSortCompare(StringAndDistance *EntryA, StringAndDistance *EntryB) { bool result = EntryA->distance > EntryB->distance; return result; } struct { BlockArena arena; String16 prefix_string; String16 last_string; mco_coro *co; Buffer *buffer; View *view; Int original_caret_pos; Int direction; } CWS; void CWSLexIdentifiers(Array *out_idents, Buffer *buffer) { Array idents = {CWS.arena}; String16 string = GetString(buffer); Lexer2 lexer = BeginLexing(string.data); for (;;) { String16 token = Next(&lexer); if (token.len <= 0) { break; } if (StartsWith(token, CWS.prefix_string) && token != CWS.prefix_string) { Int pos = token.data - buffer->str; // Here we are computing distance based on position from currently open // buffer but should be fine. We are sorting then putting into the array // and it doesn't displace the original ones so it's fine Int distance = Absolute(CWS.original_caret_pos - pos); int64_t value_index = FindValueIndex(&idents, token); if (value_index != -1) { if (idents[value_index].distance > distance) { idents[value_index].distance = distance; } } else { Add(&idents, {Copy16(CWS.arena, token), distance}); } } } if (idents.len > 1) { Array temp = TightCopy(CWS.arena, idents); MergeSort(idents.len, idents.data, temp.data); } For (idents) { if (FindValueIndex(out_idents, it.string) == -1) Add(out_idents, it); } } #if 0 void CWSGetNextBuffer(mco_coro *co) { // Would be nice to iterate through 64 last active buffers // - Then all buffers Add(&CWS.visited_buffers, CWS.buffer->id); mco_result res = mco_push(co, &CWS.buffer->id, sizeof(CWS.buffer->id)); Assert(res == MCO_SUCCESS); mco_yield(co); For (IterateInReverse(&Buffers)) { Add(&buffers, it->id); } ForItem (window, Windows) { if (!window->visible) { continue; } View *view = GetView(window->active_view); Buffer *buffer = GetBuffer(view->active_buffer); if (Contains(CWS.visited_buffers, buffer->id)) { continue; } Add(&CWS.visited_buffers, buffer->id); mco_result res = mco_push(co, &buffer->id, sizeof(buffer->id)); Assert(res == MCO_SUCCESS); mco_yield(co); } } #endif void WordComplete(mco_coro *co) { Array buffers = {CWS.arena}; Add(&buffers, CWS.buffer->id); For (IterateInReverse(&Buffers)) { Add(&buffers, it->id); } Int buffer_i = 0; Array idents = {CWS.arena}; Add(&idents, {CWS.prefix_string, 0}); for (Int i = 1;;) { if (i >= idents.len) { while (buffer_i < buffers.len) { Buffer *buffer = GetBuffer(buffers[buffer_i++]); CWSLexIdentifiers(&idents, buffer); if (i < idents.len) { break; } } if (i >= idents.len) { goto yield; } } CWS.last_string = Copy16(CWS.arena, idents[i].string); SelectRange(CWS.view, EncloseWord(CWS.buffer, CWS.original_caret_pos)); Replace(CWS.view, CWS.last_string); yield:; mco_yield(co); if (CWS.direction == -1 && i > 0 && i == idents.len) { // special case for when we are at the end of the list and we want to go back // to make it sure we don't need to click twice to flip i -= 1; } i += CWS.direction; i = Clamp(i, (Int)0, idents.len); } } void WordComplete(View *view, Int pos) { Buffer *buffer = GetBuffer(view->active_buffer); Range prefix_string_range = {GetWordStart(buffer, pos), pos}; String16 prefix = GetString(buffer, prefix_string_range); if (prefix == u"") { return; } bool continue_with_previous = CWS.co && CWS.last_string == prefix && CWS.buffer == buffer && CWS.view == view; if (!continue_with_previous) { if (CWS.co) { mco_result res = mco_destroy(CWS.co); Assert(res == MCO_SUCCESS); CWS.co = NULL; } Release(&CWS.arena); MemoryZero(&CWS, sizeof(CWS)); // CWS.visited_buffers.allocator = CWS.arena; CWS.buffer = buffer; CWS.view = view; CWS.original_caret_pos = pos; CWS.prefix_string = Copy16(CWS.arena, prefix); mco_desc desc = mco_desc_init(WordComplete, 0); mco_result res = mco_create(&CWS.co, &desc); Assert(res == MCO_SUCCESS); } CWS.direction = 1; mco_result res = mco_resume(CWS.co); Assert(res == MCO_SUCCESS); if (mco_status(CWS.co) == MCO_DEAD) { res = mco_destroy(CWS.co); Assert(res == MCO_SUCCESS); CWS.co = NULL; } } void CompletePrevWord(View *view, Int pos) { Buffer *buffer = GetBuffer(view->active_buffer); Range prefix_string_range = {GetWordStart(buffer, pos), pos}; String16 prefix = GetString(buffer, prefix_string_range); bool continue_with_previous = CWS.co && CWS.last_string == prefix && CWS.buffer == buffer; if (!continue_with_previous) { return; } CWS.direction = -1; mco_result res = mco_resume(CWS.co); Assert(res == MCO_SUCCESS); if (mco_status(CWS.co) == MCO_DEAD) { res = mco_destroy(CWS.co); Assert(res == MCO_SUCCESS); CWS.co = NULL; } } void CMD_WordComplete() { BSet active = GetBSet(ActiveWindowID); bool ok = active.view->carets.len == 1 && GetSize(active.view->carets[0].range) == 0; if (!ok) { return; } WordComplete(active.view, active.view->carets[0].range.min); } RegisterCommand(CMD_WordComplete, "ctrl-space", "Completes the current word"); void CMD_CompletePrevWord() { BSet active = GetBSet(ActiveWindowID); bool ok = active.view->carets.len == 1 && GetSize(active.view->carets[0].range) == 0; if (!ok) { return; } CompletePrevWord(active.view, active.view->carets[0].range.min); } RegisterCommand(CMD_CompletePrevWord, "ctrl-shift-space", "If already completing a word, iterate to previous word"); void CMD_CompletePrevWordOrDedent() { BSet active = GetBSet(ActiveWindowID); if (active.view->carets.len == 1 && GetSize(active.view->carets[0].range) == 0) { CMD_CompletePrevWord(); } else { IndentSelectedLines(active.view, true); } } RegisterCommand(CMD_CompletePrevWordOrDedent, "", "Completes the current word or it indents it, when single caret with no selection it goes for word complete"); void CMD_WordCompleteOrIndent() { BSet active = GetBSet(ActiveWindowID); if (active.view->carets.len == 1 && GetSize(active.view->carets[0].range) == 0) { CMD_WordComplete(); } else { IndentSelectedLines(active.view); } } RegisterCommand(CMD_WordCompleteOrIndent, "", "Completes the current word or it indents it, when single caret with no selection it goes for word complete");