turbo-editors/turbo-corepublic Fork 0
v1.0.2
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 · 303 lines · 8.8 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 17h 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
📦 Turbo Core — a save that creates a file tells the server (workspace/didChangeWatchedFiles), so moon-lsp diagnoses a new .mbt from its first save 3561e52 k33g 7h ago89// FileChangeType says what happened to a watched file.
90type FileChangeType int
91
92// The three things that can happen to a file, as the protocol numbers them.
93const (
94 FileCreated FileChangeType = 1
95 FileChanged FileChangeType = 2
96 FileDeleted FileChangeType = 3
97)
98
99// FileEvent is one change to a file on disk.
100type FileEvent struct {
101 URI string `json:"uri"`
102 Type FileChangeType `json:"type"`
103}
104
105// DidChangeWatchedFilesParams reports changes to files on disk — as opposed
106// to documents, which are what the editor has open. A server that works a
107// project's file list out from disk learns of a new file from this and from
108// nothing else: moon-lsp does not diagnose a file it was not told exists.
109type DidChangeWatchedFilesParams struct {
110 Changes []FileEvent `json:"changes"`
111}
112
🛟 Updated. 28d5985 k33g 17h ago113// Severity is how serious a diagnostic is.
114type Severity int
115
116// The severities the protocol defines.
117const (
118 SeverityError Severity = 1
119 SeverityWarning Severity = 2
120 SeverityInformation Severity = 3
121 SeverityHint Severity = 4
122)
123
124// Diagnostic is one problem the server found in a document.
125type Diagnostic struct {
126 Range Range `json:"range"`
127 Severity Severity `json:"severity,omitempty"`
128 Source string `json:"source,omitempty"`
129 Message string `json:"message"`
130}
131
132// PublishDiagnosticsParams is the notification carrying a document's problems.
133type PublishDiagnosticsParams struct {
134 URI string `json:"uri"`
135 Diagnostics []Diagnostic `json:"diagnostics"`
136}
137
138// CompletionItemKind says what sort of thing a completion is, which is what
139// the little tag beside each entry shows.
140type CompletionItemKind int
141
142// The kinds a language server actually returns for source code.
143const (
144 KindText CompletionItemKind = 1
145 KindMethod CompletionItemKind = 2
146 KindFunction CompletionItemKind = 3
147 KindConstructor CompletionItemKind = 4
148 KindField CompletionItemKind = 5
149 KindVariable CompletionItemKind = 6
150 KindClass CompletionItemKind = 7
151 KindInterface CompletionItemKind = 8
152 KindModule CompletionItemKind = 9
153 KindProperty CompletionItemKind = 10
154 KindKeyword CompletionItemKind = 14
155 KindSnippet CompletionItemKind = 15
156 KindConstant CompletionItemKind = 21
157 KindStruct CompletionItemKind = 22
158 KindTypeParameter CompletionItemKind = 25
159)
160
161// kindNames are the short tags shown beside a completion.
162var kindNames = map[CompletionItemKind]string{
163 KindMethod: "method",
164 KindFunction: "func",
165 KindConstructor: "new",
166 KindField: "field",
167 KindVariable: "var",
168 KindClass: "type",
169 KindInterface: "iface",
170 KindModule: "pkg",
171 KindProperty: "prop",
172 KindKeyword: "kw",
173 KindSnippet: "snip",
174 KindConstant: "const",
175 KindStruct: "struct",
176 KindTypeParameter: "typar",
177 KindText: "text",
178}
179
180// String returns the short tag for a kind, or "?" for one this editor does not
181// name.
182func (k CompletionItemKind) String() string {
183 if name, ok := kindNames[k]; ok {
184 return name
185 }
186 return "?"
187}
188
189// CompletionItem is one entry of a completion list.
190type CompletionItem struct {
191 Label string `json:"label"`
192 Kind CompletionItemKind `json:"kind,omitempty"`
193 Detail string `json:"detail,omitempty"`
194 SortText string `json:"sortText,omitempty"`
195 FilterText string `json:"filterText,omitempty"`
196 InsertText string `json:"insertText,omitempty"`
197}
198
199// Insertion returns the text to put into the buffer when this item is chosen.
200//
201// A snippet's placeholder syntax is stripped: the editor has nowhere to put
202// the tab stops, and leaving "${1:x}" in the source would be worse than
203// leaving it out.
204func (i CompletionItem) Insertion() string {
205 text := i.InsertText
206 if text == "" {
207 text = i.Label
208 }
209 return stripSnippetPlaceholders(text)
210}
211
212// stripSnippetPlaceholders removes "$0", "${1:name}" and the like, keeping the
213// default text a placeholder proposes.
214func stripSnippetPlaceholders(text string) string {
215 var out strings.Builder
216
217 for i := 0; i < len(text); i++ {
218 if text[i] != '$' {
219 out.WriteByte(text[i])
220 continue
221 }
222 i += skipPlaceholder(text[i:], &out) - 1
223 }
224 return out.String()
225}
226
227// skipPlaceholder writes the useful part of the placeholder starting at text
228// and returns how many bytes of it to skip.
229func skipPlaceholder(text string, out *strings.Builder) int {
230 if len(text) > 1 && text[1] == '{' {
231 end := strings.IndexByte(text, '}')
232 if end < 0 {
233 return len(text)
234 }
235 // "${1:name}" proposes "name"; "${1}" proposes nothing.
236 if colon := strings.IndexByte(text[:end], ':'); colon >= 0 {
237 out.WriteString(text[colon+1 : end])
238 }
239 return end + 1
240 }
241
242 skip := 1
243 for skip < len(text) && text[skip] >= '0' && text[skip] <= '9' {
244 skip++
245 }
246 return skip
247}
248
249// CompletionList is what a completion request answers with.
250type CompletionList struct {
251 IsIncomplete bool `json:"isIncomplete"`
252 Items []CompletionItem `json:"items"`
253}
254
255// MarkupContent is a piece of documentation, in plain text or Markdown.
256type MarkupContent struct {
257 Kind string `json:"kind"`
258 Value string `json:"value"`
259}
260
261// Hover is what a hover request answers with.
262type Hover struct {
263 Contents MarkupContent `json:"contents"`
264 Range *Range `json:"range,omitempty"`
265}
266
267// RuneToUTF16 converts a rune column on a line into the UTF-16 code-unit
268// offset the protocol wants.
269//
270// lsp.RuneToUTF16("héllo", 3) // 3 — é is one UTF-16 unit
271// lsp.RuneToUTF16("𝄞x", 1) // 2 — the clef needs a surrogate pair
272func RuneToUTF16(line string, runeColumn int) int {
273 units := 0
274 for i, r := range []rune(line) {
275 if i >= runeColumn {
276 break
277 }
278 units += utf16Len(r)
279 }
280 return units
281}
282
283// UTF16ToRune converts a UTF-16 code-unit offset into a rune column, which is
284// what the editor counts in.
285func UTF16ToRune(line string, utf16Column int) int {
286 units := 0
287 for i, r := range []rune(line) {
288 if units >= utf16Column {
289 return i
290 }
291 units += utf16Len(r)
292 }
293 return len([]rune(line))
294}
295
296// utf16Len returns how many UTF-16 code units a rune needs: two for anything
297// outside the basic multilingual plane, one otherwise.
298func utf16Len(r rune) int {
299 if r > 0xFFFF {
300 return len(utf16.Encode([]rune{r}))
301 }
302 return 1
303}