2025-10-15 10:12:44 +03:00

72 lines
1.9 KiB
Go

package ls
import (
"cmp"
"slices"
"strings"
"unicode/utf8"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
)
type LSPLineStarts []core.TextPos
type LSPLineMap struct {
LineStarts LSPLineStarts
AsciiOnly bool // TODO(jakebailey): collect ascii-only info per line
}
func ComputeLSPLineStarts(text string) *LSPLineMap {
// This is like core.ComputeLineStarts, but only considers "\n", "\r", and "\r\n" as line breaks,
// and reports when the text is ASCII-only.
lineStarts := make([]core.TextPos, 0, strings.Count(text, "\n")+1)
asciiOnly := true
textLen := core.TextPos(len(text))
var pos core.TextPos
var lineStart core.TextPos
for pos < textLen {
b := text[pos]
if b < utf8.RuneSelf {
pos++
switch b {
case '\r':
if pos < textLen && text[pos] == '\n' {
pos++
}
fallthrough
case '\n':
lineStarts = append(lineStarts, lineStart)
lineStart = pos
}
} else {
_, size := utf8.DecodeRuneInString(text[pos:])
pos += core.TextPos(size)
asciiOnly = false
}
}
lineStarts = append(lineStarts, lineStart)
return &LSPLineMap{
LineStarts: lineStarts,
AsciiOnly: asciiOnly,
}
}
func (lm *LSPLineMap) ComputeIndexOfLineStart(targetPos core.TextPos) int {
// port of computeLineOfPosition(lineStarts: readonly number[], position: number, lowerBound?: number): number {
lineNumber, ok := slices.BinarySearchFunc(lm.LineStarts, targetPos, func(p, t core.TextPos) int {
return cmp.Compare(int(p), int(t))
})
if !ok && lineNumber > 0 {
// If the actual position was not found, the binary search returns where the target line start would be inserted
// if the target was in the slice.
// e.g. if the line starts at [5, 10, 23, 80] and the position requested was 20
// then the search will return (3, false).
//
// We want the index of the previous line start, so we subtract 1.
lineNumber = lineNumber - 1
}
return lineNumber
}