Skip to content

Commit 5a200e7

Browse files
committed
utf16 fix
1 parent 4284d51 commit 5a200e7

1 file changed

Lines changed: 10 additions & 0 deletions

File tree

src/core/lsp/Protocol.zig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,14 @@ pub fn byteOffsetToPosition(text: []const u8, byte_offset: usize, encoding: Posi
3838
const line_bytes = text[line_start..offset];
3939
const character: u32 = switch (encoding) {
4040
.utf8 => @intCast(line_bytes.len),
41+
// `byte_offset` comes from UI-side hit-testing, not zls, so it isn't guaranteed to
42+
// land on a UTF-8 character boundary — `Utf8View.initUnchecked` trusts its input is
43+
// valid UTF-8 and its iterator does `catch unreachable` on a bad byte sequence
44+
// (crashed on Windows: "reached unreachable code" on every hover). Validate first and
45+
// fall back to a byte count, which is wrong by at most a few units for the rare
46+
// misaligned/invalid case, rather than panicking.
4147
.utf16 => blk: {
48+
if (!std.unicode.utf8ValidateSlice(line_bytes)) break :blk @intCast(line_bytes.len);
4249
var units: u32 = 0;
4350
var view = std.unicode.Utf8View.initUnchecked(line_bytes);
4451
var it = view.iterator();
@@ -67,7 +74,10 @@ pub fn positionToByteOffset(text: []const u8, pos: Position, encoding: PositionE
6774

6875
switch (encoding) {
6976
.utf8 => return line_start + @min(pos.character, line_bytes.len),
77+
// See `byteOffsetToPosition`'s matching `.utf16` branch for why this validates first
78+
// instead of trusting `initUnchecked`.
7079
.utf16 => {
80+
if (!std.unicode.utf8ValidateSlice(line_bytes)) return line_start + @min(pos.character, line_bytes.len);
7181
var units: u32 = 0;
7282
var byte_off: usize = 0;
7383
var view = std.unicode.Utf8View.initUnchecked(line_bytes);

0 commit comments

Comments
 (0)