diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-16 04:38:32 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-16 04:38:32 +0200 |
| commit | 5e0cf1ede41b2887db98ca61c8100cbe1da61170 (patch) | |
| tree | d3d172643fb18d8e9b03af591125909babb9c0d8 /internal/lsp/utf16_offset_test.go | |
| parent | 409cec495ae619fa874e0e827ac620b881f84941 (diff) | |
Fix byte vs UTF-16 indexing in LSP position handling
Adds utf16OffsetToByteOffset helper to correctly convert LSP character
positions (UTF-16 code units) to Go string byte offsets. Fixes trigger
detection, prefix heuristic, and completion text slicing for files
containing multi-byte characters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/lsp/utf16_offset_test.go')
| -rw-r--r-- | internal/lsp/utf16_offset_test.go | 59 |
1 files changed, 59 insertions, 0 deletions
diff --git a/internal/lsp/utf16_offset_test.go b/internal/lsp/utf16_offset_test.go new file mode 100644 index 0000000..49a0fa0 --- /dev/null +++ b/internal/lsp/utf16_offset_test.go @@ -0,0 +1,59 @@ +package lsp + +import "testing" + +func TestUTF16OffsetToByteOffset_ASCII(t *testing.T) { + s := "hello world" + if got := utf16OffsetToByteOffset(s, 5); got != 5 { + t.Fatalf("expected 5, got %d", got) + } +} + +func TestUTF16OffsetToByteOffset_MultiByte(t *testing.T) { + // "aé" — 'a' is 1 byte/1 UTF-16 unit, 'é' is 2 bytes/1 UTF-16 unit + s := "aé" + // UTF-16 offset 1 → byte 1 (after 'a') + if got := utf16OffsetToByteOffset(s, 1); got != 1 { + t.Fatalf("expected 1 after 'a', got %d", got) + } + // UTF-16 offset 2 → byte 3 (after 'é' which is 2 UTF-8 bytes) + if got := utf16OffsetToByteOffset(s, 2); got != 3 { + t.Fatalf("expected 3 after 'é', got %d", got) + } +} + +func TestUTF16OffsetToByteOffset_Emoji(t *testing.T) { + // "a🎉b" — 'a' is 1/1, '🎉' is 4 bytes / 2 UTF-16 units, 'b' is 1/1 + s := "a🎉b" + // UTF-16 offset 1 → byte 1 (after 'a') + if got := utf16OffsetToByteOffset(s, 1); got != 1 { + t.Fatalf("expected 1, got %d", got) + } + // UTF-16 offset 3 → byte 5 (after '🎉' which is 4 bytes, 2 UTF-16 units) + if got := utf16OffsetToByteOffset(s, 3); got != 5 { + t.Fatalf("expected 5 after emoji, got %d", got) + } + // UTF-16 offset 4 → byte 6 (after 'b') + if got := utf16OffsetToByteOffset(s, 4); got != 6 { + t.Fatalf("expected 6, got %d", got) + } +} + +func TestUTF16OffsetToByteOffset_BeyondEnd(t *testing.T) { + s := "abc" + if got := utf16OffsetToByteOffset(s, 10); got != 3 { + t.Fatalf("expected len(s)=3 for offset beyond end, got %d", got) + } +} + +func TestUTF16OffsetToByteOffset_Empty(t *testing.T) { + if got := utf16OffsetToByteOffset("", 0); got != 0 { + t.Fatalf("expected 0 for empty string, got %d", got) + } +} + +func TestUTF16OffsetToByteOffset_Zero(t *testing.T) { + if got := utf16OffsetToByteOffset("hello", 0); got != 0 { + t.Fatalf("expected 0 for offset 0, got %d", got) + } +} |
