forked from sourcegraph/go-langserver
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fs.go
132 lines (115 loc) · 3.6 KB
/
fs.go
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
package langserver
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
opentracing "github.com/opentracing/opentracing-go"
"github.com/sourcegraph/ctxvfs"
"github.com/sourcegraph/go-langserver/pkg/lsp"
"github.com/sourcegraph/jsonrpc2"
)
// IsFileSystemRequest returns if this is an LSP method whose sole
// purpose is modifying the contents of the overlay file system.
func IsFileSystemRequest(method string) bool {
return method == "textDocument/didOpen" ||
method == "textDocument/didChange" ||
method == "textDocument/didClose" ||
method == "textDocument/didSave"
}
func (h *HandlerShared) HandleFileSystemRequest(ctx context.Context, req *jsonrpc2.Request) error {
span := opentracing.SpanFromContext(ctx)
switch req.Method {
case "textDocument/didOpen":
var params lsp.DidOpenTextDocumentParams
if err := json.Unmarshal(*req.Params, ¶ms); err != nil {
return err
}
span.SetTag("uri", params.TextDocument.URI)
h.addOverlayFile(params.TextDocument.URI, []byte(params.TextDocument.Text))
return nil
case "textDocument/didChange":
var params lsp.DidChangeTextDocumentParams
if err := json.Unmarshal(*req.Params, ¶ms); err != nil {
return err
}
contents, found := h.readOverlayFile(params.TextDocument.URI)
if !found {
return fmt.Errorf("received textDocument/didChange for unknown file %q", params.TextDocument.URI)
}
for _, change := range params.ContentChanges {
switch {
case change.Range == nil && change.RangeLength == 0:
contents = []byte(change.Text) // new full content
default:
return fmt.Errorf("incremental updates in textDocument/didChange not supported for file %q", params.TextDocument.URI)
}
}
h.addOverlayFile(params.TextDocument.URI, contents)
return nil
case "textDocument/didClose":
var params lsp.DidCloseTextDocumentParams
if err := json.Unmarshal(*req.Params, ¶ms); err != nil {
return err
}
h.removeOverlayFile(params.TextDocument.URI)
return nil
case "textDocument/didSave":
// no-op
return nil
default:
panic("unexpected file system request method: " + req.Method)
}
}
func (h *HandlerShared) FilePath(uri string) string {
path := strings.TrimPrefix(uri, "file://")
if !strings.HasPrefix(path, "/") {
panic(fmt.Sprintf("bad uri %q (path %q MUST have leading slash; it can't be relative)", uri, path))
}
if strings.Contains(path, ":") {
panic(fmt.Sprintf("bad uri %q (path %q MUST NOT contain ':')", uri, path))
}
if strings.Contains(path, "@") {
panic(fmt.Sprintf("bad uri %q (path %q MUST NOT contain '@')", uri, path))
}
return path
}
func (h *HandlerShared) readFile(ctx context.Context, uri string) ([]byte, error) {
h.Mu.Lock()
fs := h.FS
path := h.FilePath(uri)
h.Mu.Unlock()
contents, err := ctxvfs.ReadFile(ctx, fs, path)
if os.IsNotExist(err) {
if _, ok := err.(*os.PathError); !ok {
err = &os.PathError{Op: "Open", Path: path, Err: err}
}
}
return contents, err
}
func uriToOverlayPath(uri string) string {
return strings.TrimPrefix(uri, "file:///")
}
func (h *HandlerShared) addOverlayFile(uri string, contents []byte) {
h.Mu.Lock()
defer h.Mu.Unlock()
h.overlayFSMu.Lock()
defer h.overlayFSMu.Unlock()
h.overlayFS[uriToOverlayPath(uri)] = contents
}
func (h *HandlerShared) removeOverlayFile(uri string) {
h.Mu.Lock()
defer h.Mu.Unlock()
h.overlayFSMu.Lock()
defer h.overlayFSMu.Unlock()
delete(h.overlayFS, uriToOverlayPath(uri))
}
func (h *HandlerShared) readOverlayFile(uri string) (contents []byte, found bool) {
h.Mu.Lock()
defer h.Mu.Unlock()
h.overlayFSMu.Lock()
defer h.overlayFSMu.Unlock()
contents, found = h.overlayFS[uriToOverlayPath(uri)]
return
}