@@ -121,7 +121,7 @@ func NewEditor(sandbox *Sandbox, config EditorConfig) *Editor {
121
121
//
122
122
// It returns the editor, so that it may be called as follows:
123
123
//
124
- // editor, err := NewEditor(s).Connect(ctx, conn)
124
+ // editor, err := NewEditor(s).Connect(ctx, conn, hooks )
125
125
func (e * Editor ) Connect (ctx context.Context , connector servertest.Connector , hooks ClientHooks ) (* Editor , error ) {
126
126
bgCtx , cancelConn := context .WithCancel (xcontext .Detach (ctx ))
127
127
conn := connector .Connect (bgCtx )
@@ -135,7 +135,7 @@ func (e *Editor) Connect(ctx context.Context, connector servertest.Connector, ho
135
135
protocol .ClientHandler (e .client ,
136
136
jsonrpc2 .MethodNotFound )))
137
137
138
- if err := e .initialize (ctx , e . config . WorkspaceFolders ); err != nil {
138
+ if err := e .initialize (ctx ); err != nil {
139
139
return nil , err
140
140
}
141
141
e .sandbox .Workdir .AddWatcher (e .onFileChanges )
@@ -197,11 +197,10 @@ func (e *Editor) Client() *Client {
197
197
return e .client
198
198
}
199
199
200
- // settings builds the settings map for use in LSP settings
201
- // RPCs.
202
- func (e * Editor ) settings () map [string ]interface {} {
203
- e .mu .Lock ()
204
- defer e .mu .Unlock ()
200
+ // settingsLocked builds the settings map for use in LSP settings RPCs.
201
+ //
202
+ // e.mu must be held while calling this function.
203
+ func (e * Editor ) settingsLocked () map [string ]interface {} {
205
204
env := make (map [string ]string )
206
205
for k , v := range e .defaultEnv {
207
206
env [k ] = v
@@ -240,26 +239,19 @@ func (e *Editor) settings() map[string]interface{} {
240
239
return settings
241
240
}
242
241
243
- func (e * Editor ) initialize (ctx context.Context , workspaceFolders [] string ) error {
242
+ func (e * Editor ) initialize (ctx context.Context ) error {
244
243
params := & protocol.ParamInitialize {}
245
244
params .ClientInfo .Name = "fakeclient"
246
245
params .ClientInfo .Version = "v1.0.0"
247
-
248
- if workspaceFolders == nil {
249
- workspaceFolders = []string {string (e .sandbox .Workdir .RelativeTo )}
250
- }
251
- for _ , folder := range workspaceFolders {
252
- params .WorkspaceFolders = append (params .WorkspaceFolders , protocol.WorkspaceFolder {
253
- URI : string (e .sandbox .Workdir .URI (folder )),
254
- Name : filepath .Base (folder ),
255
- })
256
- }
257
-
246
+ e .mu .Lock ()
247
+ params .WorkspaceFolders = e .makeWorkspaceFoldersLocked ()
248
+ params .InitializationOptions = e .settingsLocked ()
249
+ e .mu .Unlock ()
258
250
params .Capabilities .Workspace .Configuration = true
259
251
params .Capabilities .Window .WorkDoneProgress = true
252
+
260
253
// TODO: set client capabilities
261
254
params .Capabilities .TextDocument .Completion .CompletionItem .TagSupport .ValueSet = []protocol.CompletionItemTag {protocol .ComplDeprecated }
262
- params .InitializationOptions = e .settings ()
263
255
264
256
params .Capabilities .TextDocument .Completion .CompletionItem .SnippetSupport = true
265
257
params .Capabilities .TextDocument .SemanticTokens .Requests .Full = true
@@ -296,6 +288,27 @@ func (e *Editor) initialize(ctx context.Context, workspaceFolders []string) erro
296
288
return nil
297
289
}
298
290
291
+ // makeWorkspaceFoldersLocked creates a slice of workspace folders to use for
292
+ // this editing session, based on the editor configuration.
293
+ //
294
+ // e.mu must be held while calling this function.
295
+ func (e * Editor ) makeWorkspaceFoldersLocked () (folders []protocol.WorkspaceFolder ) {
296
+ paths := e .config .WorkspaceFolders
297
+ if len (paths ) == 0 {
298
+ paths = append (paths , string (e .sandbox .Workdir .RelativeTo ))
299
+ }
300
+
301
+ for _ , path := range paths {
302
+ uri := string (e .sandbox .Workdir .URI (path ))
303
+ folders = append (folders , protocol.WorkspaceFolder {
304
+ URI : uri ,
305
+ Name : filepath .Base (uri ),
306
+ })
307
+ }
308
+
309
+ return folders
310
+ }
311
+
299
312
// onFileChanges is registered to be called by the Workdir on any writes that
300
313
// go through the Workdir API. It is called synchronously by the Workdir.
301
314
func (e * Editor ) onFileChanges (ctx context.Context , evts []FileEvent ) {
@@ -1195,6 +1208,54 @@ func (e *Editor) ChangeConfiguration(ctx context.Context, newConfig EditorConfig
1195
1208
return nil
1196
1209
}
1197
1210
1211
+ // ChangeWorkspaceFolders sets the new workspace folders, and sends a
1212
+ // didChangeWorkspaceFolders notification to the server.
1213
+ //
1214
+ // The given folders must all be unique.
1215
+ func (e * Editor ) ChangeWorkspaceFolders (ctx context.Context , folders []string ) error {
1216
+ // capture existing folders so that we can compute the change.
1217
+ e .mu .Lock ()
1218
+ oldFolders := e .makeWorkspaceFoldersLocked ()
1219
+ e .config .WorkspaceFolders = folders
1220
+ newFolders := e .makeWorkspaceFoldersLocked ()
1221
+ e .mu .Unlock ()
1222
+
1223
+ if e .Server == nil {
1224
+ return nil
1225
+ }
1226
+
1227
+ var params protocol.DidChangeWorkspaceFoldersParams
1228
+
1229
+ // Keep track of old workspace folders that must be removed.
1230
+ toRemove := make (map [protocol.URI ]protocol.WorkspaceFolder )
1231
+ for _ , folder := range oldFolders {
1232
+ toRemove [folder .URI ] = folder
1233
+ }
1234
+
1235
+ // Sanity check: if we see a folder twice the algorithm below doesn't work,
1236
+ // so track seen folders to ensure that we panic in that case.
1237
+ seen := make (map [protocol.URI ]protocol.WorkspaceFolder )
1238
+ for _ , folder := range newFolders {
1239
+ if _ , ok := seen [folder .URI ]; ok {
1240
+ panic (fmt .Sprintf ("folder %s seen twice" , folder .URI ))
1241
+ }
1242
+
1243
+ // If this folder already exists, we don't want to remove it.
1244
+ // Otherwise, we need to add it.
1245
+ if _ , ok := toRemove [folder .URI ]; ok {
1246
+ delete (toRemove , folder .URI )
1247
+ } else {
1248
+ params .Event .Added = append (params .Event .Added , folder )
1249
+ }
1250
+ }
1251
+
1252
+ for _ , v := range toRemove {
1253
+ params .Event .Removed = append (params .Event .Removed , v )
1254
+ }
1255
+
1256
+ return e .Server .DidChangeWorkspaceFolders (ctx , & params )
1257
+ }
1258
+
1198
1259
// CodeAction executes a codeAction request on the server.
1199
1260
func (e * Editor ) CodeAction (ctx context.Context , path string , rng * protocol.Range , diagnostics []protocol.Diagnostic ) ([]protocol.CodeAction , error ) {
1200
1261
if e .Server == nil {
0 commit comments