langserver
1.x
Docs/langserver/Positions
Open

Reading1 min
Updated2 Aug 2026
Sourcev1/positions.mdx

Positions

LSP addresses a position as a line plus a character counted in UTF-16 code units. Go indexes strings by byte.

For ASCII the two coincide. That is precisely why the difference survives review: everything works until a document contains an accent, a CJK character or an emoji.

offset := lsp.OffsetFromPosition(src, lsp.Position{Line: 2, Character: 14})
pos    := lsp.PositionFromOffset(src, offset)

Why counting runes is also wrong01

A rune above the Basic Multilingual Plane, which is most emoji, is one rune, up to four bytes, and two code units.

MeasureAn emoji above the BMP
Runes1
Bytesup to 4
UTF-16 code units2

Counting runes is as wrong as counting bytes. It just fails later, and on a narrower set of documents, which makes it harder to find.

Out-of-range positions clamp02

A position past the end of a line or a document is clamped instead of returning an error.

This is deliberate. Editors legitimately send positions against a document version the server has not applied yet, and treating that as a protocol violation produces a server that disconnects during ordinary fast typing.