Skip to content

Commit 47f76b4

Browse files
committed
lsp watch by andrew squashed
1 parent 4252f06 commit 47f76b4

16 files changed

+1015
-83
lines changed

internal/api/api.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ func (api *API) PositionEncoding() lsproto.PositionEncodingKind {
119119
return lsproto.PositionEncodingKindUTF8
120120
}
121121

122+
// Client implements ProjectHost.
123+
func (api *API) Client() project.Client {
124+
return nil
125+
}
126+
127+
// IsWatchEnabled implements ProjectHost.
128+
func (api *API) IsWatchEnabled() bool {
129+
return false
130+
}
131+
122132
func (api *API) HandleRequest(id int, method string, payload []byte) ([]byte, error) {
123133
params, err := unmarshalPayload(method, payload)
124134
if err != nil {
@@ -351,7 +361,7 @@ func (api *API) getOrCreateScriptInfo(fileName string, path tspath.Path, scriptK
351361
if !ok {
352362
return nil
353363
}
354-
info = project.NewScriptInfo(fileName, path, scriptKind)
364+
info = project.NewScriptInfo(fileName, path, scriptKind, api.host.FS())
355365
info.SetTextFromDisk(content)
356366
api.scriptInfosMu.Lock()
357367
defer api.scriptInfosMu.Unlock()

internal/lsp/lsproto/jsonrpc.go

+20-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ type ID struct {
2828
int int32
2929
}
3030

31+
func NewIDString(str string) *ID {
32+
return &ID{str: str}
33+
}
34+
3135
func (id *ID) MarshalJSON() ([]byte, error) {
3236
if id.str != "" {
3337
return json.Marshal(id.str)
@@ -43,6 +47,13 @@ func (id *ID) UnmarshalJSON(data []byte) error {
4347
return json.Unmarshal(data, &id.int)
4448
}
4549

50+
func (id *ID) TryInt() (int32, bool) {
51+
if id == nil || id.str != "" {
52+
return 0, false
53+
}
54+
return id.int, true
55+
}
56+
4657
func (id *ID) MustInt() int32 {
4758
if id.str != "" {
4859
panic("ID is not an integer")
@@ -54,11 +65,19 @@ func (id *ID) MustInt() int32 {
5465

5566
type RequestMessage struct {
5667
JSONRPC JSONRPCVersion `json:"jsonrpc"`
57-
ID *ID `json:"id"`
68+
ID *ID `json:"id,omitempty"`
5869
Method Method `json:"method"`
5970
Params any `json:"params"`
6071
}
6172

73+
func NewRequestMessage(method Method, id *ID, params any) *RequestMessage {
74+
return &RequestMessage{
75+
ID: id,
76+
Method: method,
77+
Params: params,
78+
}
79+
}
80+
6281
func (r *RequestMessage) UnmarshalJSON(data []byte) error {
6382
var raw struct {
6483
JSONRPC JSONRPCVersion `json:"jsonrpc"`

internal/lsp/server.go

+123-6
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,18 @@ func NewServer(opts *ServerOptions) *Server {
4343
}
4444
}
4545

46-
var _ project.ServiceHost = (*Server)(nil)
46+
var (
47+
_ project.ServiceHost = (*Server)(nil)
48+
_ project.Client = (*Server)(nil)
49+
)
4750

4851
type Server struct {
4952
r *lsproto.BaseReader
5053
w *lsproto.BaseWriter
5154

5255
stderr io.Writer
5356

57+
clientSeq int32
5458
requestMethod string
5559
requestTime time.Time
5660

@@ -62,36 +66,100 @@ type Server struct {
6266
initializeParams *lsproto.InitializeParams
6367
positionEncoding lsproto.PositionEncodingKind
6468

69+
watchEnabled bool
70+
watcherID int
71+
watchers core.Set[project.WatcherHandle]
6572
logger *project.Logger
6673
projectService *project.Service
6774
converters *ls.Converters
6875
}
6976

70-
// FS implements project.ProjectServiceHost.
77+
// FS implements project.ServiceHost.
7178
func (s *Server) FS() vfs.FS {
7279
return s.fs
7380
}
7481

75-
// DefaultLibraryPath implements project.ProjectServiceHost.
82+
// DefaultLibraryPath implements project.ServiceHost.
7683
func (s *Server) DefaultLibraryPath() string {
7784
return s.defaultLibraryPath
7885
}
7986

80-
// GetCurrentDirectory implements project.ProjectServiceHost.
87+
// GetCurrentDirectory implements project.ServiceHost.
8188
func (s *Server) GetCurrentDirectory() string {
8289
return s.cwd
8390
}
8491

85-
// NewLine implements project.ProjectServiceHost.
92+
// NewLine implements project.ServiceHost.
8693
func (s *Server) NewLine() string {
8794
return s.newLine.GetNewLineCharacter()
8895
}
8996

90-
// Trace implements project.ProjectServiceHost.
97+
// Trace implements project.ServiceHost.
9198
func (s *Server) Trace(msg string) {
9299
s.Log(msg)
93100
}
94101

102+
// Client implements project.ServiceHost.
103+
func (s *Server) Client() project.Client {
104+
if !s.watchEnabled {
105+
return nil
106+
}
107+
return s
108+
}
109+
110+
// WatchFiles implements project.Client.
111+
func (s *Server) WatchFiles(watchers []*lsproto.FileSystemWatcher) (project.WatcherHandle, error) {
112+
watcherId := fmt.Sprintf("watcher-%d", s.watcherID)
113+
if err := s.sendRequest(lsproto.MethodClientRegisterCapability, &lsproto.RegistrationParams{
114+
Registrations: []*lsproto.Registration{
115+
{
116+
Id: watcherId,
117+
Method: string(lsproto.MethodWorkspaceDidChangeWatchedFiles),
118+
RegisterOptions: ptrTo(any(lsproto.DidChangeWatchedFilesRegistrationOptions{
119+
Watchers: watchers,
120+
})),
121+
},
122+
},
123+
}); err != nil {
124+
return "", fmt.Errorf("failed to register file watcher: %w", err)
125+
}
126+
127+
handle := project.WatcherHandle(watcherId)
128+
s.watchers.Add(handle)
129+
s.watcherID++
130+
return handle, nil
131+
}
132+
133+
// UnwatchFiles implements project.Client.
134+
func (s *Server) UnwatchFiles(handle project.WatcherHandle) error {
135+
if s.watchers.Has(handle) {
136+
if err := s.sendRequest(lsproto.MethodClientUnregisterCapability, &lsproto.UnregistrationParams{
137+
Unregisterations: []*lsproto.Unregistration{
138+
{
139+
Id: string(handle),
140+
Method: string(lsproto.MethodWorkspaceDidChangeWatchedFiles),
141+
},
142+
},
143+
}); err != nil {
144+
return fmt.Errorf("failed to unregister file watcher: %w", err)
145+
}
146+
s.watchers.Delete(handle)
147+
return nil
148+
}
149+
150+
return fmt.Errorf("no file watcher exists with ID %s", handle)
151+
}
152+
153+
// RefreshDiagnostics implements project.Client.
154+
func (s *Server) RefreshDiagnostics() error {
155+
if ptrIsTrue(s.initializeParams.Capabilities.Workspace.Diagnostics.RefreshSupport) {
156+
if err := s.sendRequest(lsproto.MethodWorkspaceDiagnosticRefresh, nil); err != nil {
157+
return fmt.Errorf("failed to refresh diagnostics: %w", err)
158+
}
159+
}
160+
return nil
161+
}
162+
95163
func (s *Server) Run() error {
96164
for {
97165
req, err := s.read()
@@ -105,6 +173,11 @@ func (s *Server) Run() error {
105173
return err
106174
}
107175

176+
// TODO: handle response messages
177+
if req == nil {
178+
continue
179+
}
180+
108181
if s.initializeParams == nil {
109182
if req.Method == lsproto.MethodInitialize {
110183
if err := s.handleInitialize(req); err != nil {
@@ -132,12 +205,37 @@ func (s *Server) read() (*lsproto.RequestMessage, error) {
132205

133206
req := &lsproto.RequestMessage{}
134207
if err := json.Unmarshal(data, req); err != nil {
208+
res := &lsproto.ResponseMessage{}
209+
if err = json.Unmarshal(data, res); err == nil {
210+
// !!! TODO: handle response
211+
return nil, nil
212+
}
135213
return nil, fmt.Errorf("%w: %w", lsproto.ErrInvalidRequest, err)
136214
}
137215

138216
return req, nil
139217
}
140218

219+
func (s *Server) sendRequest(method lsproto.Method, params any) error {
220+
s.clientSeq++
221+
id := lsproto.NewIDString(fmt.Sprintf("ts%d", s.clientSeq))
222+
req := lsproto.NewRequestMessage(method, id, params)
223+
data, err := json.Marshal(req)
224+
if err != nil {
225+
return err
226+
}
227+
return s.w.Write(data)
228+
}
229+
230+
func (s *Server) sendNotification(method lsproto.Method, params any) error {
231+
req := lsproto.NewRequestMessage(method, nil /*id*/, params)
232+
data, err := json.Marshal(req)
233+
if err != nil {
234+
return err
235+
}
236+
return s.w.Write(data)
237+
}
238+
141239
func (s *Server) sendResult(id *lsproto.ID, result any) error {
142240
return s.sendResponse(&lsproto.ResponseMessage{
143241
ID: id,
@@ -189,6 +287,8 @@ func (s *Server) handleMessage(req *lsproto.RequestMessage) error {
189287
return s.handleDidSave(req)
190288
case *lsproto.DidCloseTextDocumentParams:
191289
return s.handleDidClose(req)
290+
case *lsproto.DidChangeWatchedFilesParams:
291+
return s.handleDidChangeWatchedFiles(req)
192292
case *lsproto.DocumentDiagnosticParams:
193293
return s.handleDocumentDiagnostic(req)
194294
case *lsproto.HoverParams:
@@ -262,9 +362,14 @@ func (s *Server) handleInitialize(req *lsproto.RequestMessage) error {
262362
}
263363

264364
func (s *Server) handleInitialized(req *lsproto.RequestMessage) error {
365+
if s.initializeParams.Capabilities.Workspace.DidChangeWatchedFiles != nil && *s.initializeParams.Capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration {
366+
s.watchEnabled = true
367+
}
368+
265369
s.logger = project.NewLogger([]io.Writer{s.stderr}, "" /*file*/, project.LogLevelVerbose)
266370
s.projectService = project.NewService(s, project.ServiceOptions{
267371
Logger: s.logger,
372+
WatchEnabled: s.watchEnabled,
268373
PositionEncoding: s.positionEncoding,
269374
})
270375

@@ -322,6 +427,11 @@ func (s *Server) handleDidClose(req *lsproto.RequestMessage) error {
322427
return nil
323428
}
324429

430+
func (s *Server) handleDidChangeWatchedFiles(req *lsproto.RequestMessage) error {
431+
params := req.Params.(*lsproto.DidChangeWatchedFilesParams)
432+
return s.projectService.OnWatchedFilesChanged(params.Changes)
433+
}
434+
325435
func (s *Server) handleDocumentDiagnostic(req *lsproto.RequestMessage) error {
326436
params := req.Params.(*lsproto.DocumentDiagnosticParams)
327437
file, project := s.getFileAndProject(params.TextDocument.Uri)
@@ -445,3 +555,10 @@ func codeFence(lang string, code string) string {
445555
func ptrTo[T any](v T) *T {
446556
return &v
447557
}
558+
559+
func ptrIsTrue(v *bool) bool {
560+
if v == nil {
561+
return false
562+
}
563+
return *v
564+
}

internal/project/documentregistry.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ func (r *DocumentRegistry) getDocumentWorker(
9292
if entry, ok := r.documents.Load(key); ok {
9393
// We have an entry for this file. However, it may be for a different version of
9494
// the script snapshot. If so, update it appropriately.
95-
if entry.sourceFile.Version != scriptInfo.version {
95+
if entry.sourceFile.Version != scriptInfo.Version() {
9696
sourceFile := parser.ParseSourceFile(scriptInfo.fileName, scriptInfo.path, scriptInfo.text, scriptTarget, scanner.JSDocParsingModeParseAll)
97-
sourceFile.Version = scriptInfo.version
97+
sourceFile.Version = scriptInfo.Version()
9898
entry.mu.Lock()
9999
defer entry.mu.Unlock()
100100
entry.sourceFile = sourceFile
@@ -104,7 +104,7 @@ func (r *DocumentRegistry) getDocumentWorker(
104104
} else {
105105
// Have never seen this file with these settings. Create a new source file for it.
106106
sourceFile := parser.ParseSourceFile(scriptInfo.fileName, scriptInfo.path, scriptInfo.text, scriptTarget, scanner.JSDocParsingModeParseAll)
107-
sourceFile.Version = scriptInfo.version
107+
sourceFile.Version = scriptInfo.Version()
108108
entry, _ := r.documents.LoadOrStore(key, &registryEntry{
109109
sourceFile: sourceFile,
110110
refCount: 0,

internal/project/host.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
package project
22

3-
import "github.com/microsoft/typescript-go/internal/vfs"
3+
import (
4+
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
5+
"github.com/microsoft/typescript-go/internal/vfs"
6+
)
7+
8+
type WatcherHandle string
9+
10+
type Client interface {
11+
WatchFiles(watchers []*lsproto.FileSystemWatcher) (WatcherHandle, error)
12+
UnwatchFiles(handle WatcherHandle) error
13+
RefreshDiagnostics() error
14+
}
415

516
type ServiceHost interface {
617
FS() vfs.FS
718
DefaultLibraryPath() string
819
GetCurrentDirectory() string
920
NewLine() string
21+
22+
Client() Client
1023
}

0 commit comments

Comments
 (0)