turbo-editors/turbo-corepublic Fork 0
d662cebdb65b319885da903daf7eff9ab1bfbb78
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

protocol.go · 279 lines · 8.0 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 21h ago1package lsp
2
3import (
4 "strings"
5 "unicode/utf16"
6)
7
8// Position is a place in a document, as the protocol counts them: a zero-based
9// line, and a character offset counted in **UTF-16 code units**.
10//
11// That last part is the trap. The editor counts columns in runes, so every
12// position crossing this boundary goes through RuneToUTF16 or UTF16ToRune. On
13// plain ASCII the two agree, which is exactly why getting it wrong survives
14// testing until someone opens a file with an accent in it.
15type Position struct {
16 Line int `json:"line"`
17 Character int `json:"character"`
18}
19
20// Range is a span of a document.
21type Range struct {
22 Start Position `json:"start"`
23 End Position `json:"end"`
24}
25
26// Location is a range in a named document.
27type Location struct {
28 URI string `json:"uri"`
29 Range Range `json:"range"`
30}
31
32// TextDocumentIdentifier names a document.
33type TextDocumentIdentifier struct {
34 URI string `json:"uri"`
35}
36
37// VersionedTextDocumentIdentifier names a document and says which revision of
38// it is being talked about.
39type VersionedTextDocumentIdentifier struct {
40 URI string `json:"uri"`
41 Version int `json:"version"`
42}
43
44// TextDocumentItem is a document being opened, content included.
45type TextDocumentItem struct {
46 URI string `json:"uri"`
47 LanguageID string `json:"languageId"`
48 Version int `json:"version"`
49 Text string `json:"text"`
50}
51
52// TextDocumentPositionParams is the shape most requests take: a document and a
53// place in it.
54type TextDocumentPositionParams struct {
55 TextDocument TextDocumentIdentifier `json:"textDocument"`
56 Position Position `json:"position"`
57}
58
59// DidOpenParams announces a newly opened document.
60type DidOpenParams struct {
61 TextDocument TextDocumentItem `json:"textDocument"`
62}
63
64// DidChangeParams carries a new version of a document.
65type DidChangeParams struct {
66 TextDocument VersionedTextDocumentIdentifier `json:"textDocument"`
67 ContentChanges []ContentChange `json:"contentChanges"`
68}
69
70// ContentChange is one edit. The editor always sends the whole document, so
71// only Text is ever set: the servers this talks to are fast enough that
72// incremental sync would buy
73// complexity rather than speed.
74type ContentChange struct {
75 Text string `json:"text"`
76}
77
78// DidCloseParams announces that a document is no longer open.
79type DidCloseParams struct {
80 TextDocument TextDocumentIdentifier `json:"textDocument"`
81}
82
83// DidSaveParams announces that a document has been written to disk.
84type DidSaveParams struct {
85 TextDocument TextDocumentIdentifier `json:"textDocument"`
86 Text string `json:"text,omitempty"`
87}
88
89// Severity is how serious a diagnostic is.
90type Severity int
91
92// The severities the protocol defines.
93const (
94 SeverityError Severity = 1
95 SeverityWarning Severity = 2
96 SeverityInformation Severity = 3
97 SeverityHint Severity = 4
98)
99
100// Diagnostic is one problem the server found in a document.
101type Diagnostic struct {
102 Range Range `json:"range"`
103 Severity Severity `json:"severity,omitempty"`
104 Source string `json:"source,omitempty"`
105 Message string `json:"message"`
106}
107
108// PublishDiagnosticsParams is the notification carrying a document's problems.
109type PublishDiagnosticsParams struct {
110 URI string `json:"uri"`
111 Diagnostics []Diagnostic `json:"diagnostics"`
112}
113
114// CompletionItemKind says what sort of thing a completion is, which is what
115// the little tag beside each entry shows.
116type CompletionItemKind int
117
118// The kinds a language server actually returns for source code.
119const (
120 KindText CompletionItemKind = 1
121 KindMethod CompletionItemKind = 2
122 KindFunction CompletionItemKind = 3
123 KindConstructor CompletionItemKind = 4
124 KindField CompletionItemKind = 5
125 KindVariable CompletionItemKind = 6
126 KindClass CompletionItemKind = 7
127 KindInterface CompletionItemKind = 8
128 KindModule CompletionItemKind = 9
129 KindProperty CompletionItemKind = 10
130 KindKeyword CompletionItemKind = 14
131 KindSnippet CompletionItemKind = 15
132 KindConstant CompletionItemKind = 21
133 KindStruct CompletionItemKind = 22
134 KindTypeParameter CompletionItemKind = 25
135)
136
137// kindNames are the short tags shown beside a completion.
138var kindNames = map[CompletionItemKind]string{
139 KindMethod: "method",
140 KindFunction: "func",
141 KindConstructor: "new",
142 KindField: "field",
143 KindVariable: "var",
144 KindClass: "type",
145 KindInterface: "iface",
146 KindModule: "pkg",
147 KindProperty: "prop",
148 KindKeyword: "kw",
149 KindSnippet: "snip",
150 KindConstant: "const",
151 KindStruct: "struct",
152 KindTypeParameter: "typar",
153 KindText: "text",
154}
155
156// String returns the short tag for a kind, or "?" for one this editor does not
157// name.
158func (k CompletionItemKind) String() string {
159 if name, ok := kindNames[k]; ok {
160 return name
161 }
162 return "?"
163}
164
165// CompletionItem is one entry of a completion list.
166type CompletionItem struct {
167 Label string `json:"label"`
168 Kind CompletionItemKind `json:"kind,omitempty"`
169 Detail string `json:"detail,omitempty"`
170 SortText string `json:"sortText,omitempty"`
171 FilterText string `json:"filterText,omitempty"`
172 InsertText string `json:"insertText,omitempty"`
173}
174
175// Insertion returns the text to put into the buffer when this item is chosen.
176//
177// A snippet's placeholder syntax is stripped: the editor has nowhere to put
178// the tab stops, and leaving "${1:x}" in the source would be worse than
179// leaving it out.
180func (i CompletionItem) Insertion() string {
181 text := i.InsertText
182 if text == "" {
183 text = i.Label
184 }
185 return stripSnippetPlaceholders(text)
186}
187
188// stripSnippetPlaceholders removes "$0", "${1:name}" and the like, keeping the
189// default text a placeholder proposes.
190func stripSnippetPlaceholders(text string) string {
191 var out strings.Builder
192
193 for i := 0; i < len(text); i++ {
194 if text[i] != '$' {
195 out.WriteByte(text[i])
196 continue
197 }
198 i += skipPlaceholder(text[i:], &out) - 1
199 }
200 return out.String()
201}
202
203// skipPlaceholder writes the useful part of the placeholder starting at text
204// and returns how many bytes of it to skip.
205func skipPlaceholder(text string, out *strings.Builder) int {
206 if len(text) > 1 && text[1] == '{' {
207 end := strings.IndexByte(text, '}')
208 if end < 0 {
209 return len(text)
210 }
211 // "${1:name}" proposes "name"; "${1}" proposes nothing.
212 if colon := strings.IndexByte(text[:end], ':'); colon >= 0 {
213 out.WriteString(text[colon+1 : end])
214 }
215 return end + 1
216 }
217
218 skip := 1
219 for skip < len(text) && text[skip] >= '0' && text[skip] <= '9' {
220 skip++
221 }
222 return skip
223}
224
225// CompletionList is what a completion request answers with.
226type CompletionList struct {
227 IsIncomplete bool `json:"isIncomplete"`
228 Items []CompletionItem `json:"items"`
229}
230
231// MarkupContent is a piece of documentation, in plain text or Markdown.
232type MarkupContent struct {
233 Kind string `json:"kind"`
234 Value string `json:"value"`
235}
236
237// Hover is what a hover request answers with.
238type Hover struct {
239 Contents MarkupContent `json:"contents"`
240 Range *Range `json:"range,omitempty"`
241}
242
243// RuneToUTF16 converts a rune column on a line into the UTF-16 code-unit
244// offset the protocol wants.
245//
246// lsp.RuneToUTF16("héllo", 3) // 3 — é is one UTF-16 unit
247// lsp.RuneToUTF16("𝄞x", 1) // 2 — the clef needs a surrogate pair
248func RuneToUTF16(line string, runeColumn int) int {
249 units := 0
250 for i, r := range []rune(line) {
251 if i >= runeColumn {
252 break
253 }
254 units += utf16Len(r)
255 }
256 return units
257}
258
259// UTF16ToRune converts a UTF-16 code-unit offset into a rune column, which is
260// what the editor counts in.
261func UTF16ToRune(line string, utf16Column int) int {
262 units := 0
263 for i, r := range []rune(line) {
264 if units >= utf16Column {
265 return i
266 }
267 units += utf16Len(r)
268 }
269 return len([]rune(line))
270}
271
272// utf16Len returns how many UTF-16 code units a rune needs: two for anything
273// outside the basic multilingual plane, one otherwise.
274func utf16Len(r rune) int {
275 if r > 0xFFFF {
276 return len(utf16.Encode([]rune{r}))
277 }
278 return 1
279}