all repos

clerk @ 03eda2a2e8694ad823182b6c81023d17e91b2385

missing tooling for ledger/hledger

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

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
lsp: completion, 2 months 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
// Utf16Len returns the UTF-16 code unit length of content[offset:end].
31
func Utf16Len(content string, offset, end int) int {
32
	if offset < 0 {
33
		offset = 0
34
	}
35
	if end > len(content) {
36
		end = len(content)
37
	}
38
	if offset >= end {
39
		return 0
40
	}
41
	n := 0
42
	for i := offset; i < end; {
43
		r, size := utf8.DecodeRuneInString(content[i:])
44
		if r == utf8.RuneError && size <= 1 {
45
			break
46
		}
47
		n += utf16Len(r)
48
		i += size
49
	}
50
	return n
51
}
52
53
// Offset converts a 0-based line and UTF-16 code unit column to a byte offset
54
// in content, clamped to the content bounds. The inverse of LineCol.
55
func Offset(content string, line, col int) int {
56
	if line < 0 {
57
		line = 0
58
	}
59
	off := 0
60
	for curLine := 0; curLine < line && off < len(content); {
61
		switch content[off] {
62
		case '\n':
63
			off++
64
			curLine++
65
		case '\r':
66
			off++
67
			if off < len(content) && content[off] == '\n' {
68
				off++
69
			}
70
			curLine++
71
		default:
72
			for off < len(content) && content[off] != '\n' && content[off] != '\r' {
73
				off++
74
			}
75
		}
76
	}
77
	lineEnd := off
78
	for lineEnd < len(content) && content[lineEnd] != '\n' && content[lineEnd] != '\r' {
79
		lineEnd++
80
	}
81
	units := 0
82
	for off < lineEnd && units < col {
83
		r, size := utf8.DecodeRuneInString(content[off:])
84
		off += size
85
		units += utf16Len(r)
86
	}
87
	return off
88
}
89
90
// LineCol converts a byte offset in content to 0-based line number and
91
// UTF-16 code unit column.
92
func LineCol(content string, offset int) (line int, col int) {
93
	if offset <= 0 {
94
		return 0, 0
95
	}
96
	if offset > len(content) {
97
		offset = len(content)
98
	}
99
	lineStart := 0
100
	lineNum := 0
101
	for i := 0; i < len(content); {
102
		r, size := utf8.DecodeRuneInString(content[i:])
103
		if r == utf8.RuneError && size <= 1 {
104
			break
105
		}
106
		if r == '\n' {
107
			if offset < i+size {
108
				col = Utf16Col(content[lineStart:offset], offset-lineStart)
109
				return lineNum, col
110
			}
111
			lineStart = i + size
112
			lineNum++
113
			i += size
114
			continue
115
		}
116
		if r == '\r' {
117
			// skip \r\n
118
			if i+1 < len(content) && content[i+1] == '\n' {
119
				size++
120
			}
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 i+size > offset {
131
			col = Utf16Col(content[lineStart:offset], offset-lineStart)
132
			return lineNum, col
133
		}
134
		i += size
135
	}
136
	col = Utf16Col(content[lineStart:], len(content)-lineStart)
137
	return lineNum, col
138
}
139
140
// Position converts a byte offset to an LSP position (0-based line, UTF-16
141
// code unit column). The inverse of Offset at the protocol.Position level.
142
func Position(content string, offset int) protocol.Position {
143
	line, col := LineCol(content, offset)
144
	return protocol.Position{Line: uint32(line), Character: uint32(col)}
145
}
146
147
// utf16Len returns the number of UTF-16 code units for a rune.
148
func utf16Len(r rune) int {
149
	if r >= 0x10000 && r <= 0x10FFFF {
150
		return 2
151
	}
152
	return 1
153
}