all repos

clerk @ 1925839

missing tooling for ledger/hledger

clerk/internal/lsp/lsputil/utf16.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
perf: lsp: resolve positions and entries via cached line index..., 1 month ago
1
package lsputil
2
3
import (
4
	"unicode/utf8"
5
6
	"go.lsp.dev/protocol"
7
)
8
9
// Utf16Col returns the UTF-16 code unit column (0-based) for a byte offset
10
// within the given line content (without newline).
11
func Utf16Col(line string, byteOffset int) int {
12
	if byteOffset <= 0 {
13
		return 0
14
	}
15
	if byteOffset > len(line) {
16
		byteOffset = len(line)
17
	}
18
	col := 0
19
	for i := 0; i < byteOffset; {
20
		r, size := utf8.DecodeRuneInString(line[i:])
21
		if r == utf8.RuneError && size <= 1 {
22
			break
23
		}
24
		col += utf16Len(r)
25
		i += size
26
	}
27
	return col
28
}
29
30
// Utf16ColBytes returns the UTF-16 code unit column of b (without newline).
31
func Utf16ColBytes(b []byte) int {
32
	col := 0
33
	for i := 0; i < len(b); {
34
		r, size := utf8.DecodeRune(b[i:])
35
		if r == utf8.RuneError && size <= 1 {
36
			break
37
		}
38
		col += utf16Len(r)
39
		i += size
40
	}
41
	return col
42
}
43
44
// Utf16Len returns the UTF-16 code unit length of content[offset:end].
45
func Utf16Len(content string, offset, end int) int {
46
	if offset < 0 {
47
		offset = 0
48
	}
49
	if end > len(content) {
50
		end = len(content)
51
	}
52
	if offset >= end {
53
		return 0
54
	}
55
	n := 0
56
	for i := offset; i < end; {
57
		r, size := utf8.DecodeRuneInString(content[i:])
58
		if r == utf8.RuneError && size <= 1 {
59
			break
60
		}
61
		n += utf16Len(r)
62
		i += size
63
	}
64
	return n
65
}
66
67
// Offset converts a 0-based line and UTF-16 code unit column to a byte offset
68
// in content, clamped to the content bounds. The inverse of LineCol.
69
func Offset(content string, line, col int) int {
70
	if line < 0 {
71
		line = 0
72
	}
73
	off := 0
74
	for curLine := 0; curLine < line && off < len(content); {
75
		switch content[off] {
76
		case '\n':
77
			off++
78
			curLine++
79
		case '\r':
80
			off++
81
			if off < len(content) && content[off] == '\n' {
82
				off++
83
			}
84
			curLine++
85
		default:
86
			for off < len(content) && content[off] != '\n' && content[off] != '\r' {
87
				off++
88
			}
89
		}
90
	}
91
	lineEnd := off
92
	for lineEnd < len(content) && content[lineEnd] != '\n' && content[lineEnd] != '\r' {
93
		lineEnd++
94
	}
95
	units := 0
96
	for off < lineEnd && units < col {
97
		r, size := utf8.DecodeRuneInString(content[off:])
98
		off += size
99
		units += utf16Len(r)
100
	}
101
	return off
102
}
103
104
// LineCol converts a byte offset in content to 0-based line number and
105
// UTF-16 code unit column.
106
func LineCol(content string, offset int) (line int, col int) {
107
	if offset <= 0 {
108
		return 0, 0
109
	}
110
	if offset > len(content) {
111
		offset = len(content)
112
	}
113
	lineStart := 0
114
	lineNum := 0
115
	for i := 0; i < len(content); {
116
		r, size := utf8.DecodeRuneInString(content[i:])
117
		if r == utf8.RuneError && size <= 1 {
118
			break
119
		}
120
		if r == '\n' {
121
			if offset < i+size {
122
				col = Utf16Col(content[lineStart:offset], offset-lineStart)
123
				return lineNum, col
124
			}
125
			lineStart = i + size
126
			lineNum++
127
			i += size
128
			continue
129
		}
130
		if r == '\r' {
131
			// skip \r\n
132
			if i+1 < len(content) && content[i+1] == '\n' {
133
				size++
134
			}
135
			if offset < i+size {
136
				col = Utf16Col(content[lineStart:offset], offset-lineStart)
137
				return lineNum, col
138
			}
139
			lineStart = i + size
140
			lineNum++
141
			i += size
142
			continue
143
		}
144
		if i+size > offset {
145
			col = Utf16Col(content[lineStart:offset], offset-lineStart)
146
			return lineNum, col
147
		}
148
		i += size
149
	}
150
	col = Utf16Col(content[lineStart:], len(content)-lineStart)
151
	return lineNum, col
152
}
153
154
// Position converts a byte offset to an LSP position (0-based line, UTF-16
155
// code unit column). The inverse of Offset at the protocol.Position level.
156
func Position(content string, offset int) protocol.Position {
157
	line, col := LineCol(content, offset)
158
	return protocol.Position{Line: uint32(line), Character: uint32(col)}
159
}
160
161
// utf16Len returns the number of UTF-16 code units for a rune.
162
func utf16Len(r rune) int {
163
	if r >= 0x10000 && r <= 0x10FFFF {
164
		return 2
165
	}
166
	return 1
167
}