Skip to content

Commit 8c3090a

Browse files
authored
get rid of bounded array usage for wcwidth prev grapheme (#338)
1 parent df2bdfa commit 8c3090a

4 files changed

Lines changed: 35 additions & 19 deletions

File tree

packages/core/src/edit-buffer.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,4 +1652,21 @@ describe("EditBuffer Clear Method", () => {
16521652
expect(() => buffer.clear()).toThrow("EditBuffer is destroyed")
16531653
})
16541654
})
1655+
1656+
describe("Regression Tests", () => {
1657+
it("should handle moving left in a long line (potential BoundedArray overflow)", () => {
1658+
// Create a string longer than 256 chars (the size of BoundedArray in getPrevGraphemeStartWCWidth)
1659+
const longText = "a".repeat(500)
1660+
buffer.setText(longText)
1661+
1662+
// Move cursor to the end (or near the end)
1663+
buffer.setCursorToLineCol(0, 500)
1664+
1665+
// Move left should not crash
1666+
buffer.moveCursorLeft()
1667+
1668+
const cursor = buffer.getCursorPosition()
1669+
expect(cursor.col).toBe(499)
1670+
})
1671+
})
16551672
})

packages/core/src/zig/edit-buffer.zig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,9 @@ pub const EditBuffer = struct {
409409
}
410410

411411
pub fn moveLeft(self: *EditBuffer) void {
412-
if (self.cursors.items.len == 0) return;
412+
if (self.cursors.items.len == 0) {
413+
return;
414+
}
413415
const cursor = &self.cursors.items[0];
414416

415417
if (cursor.col > 0) {

packages/core/src/zig/text-buffer-iterators.zig

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,13 +289,16 @@ pub fn getPrevGraphemeWidth(rope: *UnifiedRope, mem_registry: *const MemRegistry
289289
const pc = prev_chunk.?;
290290
const bytes = pc.chunk.getBytes(mem_registry);
291291
const prev = utf8.getPrevGraphemeStart(bytes, bytes.len, tab_width, width_method);
292-
if (prev) |res| return res.width;
292+
if (prev) |res| {
293+
return res.width;
294+
}
293295
return 0;
294296
}
295297

296298
const bytes = chunk.getBytes(mem_registry);
297299
const is_ascii = (chunk.flags & TextChunk.Flags.ASCII_ONLY) != 0;
298300
const local_col: u32 = clamped_col - cols_before;
301+
299302
const here = utf8.findPosByWidth(bytes, local_col, tab_width, is_ascii, false, width_method);
300303

301304
const grapheme_start_col = here.columns_used;
@@ -325,7 +328,9 @@ pub fn getPrevGraphemeWidth(rope: *UnifiedRope, mem_registry: *const MemRegistry
325328
}
326329

327330
const prev = utf8.getPrevGraphemeStart(bytes, @intCast(here.byte_offset), tab_width, width_method);
328-
if (prev) |res| return res.width;
331+
if (prev) |res| {
332+
return res.width;
333+
}
329334
return 0;
330335
}
331336

packages/core/src/zig/utf8.zig

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,10 +1499,9 @@ fn getPrevGraphemeStartWCWidth(text: []const u8, byte_offset: usize, tab_width:
14991499
if (byte_offset == 0 or text.len == 0) return null;
15001500
if (byte_offset > text.len) return null;
15011501

1502-
// Build a list of all codepoint positions
1503-
var codepoint_positions = std.BoundedArray(struct { pos: usize, width: u32 }, 256).init(0) catch unreachable;
1504-
15051502
var pos: usize = 0;
1503+
var last_result: ?PrevGraphemeResult = null;
1504+
15061505
while (pos < byte_offset) {
15071506
const b = text[pos];
15081507
const curr_cp: u21 = if (b < 0x80) b else blk: {
@@ -1513,23 +1512,16 @@ fn getPrevGraphemeStartWCWidth(text: []const u8, byte_offset: usize, tab_width:
15131512
const cp_len: usize = if (b < 0x80) 1 else decodeUtf8Unchecked(text, pos).len;
15141513
const cp_width = charWidth(b, curr_cp, tab_width);
15151514

1516-
codepoint_positions.appendAssumeCapacity(.{ .pos = pos, .width = cp_width });
1517-
pos += cp_len;
1518-
}
1519-
1520-
// Find the last non-zero-width codepoint before byte_offset
1521-
var i: isize = @as(isize, @intCast(codepoint_positions.len)) - 1;
1522-
while (i >= 0) : (i -= 1) {
1523-
const idx = @as(usize, @intCast(i));
1524-
if (codepoint_positions.get(idx).width > 0) {
1525-
return .{
1526-
.start_offset = codepoint_positions.get(idx).pos,
1527-
.width = codepoint_positions.get(idx).width,
1515+
if (cp_width > 0) {
1516+
last_result = .{
1517+
.start_offset = pos,
1518+
.width = cp_width,
15281519
};
15291520
}
1521+
pos += cp_len;
15301522
}
15311523

1532-
return null;
1524+
return last_result;
15331525
}
15341526

15351527
/// Get previous grapheme start using Unicode grapheme cluster segmentation

0 commit comments

Comments
 (0)