diff --git a/packages/core/src/renderables/EditBufferRenderable.test.ts b/packages/core/src/renderables/EditBufferRenderable.test.ts index 6a063075c..7e650c934 100644 --- a/packages/core/src/renderables/EditBufferRenderable.test.ts +++ b/packages/core/src/renderables/EditBufferRenderable.test.ts @@ -227,6 +227,28 @@ describe("EditBufferRenderable", () => { expect(textarea.cursorOffset).toBe(6) }) + test("tracks the cursor and viewport past 65535 columns", async () => { + const textarea = new TextareaRenderable(renderer, { + width: 20, + height: 3, + wrapMode: "none", + }) + + renderer.root.add(textarea) + await renderOnce() + + const length = 70000 + textarea.insertText("x".repeat(length)) + await renderOnce() + + expect(textarea.logicalCursor).toMatchObject({ row: 0, col: length, offset: length }) + expect(textarea.visualCursor.offset).toBe(length) + + const viewport = textarea.editorView.getViewport() + expect(viewport.offsetX).toBeLessThanOrEqual(length) + expect(viewport.offsetX + viewport.width).toBeGreaterThan(length) + }) + test("goes to exact current line boundaries through renderable api", async () => { const textarea = new TextareaRenderable(renderer, { width: 20, diff --git a/packages/core/src/zig/bench/text-chunk-graphemes_bench.zig b/packages/core/src/zig/bench/text-chunk-graphemes_bench.zig index 6bd39f807..a1e6b83b8 100644 --- a/packages/core/src/zig/bench/text-chunk-graphemes_bench.zig +++ b/packages/core/src/zig/bench/text-chunk-graphemes_bench.zig @@ -99,14 +99,11 @@ fn benchGetGraphemes( else => false, }; - // Create TextChunk - // Width is approximate - clamped to u16 max - const approx_width: u16 = @intCast(@min(text.len, std.math.maxInt(u16))); var chunk: TextChunk = .{ .mem_id = mem_id, .byte_start = 0, .byte_end = @intCast(text.len), - .width = approx_width, + .width = @intCast(text.len), .flags = if (is_ascii) TextChunk.Flags.ASCII_ONLY else 0, }; @@ -186,12 +183,11 @@ fn computeBenchName(allocator: std.mem.Allocator, size: usize, text_type: TextTy .ascii => true, else => false, }; - const approx_width: u16 = @intCast(@min(text.len, std.math.maxInt(u16))); var chunk: TextChunk = .{ .mem_id = mem_id, .byte_start = 0, .byte_end = @intCast(text.len), - .width = approx_width, + .width = @intCast(text.len), .flags = if (is_ascii) TextChunk.Flags.ASCII_ONLY else 0, }; diff --git a/packages/core/src/zig/tests/text-buffer_test.zig b/packages/core/src/zig/tests/text-buffer_test.zig index 16714ce97..aa677a7ce 100644 --- a/packages/core/src/zig/tests/text-buffer_test.zig +++ b/packages/core/src/zig/tests/text-buffer_test.zig @@ -1559,8 +1559,25 @@ test "TextBuffer setText - CRLF at SIMD boundary" { try std.testing.expectEqual(expected_len, written); } -test "TextBuffer setText - line with multiple u16-sized chunks (SKIPPED)" { - return error.SkipZigTest; +test "TextBuffer setText - line width past u16 maximum" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + const link_pool = link.initGlobalLinkPool(std.testing.allocator); + defer link.deinitGlobalLinkPool(); + + var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode); + defer tb.deinit(); + + const length: u32 = std.math.maxInt(u16) + 1; + const text = try std.testing.allocator.alloc(u8, length); + defer std.testing.allocator.free(text); + @memset(text, 'x'); + + try tb.setText(text); + + try std.testing.expectEqual(length, iter_mod.lineWidthAt(tb.rope(), 0)); + try std.testing.expectEqual(length, iter_mod.coordsToOffset(tb.rope(), 0, length).?); + try std.testing.expectEqual(iter_mod.Coords{ .row = 0, .col = length }, iter_mod.offsetToCoords(tb.rope(), length).?); } test "TextBuffer setText - validate rope structure is correct" { diff --git a/packages/core/src/zig/text-buffer-segment.zig b/packages/core/src/zig/text-buffer-segment.zig index 220edfc17..c3e300de2 100644 --- a/packages/core/src/zig/text-buffer-segment.zig +++ b/packages/core/src/zig/text-buffer-segment.zig @@ -37,7 +37,7 @@ pub const TextChunk = struct { mem_id: u8, byte_start: u32, byte_end: u32, - width: u16, + width: u32, flags: u8 = 0, graphemes: ?[]GraphemeInfo = null, wrap_offsets: ?[]utf8.WrapBreak = null, diff --git a/packages/core/src/zig/text-buffer-view.zig b/packages/core/src/zig/text-buffer-view.zig index af5b365a8..26296c962 100644 --- a/packages/core/src/zig/text-buffer-view.zig +++ b/packages/core/src/zig/text-buffer-view.zig @@ -301,7 +301,7 @@ pub const UnifiedTextBufferView = struct { fn calculateChunkFitWord(self: *const Self, chunk: *const TextChunk, char_offset_in_chunk: u32, max_width: u32) tb.ChunkFitResult { if (max_width == 0) return .{ .char_count = 0, .width = 0 }; - const total_width = @as(u32, chunk.width) - char_offset_in_chunk; + const total_width = chunk.width - char_offset_in_chunk; if (total_width == 0) return .{ .char_count = 0, .width = 0 }; if (total_width <= max_width) return .{ .char_count = total_width, .width = total_width }; diff --git a/packages/core/src/zig/text-buffer.zig b/packages/core/src/zig/text-buffer.zig index cef075d55..284ce7159 100644 --- a/packages/core/src/zig/text-buffer.zig +++ b/packages/core/src/zig/text-buffer.zig @@ -524,13 +524,11 @@ pub const UnifiedTextBuffer = struct { flags |= TextChunk.Flags.ASCII_ONLY; } - const chunk_width: u16 = @intCast(@min(65535, utf8.calculateTextWidth(chunk_bytes, self.tab_width, is_ascii, self.width_method))); - return .{ .mem_id = mem_id, .byte_start = byte_start, .byte_end = byte_end, - .width = chunk_width, + .width = utf8.calculateTextWidth(chunk_bytes, self.tab_width, is_ascii, self.width_method), .flags = flags, }; }