LSP-server-example

Форк
0
220 строк · 6.9 Кб
1
// //nolint
2

3
// Copyright 2023 The Go Authors. All rights reserved.
4
// Use of this source code is governed by a BSD-style
5
// license that can be found in the LICENSE file.
6

7
package protocol
8

9
// This file declares URI, DocumentURI, and its methods.
10
//
11
// For the LSP definition of these types, see
12
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#uri
13

14
import (
15
	"fmt"
16
	"net/url"
17
	"path/filepath"
18
	"strings"
19
	"unicode"
20
)
21

22
// A DocumentURI is the URI of a client editor document.
23
//
24
// According to the LSP specification:
25
//
26
//	Care should be taken to handle encoding in URIs. For
27
//	example, some clients (such as VS Code) may encode colons
28
//	in drive letters while others do not. The URIs below are
29
//	both valid, but clients and servers should be consistent
30
//	with the form they use themselves to ensure the other party
31
//	doesn’t interpret them as distinct URIs. Clients and
32
//	servers should not assume that each other are encoding the
33
//	same way (for example a client encoding colons in drive
34
//	letters cannot assume server responses will have encoded
35
//	colons). The same applies to casing of drive letters - one
36
//	party should not assume the other party will return paths
37
//	with drive letters cased the same as it.
38
//
39
//	file:///c:/project/readme.md
40
//	file:///C%3A/project/readme.md
41
//
42
// This is done during JSON unmarshalling;
43
// see [DocumentURI.UnmarshalText] for details.
44
type DocumentURI string
45

46
// A URI is an arbitrary URL (e.g. https), not necessarily a file.
47
type URI = string
48

49
// UnmarshalText implements decoding of DocumentURI values.
50
//
51
// In particular, it implements a systematic correction of various odd
52
// features of the definition of DocumentURI in the LSP spec that
53
// appear to be workarounds for bugs in VS Code. For example, it may
54
// URI-encode the URI itself, so that colon becomes %3A, and it may
55
// send file://foo.go URIs that have two slashes (not three) and no
56
// hostname.
57
//
58
// We use UnmarshalText, not UnmarshalJSON, because it is called even
59
// for non-addressable values such as keys and values of map[K]V,
60
// where there is no pointer of type *K or *V on which to call
61
// UnmarshalJSON. (See Go issue #28189 for more detail.)
62
//
63
// Non-empty DocumentURIs are valid "file"-scheme URIs.
64
// The empty DocumentURI is valid.
65
func (uri *DocumentURI) UnmarshalText(data []byte) (err error) {
66
	*uri, err = ParseDocumentURI(string(data))
67
	return
68
}
69

70
// Path returns the file path for the given URI.
71
//
72
// DocumentURI("").Path() returns the empty string.
73
//
74
// Path panics if called on a URI that is not a valid filename.
75
func (uri DocumentURI) Path() string {
76
	filename, err := filename(uri)
77
	if err != nil {
78
		// e.g. ParseRequestURI failed.
79
		//
80
		// This can only affect DocumentURIs created by
81
		// direct string manipulation; all DocumentURIs
82
		// received from the client pass through
83
		// ParseRequestURI, which ensures validity.
84
		panic(err)
85
	}
86
	return filepath.FromSlash(filename)
87
}
88

89
// Dir returns the URI for the directory containing the receiver.
90
func (uri DocumentURI) Dir() DocumentURI {
91
	// This function could be more efficiently implemented by avoiding any call
92
	// to Path(), but at least consolidates URI manipulation.
93
	return URIFromPath(filepath.Dir(uri.Path()))
94
}
95

96
// Encloses reports whether uri's path, considered as a sequence of segments,
97
// is a prefix of file's path.
98
func (uri DocumentURI) Encloses(file DocumentURI) bool {
99
	return InDir(uri.Path(), file.Path())
100
}
101

102
func filename(uri DocumentURI) (string, error) {
103
	if uri == "" {
104
		return "", nil
105
	}
106

107
	// This conservative check for the common case
108
	// of a simple non-empty absolute POSIX filename
109
	// avoids the allocation of a net.URL.
110
	if strings.HasPrefix(string(uri), "file:///") {
111
		rest := string(uri)[len("file://"):] // leave one slash
112
		for i := 0; i < len(rest); i++ {
113
			b := rest[i]
114
			// Reject these cases:
115
			if b < ' ' || b == 0x7f || // control character
116
				b == '%' || b == '+' || // URI escape
117
				b == ':' || // Windows drive letter
118
				b == '@' || b == '&' || b == '?' { // authority or query
119
				goto slow
120
			}
121
		}
122
		return rest, nil
123
	}
124
slow:
125

126
	u, err := url.ParseRequestURI(string(uri))
127
	if err != nil {
128
		return "", err
129
	}
130
	if u.Scheme != fileScheme {
131
		return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri)
132
	}
133
	// If the URI is a Windows URI, we trim the leading "/" and uppercase
134
	// the drive letter, which will never be case sensitive.
135
	if isWindowsDriveURIPath(u.Path) {
136
		u.Path = strings.ToUpper(string(u.Path[1])) + u.Path[2:]
137
	}
138

139
	return u.Path, nil
140
}
141

142
// ParseDocumentURI interprets a string as a DocumentURI, applying VS
143
// Code workarounds; see [DocumentURI.UnmarshalText] for details.
144
func ParseDocumentURI(s string) (DocumentURI, error) {
145
	if s == "" {
146
		return "", nil
147
	}
148

149
	if !strings.HasPrefix(s, "file://") {
150
		return "", fmt.Errorf("DocumentURI scheme is not 'file': %s", s)
151
	}
152

153
	// VS Code sends URLs with only two slashes,
154
	// which are invalid. golang/go#39789.
155
	if !strings.HasPrefix(s, "file:///") {
156
		s = "file:///" + s[len("file://"):]
157
	}
158

159
	// Even though the input is a URI, it may not be in canonical form. VS Code
160
	// in particular over-escapes :, @, etc. Unescape and re-encode to canonicalize.
161
	path, err := url.PathUnescape(s[len("file://"):])
162
	if err != nil {
163
		return "", err
164
	}
165

166
	// File URIs from Windows may have lowercase drive letters.
167
	// Since drive letters are guaranteed to be case insensitive,
168
	// we change them to uppercase to remain consistent.
169
	// For example, file:///c:/x/y/z becomes file:///C:/x/y/z.
170
	if isWindowsDriveURIPath(path) {
171
		path = path[:1] + strings.ToUpper(string(path[1])) + path[2:]
172
	}
173
	u := url.URL{Scheme: fileScheme, Path: path}
174
	return DocumentURI(u.String()), nil
175
}
176

177
// URIFromPath returns DocumentURI for the supplied file path.
178
// Given "", it returns "".
179
func URIFromPath(path string) DocumentURI {
180
	if path == "" {
181
		return ""
182
	}
183
	if !isWindowsDrivePath(path) {
184
		if abs, err := filepath.Abs(path); err == nil {
185
			path = abs
186
		}
187
	}
188
	// Check the file path again, in case it became absolute.
189
	if isWindowsDrivePath(path) {
190
		path = "/" + strings.ToUpper(string(path[0])) + path[1:]
191
	}
192
	path = filepath.ToSlash(path)
193
	u := url.URL{
194
		Scheme: fileScheme,
195
		Path:   path,
196
	}
197
	return DocumentURI(u.String())
198
}
199

200
const fileScheme = "file"
201

202
// isWindowsDrivePath returns true if the file path is of the form used by
203
// Windows. We check if the path begins with a drive letter, followed by a ":".
204
// For example: C:/x/y/z.
205
func isWindowsDrivePath(path string) bool {
206
	if len(path) < 3 {
207
		return false
208
	}
209
	return unicode.IsLetter(rune(path[0])) && path[1] == ':'
210
}
211

212
// isWindowsDriveURIPath returns true if the file URI is of the format used by
213
// Windows URIs. The url.Parse package does not specially handle Windows paths
214
// (see golang/go#6027), so we check if the URI path has a drive prefix (e.g. "/C:").
215
func isWindowsDriveURIPath(uri string) bool {
216
	if len(uri) < 4 {
217
		return false
218
	}
219
	return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':'
220
}
221

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.