diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index f451e84..9302102 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -29,16 +29,17 @@ struct Line { Range range; }; +struct LineAndColumn { + Line line; + int64_t column; +}; + struct Edit { Range range; String string; }; -// :buffer_initialized -// @todo: it's very tempting to ensure that buffer is initialized -// then we know: -// - we always have at least one thing in lines array. -// - I think it would be probably easy enough to enforce +// - Buffer should be initialized before use! struct Buffer { Allocator allocator; char *data[2]; @@ -228,30 +229,6 @@ String CopyNullTerminated(Allocator allocator, Buffer &buffer, Range range) { return result; } -Line GetLine(Buffer &buffer, int64_t line) { - // :buffer_initialized - // @todo: do I enfore initialized state of the buffer + - // lines array maybe should always have something - Assert(buffer.lines.len); - - line = Clamp(line, (int64_t)0, buffer.lines.len - 1); - Range range = buffer.lines[line]; - Line result = {line, range}; - return result; -} - -Line FindLine(Buffer &buffer, int64_t pos) { - For(buffer.lines) { - // The program is doing '<= it.max' so as to include the new line. - // Otherwise this function wouldn't be able to find certain positions. - if (pos >= it.min && pos <= it.max) { - Line result = {buffer.lines.get_index(it), it}; - return result; - } - } - return {}; -} - int64_t AdjustUTF8Pos(String string, int64_t pos, int64_t direction) { for (; pos >= 0 && pos < string.len;) { if (IsUTF8ContinuationByte(string.data[pos])) { @@ -329,14 +306,14 @@ void Advance(BufferIter *iter) { iter->item = GetUTF32(*iter->buffer, iter->pos, &iter->utf8_codepoint_size); } -BufferIter Iterate(Buffer *buffer, Range range, int64_t direction = ITERATE_FORWARD) { +BufferIter Iterate(Buffer &buffer, Range range, int64_t direction = ITERATE_FORWARD) { Assert(direction == ITERATE_FORWARD || direction == ITERATE_BACKWARD); - Assert(!IsUTF8ContinuationByte(GetChar(*buffer, range.min))); + Assert(!IsUTF8ContinuationByte(GetChar(buffer, range.min))); Assert(range.max >= range.min); - range.min = Clamp(*buffer, range.min); - range.max = Clamp(*buffer, range.max); + range.min = Clamp(buffer, range.min); + range.max = Clamp(buffer, range.max); - BufferIter result = {buffer, range.min, range.max, direction}; + BufferIter result = {&buffer, range.min, range.max, direction}; if (direction == ITERATE_BACKWARD) { result.end = range.min; result.pos = range.max; @@ -346,6 +323,52 @@ BufferIter Iterate(Buffer *buffer, Range range, int64_t direction = ITERATE_FORW return result; } +Line GetLine(Buffer &buffer, int64_t line) { + Assert(buffer.lines.len); + + line = Clamp(line, (int64_t)0, buffer.lines.len - 1); + Range range = buffer.lines[line]; + Line result = {line, range}; + return result; +} + +Line FindLine(Buffer &buffer, int64_t pos) { + For(buffer.lines) { + // The program is doing '<= it.max' so as to include the new line. + // Otherwise this function wouldn't be able to find certain positions. + if (pos >= it.min && pos <= it.max) { + Line result = {buffer.lines.get_index(it), it}; + return result; + } + } + return {}; +} + +LineAndColumn FindLineAndColumn(Buffer &buffer, int64_t pos) { + LineAndColumn result = {}; + result.column = 1; + result.line = FindLine(buffer, pos); + for (BufferIter iter = Iterate(buffer, result.line.range); IsValid(iter); Advance(&iter)) { + if (iter.pos == pos) { + result.column = iter.codepoint_index; + break; + } + } + return result; +} + +int64_t FindPos(Buffer &buffer, int64_t line_number, int64_t column) { + Line line = GetLine(buffer, line_number); + int64_t result = line.range.max; + for (BufferIter iter = Iterate(buffer, line.range); IsValid(iter); Advance(&iter)) { + if (iter.codepoint_index == column) { + result = iter.pos; + break; + } + } + return result; +} + void RunBufferTests() { Scratch scratch; { @@ -407,7 +430,7 @@ void RunBufferTests() { { Array s = {scratch}; - for (BufferIter iter = Iterate(&buffer, {0, 6}); IsValid(iter); Advance(&iter)) { + for (BufferIter iter = Iterate(buffer, {0, 6}); IsValid(iter); Advance(&iter)) { Assert(iter.item < 255); s.add((char)iter.item); @@ -417,7 +440,7 @@ void RunBufferTests() { } { Array s = {scratch}; - for (BufferIter iter = Iterate(&buffer, {0, 6}, ITERATE_BACKWARD); IsValid(iter); Advance(&iter)) { + for (BufferIter iter = Iterate(buffer, {0, 6}, ITERATE_BACKWARD); IsValid(iter); Advance(&iter)) { Assert(iter.item < 255); s.add((char)iter.item); @@ -427,7 +450,7 @@ void RunBufferTests() { } { Array s = {scratch}; - for (BufferIter iter = Iterate(&buffer, {0, buffer.len}); IsValid(iter); Advance(&iter)) { + for (BufferIter iter = Iterate(buffer, {0, buffer.len}); IsValid(iter); Advance(&iter)) { Assert(iter.item < 255); s.add((char)iter.item); @@ -438,7 +461,7 @@ void RunBufferTests() { } { Array s = {scratch}; - for (BufferIter iter = Iterate(&buffer, {0, buffer.len}, ITERATE_BACKWARD); IsValid(iter); Advance(&iter)) { + for (BufferIter iter = Iterate(buffer, {0, buffer.len}, ITERATE_BACKWARD); IsValid(iter); Advance(&iter)) { Assert(iter.item < 255); s.add((char)iter.item); diff --git a/src/text_editor/main.cpp b/src/text_editor/main.cpp index 09590ef..415717b 100644 --- a/src/text_editor/main.cpp +++ b/src/text_editor/main.cpp @@ -69,15 +69,29 @@ void DrawString(Font font, String text, Vector2 position, float fontSize, float } int64_t MoveRight(Buffer &buffer, int64_t pos) { - int64_t result = pos + 1; - result = AdjustUTF8Pos(buffer, result); - return result; + pos = pos + 1; + pos = AdjustUTF8Pos(buffer, pos); + Assert(pos >= 0 && pos <= buffer.len); + return pos; } int64_t MoveLeft(Buffer &buffer, int64_t pos) { - int64_t result = pos - 1; - result = AdjustUTF8Pos(buffer, result, -1); - return result; + pos = pos - 1; + pos = AdjustUTF8Pos(buffer, pos, -1); + Assert(pos >= 0 && pos <= buffer.len); + return pos; +} + +int64_t MoveDown(Buffer &buffer, int64_t pos) { + LineAndColumn info = FindLineAndColumn(buffer, pos); + int64_t new_pos = FindPos(buffer, info.line.number + 1, info.column); + return new_pos; +} + +int64_t MoveUp(Buffer &buffer, int64_t pos) { + LineAndColumn info = FindLineAndColumn(buffer, pos); + int64_t new_pos = FindPos(buffer, info.line.number - 1, info.column); + return new_pos; } int main() { @@ -99,8 +113,8 @@ int main() { Window *window = &windows[0]; Buffer *buffer = &window->buffer; InitBuffer(buffer); - if (0) { - for (int i = 0; i < 100; i += 1) { + if (1) { + for (int i = 0; i < 5; i += 1) { Array edits = {FrameArena}; AddEdit(&edits, GetEnd(*buffer), Format(FrameArena, "line number: %d\n", i)); ApplyEdits(buffer, edits); @@ -108,6 +122,7 @@ int main() { } window->cursors.add({}); + window->cursors.add(GetLine(*buffer, 1).range); } Vec2 camera_offset_world_to_render_units = {}; @@ -123,20 +138,6 @@ int main() { focused_window->window_world_to_window_units.y -= mouse_wheel; focused_window->window_world_to_window_units.y = ClampBottom(focused_window->window_world_to_window_units.y, 0.f); - for (int c = GetCharPressed(); c; c = GetCharPressed()) { - String string = "?"; - UTF8Result utf8 = UTF32ToUTF8((uint32_t)c); - if (utf8.error == 0) { - string = {(char *)utf8.out_str, (int64_t)utf8.len}; - } - - Array edits = {FrameArena}; - For(focused_window->cursors) AddEdit(&edits, it, string); - ApplyEdits(&focused_window->buffer, edits); - For(focused_window->cursors) - it.max = it.min = MoveRight(focused_window->buffer, it.min); - } - if (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) { For(focused_window->cursors) { it.max = it.min = MoveLeft(focused_window->buffer, it.min); @@ -147,6 +148,53 @@ int main() { it.max = it.min = MoveRight(focused_window->buffer, it.min); } } + if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) { + For(focused_window->cursors) { + it.max = it.min = MoveDown(focused_window->buffer, it.min); + } + } + if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) { + For(focused_window->cursors) { + it.max = it.min = MoveUp(focused_window->buffer, it.min); + } + } + + // Merge cursors that overlap + IterRemove(focused_window->cursors) { + IterRemovePrepare(focused_window->cursors); + + ForItem(cursor, focused_window->cursors) { + if (&cursor == &it) continue; + + if (cursor.min == it.min) { + remove_item = true; + break; + } + } + } + + // Handle user input chars + for (int c = GetCharPressed(); c; c = GetCharPressed()) { + String string = "?"; + UTF8Result utf8 = UTF32ToUTF8((uint32_t)c); + if (utf8.error == 0) { + string = {(char *)utf8.out_str, (int64_t)utf8.len}; + } + + Array edits = {FrameArena}; + For(focused_window->cursors) { + AddEdit(&edits, it, string); + } + ApplyEdits(&focused_window->buffer, edits); + For(focused_window->cursors) { + // Need to update current cursor and all cursors after it. + ForItem(cursor, focused_window->cursors) { + if (cursor.min >= it.min) { + cursor.min = cursor.max = MoveRight(focused_window->buffer, cursor.min); + } + } + } + } } BeginDrawing(); @@ -215,16 +263,16 @@ int main() { if (font.texture.id == 0) font = GetFontDefault(); float textOffsetX = 0.0f; float scaleFactor = font_size / font.baseSize; // Character quad scaling factor - for (int64_t i = 0;;) { - bool end_of_buffer = i == window.buffer.len; - bool new_line = i == line_range.max && i != window.buffer.len; - bool in_range = i < text.len; + + for (BufferIter iter = Iterate(window.buffer, line_range);; Advance(&iter)) { + bool end_of_buffer = iter.pos == window.buffer.len; + bool new_line = iter.pos == line_range.max; + bool in_range = IsValid(iter); bool continue_looping = end_of_buffer || new_line || in_range; if (!continue_looping) break; - int codepointByteCount = 1; - int codepoint = '\n'; - if (in_range) codepoint = GetCodepointNext(&text.data[i], &codepointByteCount); + int codepoint = '\n'; + if (in_range) codepoint = iter.item; int index = GetGlyphIndex(font, codepoint); GlyphInfo *glyph = font.glyphs + index; @@ -239,12 +287,12 @@ int main() { // Clip everything that is outside the window and screen if (CheckCollisionRecs(cell_rectangle, ToRectangle(window_rect_in_render_units_clamped_to_screen))) { - row.cells.add({cell_rect, codepoint, line_range.min + i}); + row.cells.add({cell_rect, codepoint, iter.pos}); row.rect.max = cell_rect.max; } textOffsetX += x_to_offset_by; - i += codepointByteCount; // Move text bytes counter to next codepoint + if (end_of_buffer || new_line) break; } if (row.cells.len) rows.add(row);