lineOffsetToBytePos converts a 1-based line number and 0-based character (rune) offset to a 0-based byte position in the string. The offset is in characters, not bytes, so we iterate runes to handle multibyte UTF-8 correctly.
(s string, line, offset int)
| 110 | // The offset is in characters, not bytes, so we iterate runes to |
| 111 | // handle multibyte UTF-8 correctly. |
| 112 | func lineOffsetToBytePos(s string, line, offset int) int { |
| 113 | pos := 0 |
| 114 | currentLine := 1 |
| 115 | for pos < len(s) && currentLine < line { |
| 116 | if s[pos] == '\n' { |
| 117 | currentLine++ |
| 118 | } |
| 119 | pos++ |
| 120 | } |
| 121 | // Now advance by `offset` runes (not bytes) within the target line. |
| 122 | for i := 0; i < offset && pos < len(s); i++ { |
| 123 | _, size := utf8.DecodeRuneInString(s[pos:]) |
| 124 | pos += size |
| 125 | } |
| 126 | if pos > len(s) { |
| 127 | return len(s) |
| 128 | } |
| 129 | return pos |
| 130 | } |
| 131 | |
| 132 | // candidateKey produces a dedup key for a candidate. |
| 133 | func candidateKey(text string, typ base.CandidateType) string { |