@@ -235,6 +235,13 @@ func (e *Editor) CloseBuffer(ctx context.Context, path string) error {
235
235
// SaveBuffer writes the content of the buffer specified by the given path to
236
236
// the filesystem.
237
237
func (e * Editor ) SaveBuffer (ctx context.Context , path string ) error {
238
+ if err := e .OrganizeImports (ctx , path ); err != nil {
239
+ return fmt .Errorf ("organizing imports before save: %v" , err )
240
+ }
241
+ if err := e .FormatBuffer (ctx , path ); err != nil {
242
+ return fmt .Errorf ("formatting before save: %v" , err )
243
+ }
244
+
238
245
e .mu .Lock ()
239
246
buf , ok := e .buffers [path ]
240
247
if ! ok {
@@ -282,41 +289,46 @@ func (e *Editor) SaveBuffer(ctx context.Context, path string) error {
282
289
283
290
// EditBuffer applies the given test edits to the buffer identified by path.
284
291
func (e * Editor ) EditBuffer (ctx context.Context , path string , edits []Edit ) error {
285
- params , err := e .doEdits (ctx , path , edits )
286
- if err != nil {
287
- return err
288
- }
289
- if e .server != nil {
290
- if err := e .server .DidChange (ctx , params ); err != nil {
291
- return fmt .Errorf ("DidChange: %v" , err )
292
- }
293
- }
294
- return nil
292
+ e .mu .Lock ()
293
+ defer e .mu .Unlock ()
294
+ return e .editBufferLocked (ctx , path , edits )
295
295
}
296
296
297
- func (e * Editor ) doEdits (ctx context.Context , path string , edits []Edit ) (* protocol.DidChangeTextDocumentParams , error ) {
297
+ // BufferText returns the content of the buffer with the given name.
298
+ func (e * Editor ) BufferText (name string ) string {
298
299
e .mu .Lock ()
299
300
defer e .mu .Unlock ()
301
+ return e .buffers [name ].text ()
302
+ }
303
+
304
+ func (e * Editor ) editBufferLocked (ctx context.Context , path string , edits []Edit ) error {
300
305
buf , ok := e .buffers [path ]
301
306
if ! ok {
302
- return nil , fmt .Errorf ("unknown buffer %q" , path )
307
+ return fmt .Errorf ("unknown buffer %q" , path )
303
308
}
304
309
var (
305
310
content = make ([]string , len (buf .content ))
306
311
err error
307
312
evts []protocol.TextDocumentContentChangeEvent
308
313
)
309
314
copy (content , buf .content )
310
- for _ , edit := range edits {
311
- content , err = editContent (content , edit )
312
- if err != nil {
313
- return nil , err
314
- }
315
- evts = append (evts , edit .toProtocolChangeEvent ())
315
+ content , err = editContent (content , edits )
316
+ if err != nil {
317
+ return err
316
318
}
319
+
317
320
buf .content = content
318
321
buf .version ++
319
322
e .buffers [path ] = buf
323
+ // A simple heuristic: if there is only one edit, send it incrementally.
324
+ // Otherwise, send the entire content.
325
+ if len (edits ) == 1 {
326
+ evts = append (evts , edits [0 ].toProtocolChangeEvent ())
327
+ } else {
328
+ evts = append (evts , protocol.TextDocumentContentChangeEvent {
329
+ Text : buf .text (),
330
+ })
331
+ }
320
332
params := & protocol.DidChangeTextDocumentParams {
321
333
TextDocument : protocol.VersionedTextDocumentIdentifier {
322
334
Version : float64 (buf .version ),
@@ -326,7 +338,12 @@ func (e *Editor) doEdits(ctx context.Context, path string, edits []Edit) (*proto
326
338
},
327
339
ContentChanges : evts ,
328
340
}
329
- return params , nil
341
+ if e .server != nil {
342
+ if err := e .server .DidChange (ctx , params ); err != nil {
343
+ return fmt .Errorf ("DidChange: %v" , err )
344
+ }
345
+ }
346
+ return nil
330
347
}
331
348
332
349
// GoToDefinition jumps to the definition of the symbol at the given position
@@ -354,6 +371,65 @@ func (e *Editor) GoToDefinition(ctx context.Context, path string, pos Pos) (stri
354
371
return newPath , newPos , nil
355
372
}
356
373
374
+ // OrganizeImports requests and performs the source.organizeImports codeAction.
375
+ func (e * Editor ) OrganizeImports (ctx context.Context , path string ) error {
376
+ if e .server == nil {
377
+ return nil
378
+ }
379
+ params := & protocol.CodeActionParams {}
380
+ params .TextDocument .URI = e .ws .URI (path )
381
+
382
+ actions , err := e .server .CodeAction (ctx , params )
383
+ if err != nil {
384
+ return fmt .Errorf ("textDocument/codeAction: %v" , err )
385
+ }
386
+ e .mu .Lock ()
387
+ defer e .mu .Unlock ()
388
+ for _ , action := range actions {
389
+ if action .Kind == protocol .SourceOrganizeImports {
390
+ for _ , change := range action .Edit .DocumentChanges {
391
+ path := e .ws .URIToPath (change .TextDocument .URI )
392
+ if float64 (e .buffers [path ].version ) != change .TextDocument .Version {
393
+ // Skip edits for old versions.
394
+ continue
395
+ }
396
+ edits := convertEdits (change .Edits )
397
+ if err := e .editBufferLocked (ctx , path , edits ); err != nil {
398
+ return fmt .Errorf ("editing buffer %q: %v" , path , err )
399
+ }
400
+ }
401
+ }
402
+ }
403
+ return nil
404
+ }
405
+
406
+ func convertEdits (protocolEdits []protocol.TextEdit ) []Edit {
407
+ var edits []Edit
408
+ for _ , lspEdit := range protocolEdits {
409
+ edits = append (edits , fromProtocolTextEdit (lspEdit ))
410
+ }
411
+ return edits
412
+ }
413
+
414
+ // FormatBuffer gofmts a Go file.
415
+ func (e * Editor ) FormatBuffer (ctx context.Context , path string ) error {
416
+ if e .server == nil {
417
+ return nil
418
+ }
419
+ // Because textDocument/formatting has no versions, we must block while
420
+ // performing formatting.
421
+ e .mu .Lock ()
422
+ defer e .mu .Unlock ()
423
+ params := & protocol.DocumentFormattingParams {}
424
+ params .TextDocument .URI = e .ws .URI (path )
425
+ resp , err := e .server .Formatting (ctx , params )
426
+ if err != nil {
427
+ return fmt .Errorf ("textDocument/formatting: %v" , err )
428
+ }
429
+ edits := convertEdits (resp )
430
+ return e .editBufferLocked (ctx , path , edits )
431
+ }
432
+
357
433
func (e * Editor ) checkBufferPosition (path string , pos Pos ) error {
358
434
e .mu .Lock ()
359
435
defer e .mu .Unlock ()
@@ -366,6 +442,3 @@ func (e *Editor) checkBufferPosition(path string, pos Pos) error {
366
442
}
367
443
return nil
368
444
}
369
-
370
- // TODO: expose more client functionality, for example Hover, CodeAction,
371
- // Rename, Completion, etc. setting the content of an entire buffer, etc.
0 commit comments