turbo-editors/turbo-corepublic Fork 0
v0.9.0
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.

🛟 Updated. 28d5985 · on v0.9.0 · k33g · 14h ago
protocol.go · 279 lines · 8.0 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
package lsp

import (
	"strings"
	"unicode/utf16"
)

// Position is a place in a document, as the protocol counts them: a zero-based
// line, and a character offset counted in **UTF-16 code units**.
//
// That last part is the trap. The editor counts columns in runes, so every
// position crossing this boundary goes through RuneToUTF16 or UTF16ToRune. On
// plain ASCII the two agree, which is exactly why getting it wrong survives
// testing until someone opens a file with an accent in it.
type Position struct {
	Line      int `json:"line"`
	Character int `json:"character"`
}

// Range is a span of a document.
type Range struct {
	Start Position `json:"start"`
	End   Position `json:"end"`
}

// Location is a range in a named document.
type Location struct {
	URI   string `json:"uri"`
	Range Range  `json:"range"`
}

// TextDocumentIdentifier names a document.
type TextDocumentIdentifier struct {
	URI string `json:"uri"`
}

// VersionedTextDocumentIdentifier names a document and says which revision of
// it is being talked about.
type VersionedTextDocumentIdentifier struct {
	URI     string `json:"uri"`
	Version int    `json:"version"`
}

// TextDocumentItem is a document being opened, content included.
type TextDocumentItem struct {
	URI        string `json:"uri"`
	LanguageID string `json:"languageId"`
	Version    int    `json:"version"`
	Text       string `json:"text"`
}

// TextDocumentPositionParams is the shape most requests take: a document and a
// place in it.
type TextDocumentPositionParams struct {
	TextDocument TextDocumentIdentifier `json:"textDocument"`
	Position     Position               `json:"position"`
}

// DidOpenParams announces a newly opened document.
type DidOpenParams struct {
	TextDocument TextDocumentItem `json:"textDocument"`
}

// DidChangeParams carries a new version of a document.
type DidChangeParams struct {
	TextDocument   VersionedTextDocumentIdentifier `json:"textDocument"`
	ContentChanges []ContentChange                 `json:"contentChanges"`
}

// ContentChange is one edit. The editor always sends the whole document, so
// only Text is ever set: the servers this talks to are fast enough that
// incremental sync would buy
// complexity rather than speed.
type ContentChange struct {
	Text string `json:"text"`
}

// DidCloseParams announces that a document is no longer open.
type DidCloseParams struct {
	TextDocument TextDocumentIdentifier `json:"textDocument"`
}

// DidSaveParams announces that a document has been written to disk.
type DidSaveParams struct {
	TextDocument TextDocumentIdentifier `json:"textDocument"`
	Text         string                 `json:"text,omitempty"`
}

// Severity is how serious a diagnostic is.
type Severity int

// The severities the protocol defines.
const (
	SeverityError       Severity = 1
	SeverityWarning     Severity = 2
	SeverityInformation Severity = 3
	SeverityHint        Severity = 4
)

// Diagnostic is one problem the server found in a document.
type Diagnostic struct {
	Range    Range    `json:"range"`
	Severity Severity `json:"severity,omitempty"`
	Source   string   `json:"source,omitempty"`
	Message  string   `json:"message"`
}

// PublishDiagnosticsParams is the notification carrying a document's problems.
type PublishDiagnosticsParams struct {
	URI         string       `json:"uri"`
	Diagnostics []Diagnostic `json:"diagnostics"`
}

// CompletionItemKind says what sort of thing a completion is, which is what
// the little tag beside each entry shows.
type CompletionItemKind int

// The kinds a language server actually returns for source code.
const (
	KindText          CompletionItemKind = 1
	KindMethod        CompletionItemKind = 2
	KindFunction      CompletionItemKind = 3
	KindConstructor   CompletionItemKind = 4
	KindField         CompletionItemKind = 5
	KindVariable      CompletionItemKind = 6
	KindClass         CompletionItemKind = 7
	KindInterface     CompletionItemKind = 8
	KindModule        CompletionItemKind = 9
	KindProperty      CompletionItemKind = 10
	KindKeyword       CompletionItemKind = 14
	KindSnippet       CompletionItemKind = 15
	KindConstant      CompletionItemKind = 21
	KindStruct        CompletionItemKind = 22
	KindTypeParameter CompletionItemKind = 25
)

// kindNames are the short tags shown beside a completion.
var kindNames = map[CompletionItemKind]string{
	KindMethod:        "method",
	KindFunction:      "func",
	KindConstructor:   "new",
	KindField:         "field",
	KindVariable:      "var",
	KindClass:         "type",
	KindInterface:     "iface",
	KindModule:        "pkg",
	KindProperty:      "prop",
	KindKeyword:       "kw",
	KindSnippet:       "snip",
	KindConstant:      "const",
	KindStruct:        "struct",
	KindTypeParameter: "typar",
	KindText:          "text",
}

// String returns the short tag for a kind, or "?" for one this editor does not
// name.
func (k CompletionItemKind) String() string {
	if name, ok := kindNames[k]; ok {
		return name
	}
	return "?"
}

// CompletionItem is one entry of a completion list.
type CompletionItem struct {
	Label      string             `json:"label"`
	Kind       CompletionItemKind `json:"kind,omitempty"`
	Detail     string             `json:"detail,omitempty"`
	SortText   string             `json:"sortText,omitempty"`
	FilterText string             `json:"filterText,omitempty"`
	InsertText string             `json:"insertText,omitempty"`
}

// Insertion returns the text to put into the buffer when this item is chosen.
//
// A snippet's placeholder syntax is stripped: the editor has nowhere to put
// the tab stops, and leaving "${1:x}" in the source would be worse than
// leaving it out.
func (i CompletionItem) Insertion() string {
	text := i.InsertText
	if text == "" {
		text = i.Label
	}
	return stripSnippetPlaceholders(text)
}

// stripSnippetPlaceholders removes "$0", "${1:name}" and the like, keeping the
// default text a placeholder proposes.
func stripSnippetPlaceholders(text string) string {
	var out strings.Builder

	for i := 0; i < len(text); i++ {
		if text[i] != '$' {
			out.WriteByte(text[i])
			continue
		}
		i += skipPlaceholder(text[i:], &out) - 1
	}
	return out.String()
}

// skipPlaceholder writes the useful part of the placeholder starting at text
// and returns how many bytes of it to skip.
func skipPlaceholder(text string, out *strings.Builder) int {
	if len(text) > 1 && text[1] == '{' {
		end := strings.IndexByte(text, '}')
		if end < 0 {
			return len(text)
		}
		// "${1:name}" proposes "name"; "${1}" proposes nothing.
		if colon := strings.IndexByte(text[:end], ':'); colon >= 0 {
			out.WriteString(text[colon+1 : end])
		}
		return end + 1
	}

	skip := 1
	for skip < len(text) && text[skip] >= '0' && text[skip] <= '9' {
		skip++
	}
	return skip
}

// CompletionList is what a completion request answers with.
type CompletionList struct {
	IsIncomplete bool             `json:"isIncomplete"`
	Items        []CompletionItem `json:"items"`
}

// MarkupContent is a piece of documentation, in plain text or Markdown.
type MarkupContent struct {
	Kind  string `json:"kind"`
	Value string `json:"value"`
}

// Hover is what a hover request answers with.
type Hover struct {
	Contents MarkupContent `json:"contents"`
	Range    *Range        `json:"range,omitempty"`
}

// RuneToUTF16 converts a rune column on a line into the UTF-16 code-unit
// offset the protocol wants.
//
//	lsp.RuneToUTF16("héllo", 3) // 3 — é is one UTF-16 unit
//	lsp.RuneToUTF16("𝄞x", 1)    // 2 — the clef needs a surrogate pair
func RuneToUTF16(line string, runeColumn int) int {
	units := 0
	for i, r := range []rune(line) {
		if i >= runeColumn {
			break
		}
		units += utf16Len(r)
	}
	return units
}

// UTF16ToRune converts a UTF-16 code-unit offset into a rune column, which is
// what the editor counts in.
func UTF16ToRune(line string, utf16Column int) int {
	units := 0
	for i, r := range []rune(line) {
		if units >= utf16Column {
			return i
		}
		units += utf16Len(r)
	}
	return len([]rune(line))
}

// utf16Len returns how many UTF-16 code units a rune needs: two for anything
// outside the basic multilingual plane, one otherwise.
func utf16Len(r rune) int {
	if r > 0xFFFF {
		return len(utf16.Encode([]rune{r}))
	}
	return 1
}