@@ -16,10 +16,11 @@ import (
16
16
"strings"
17
17
"sync"
18
18
19
- "golang.org/x/tools/internal/jsonrpc2"
20
- "golang.org/x/tools/internal/jsonrpc2/servertest"
21
19
"golang.org/x/tools/gopls/internal/lsp/command"
22
20
"golang.org/x/tools/gopls/internal/lsp/protocol"
21
+ "golang.org/x/tools/gopls/internal/lsp/source"
22
+ "golang.org/x/tools/internal/jsonrpc2"
23
+ "golang.org/x/tools/internal/jsonrpc2/servertest"
23
24
"golang.org/x/tools/internal/span"
24
25
"golang.org/x/tools/internal/xcontext"
25
26
)
@@ -39,7 +40,7 @@ type Editor struct {
39
40
40
41
mu sync.Mutex // guards config, buffers, serverCapabilities
41
42
config EditorConfig // editor configuration
42
- buffers map [string ]buffer // open buffers
43
+ buffers map [string ]buffer // open buffers (relative path -> buffer content)
43
44
serverCapabilities protocol.ServerCapabilities // capabilities / options
44
45
45
46
// Call metrics for the purpose of expectations. This is done in an ad-hoc
@@ -50,16 +51,18 @@ type Editor struct {
50
51
calls CallCounts
51
52
}
52
53
54
+ // CallCounts tracks the number of protocol notifications of different types.
53
55
type CallCounts struct {
54
56
DidOpen , DidChange , DidSave , DidChangeWatchedFiles , DidClose uint64
55
57
}
56
58
59
+ // buffer holds information about an open buffer in the editor.
57
60
type buffer struct {
58
- windowsLineEndings bool
59
- version int
60
- path string
61
- lines []string
62
- dirty bool
61
+ windowsLineEndings bool // use windows line endings when merging lines
62
+ version int // monotonic version; incremented on edits
63
+ path string // relative path in the workspace
64
+ lines []string // line content
65
+ dirty bool // if true, content is unsaved (TODO(rfindley): rename this field)
63
66
}
64
67
65
68
func (b buffer ) text () string {
@@ -374,7 +377,6 @@ func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error {
374
377
375
378
func (e * Editor ) createBuffer (ctx context.Context , path string , dirty bool , content string ) error {
376
379
e .mu .Lock ()
377
- defer e .mu .Unlock ()
378
380
379
381
buf := buffer {
380
382
windowsLineEndings : e .config .WindowsLineEndings ,
@@ -385,13 +387,25 @@ func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, cont
385
387
}
386
388
e .buffers [path ] = buf
387
389
388
- item := protocol.TextDocumentItem {
390
+ item := e .textDocumentItem (buf )
391
+ e .mu .Unlock ()
392
+
393
+ return e .sendDidOpen (ctx , item )
394
+ }
395
+
396
+ // textDocumentItem builds a protocol.TextDocumentItem for the given buffer.
397
+ //
398
+ // Precondition: e.mu must be held.
399
+ func (e * Editor ) textDocumentItem (buf buffer ) protocol.TextDocumentItem {
400
+ return protocol.TextDocumentItem {
389
401
URI : e .sandbox .Workdir .URI (buf .path ),
390
402
LanguageID : languageID (buf .path , e .config .FileAssociations ),
391
403
Version : int32 (buf .version ),
392
404
Text : buf .text (),
393
405
}
406
+ }
394
407
408
+ func (e * Editor ) sendDidOpen (ctx context.Context , item protocol.TextDocumentItem ) error {
395
409
if e .Server != nil {
396
410
if err := e .Server .DidOpen (ctx , & protocol.DidOpenTextDocumentParams {
397
411
TextDocument : item ,
@@ -451,9 +465,13 @@ func (e *Editor) CloseBuffer(ctx context.Context, path string) error {
451
465
delete (e .buffers , path )
452
466
e .mu .Unlock ()
453
467
468
+ return e .sendDidClose (ctx , e .TextDocumentIdentifier (path ))
469
+ }
470
+
471
+ func (e * Editor ) sendDidClose (ctx context.Context , doc protocol.TextDocumentIdentifier ) error {
454
472
if e .Server != nil {
455
473
if err := e .Server .DidClose (ctx , & protocol.DidCloseTextDocumentParams {
456
- TextDocument : e . TextDocumentIdentifier ( path ) ,
474
+ TextDocument : doc ,
457
475
}); err != nil {
458
476
return fmt .Errorf ("DidClose: %w" , err )
459
477
}
@@ -1157,16 +1175,91 @@ func (e *Editor) Rename(ctx context.Context, path string, pos Pos, newName strin
1157
1175
return err
1158
1176
}
1159
1177
for _ , change := range wsEdits .DocumentChanges {
1160
- if change .TextDocumentEdit != nil {
1161
- if err := e .applyProtocolEdit (ctx , * change .TextDocumentEdit ); err != nil {
1162
- return err
1163
- }
1178
+ if err := e .applyDocumentChange (ctx , change ); err != nil {
1179
+ return err
1164
1180
}
1165
1181
}
1166
1182
return nil
1167
1183
}
1168
1184
1169
- func (e * Editor ) applyProtocolEdit (ctx context.Context , change protocol.TextDocumentEdit ) error {
1185
+ func (e * Editor ) RenameFile (ctx context.Context , oldPath , newPath string ) error {
1186
+ closed , opened , err := e .renameBuffers (ctx , oldPath , newPath )
1187
+ if err != nil {
1188
+ return err
1189
+ }
1190
+
1191
+ for _ , c := range closed {
1192
+ if err := e .sendDidClose (ctx , c ); err != nil {
1193
+ return err
1194
+ }
1195
+ }
1196
+ for _ , o := range opened {
1197
+ if err := e .sendDidOpen (ctx , o ); err != nil {
1198
+ return err
1199
+ }
1200
+ }
1201
+
1202
+ // Finally, perform the renaming on disk.
1203
+ return e .sandbox .Workdir .RenameFile (ctx , oldPath , newPath )
1204
+ }
1205
+
1206
+ // renameBuffers renames in-memory buffers affected by the renaming of
1207
+ // oldPath->newPath, returning the resulting text documents that must be closed
1208
+ // and opened over the LSP.
1209
+ func (e * Editor ) renameBuffers (ctx context.Context , oldPath , newPath string ) (closed []protocol.TextDocumentIdentifier , opened []protocol.TextDocumentItem , _ error ) {
1210
+ e .mu .Lock ()
1211
+ defer e .mu .Unlock ()
1212
+
1213
+ // In case either oldPath or newPath is absolute, convert to absolute paths
1214
+ // before checking for containment.
1215
+ oldAbs := e .sandbox .Workdir .AbsPath (oldPath )
1216
+ newAbs := e .sandbox .Workdir .AbsPath (newPath )
1217
+
1218
+ // Collect buffers that are affected by the given file or directory renaming.
1219
+ buffersToRename := make (map [string ]string ) // old path -> new path
1220
+
1221
+ for path := range e .buffers {
1222
+ abs := e .sandbox .Workdir .AbsPath (path )
1223
+ if oldAbs == abs || source .InDirLex (oldAbs , abs ) {
1224
+ rel , err := filepath .Rel (oldAbs , abs )
1225
+ if err != nil {
1226
+ return nil , nil , fmt .Errorf ("filepath.Rel(%q, %q): %v" , oldAbs , abs , err )
1227
+ }
1228
+ nabs := filepath .Join (newAbs , rel )
1229
+ newPath := e .sandbox .Workdir .RelPath (nabs )
1230
+ buffersToRename [path ] = newPath
1231
+ }
1232
+ }
1233
+
1234
+ // Update buffers, and build protocol changes.
1235
+ for old , new := range buffersToRename {
1236
+ buf := e .buffers [old ]
1237
+ delete (e .buffers , old )
1238
+ buf .version = 1
1239
+ buf .path = new
1240
+ e .buffers [new ] = buf
1241
+
1242
+ closed = append (closed , e .TextDocumentIdentifier (old ))
1243
+ opened = append (opened , e .textDocumentItem (buf ))
1244
+ }
1245
+
1246
+ return closed , opened , nil
1247
+ }
1248
+
1249
+ func (e * Editor ) applyDocumentChange (ctx context.Context , change protocol.DocumentChanges ) error {
1250
+ if change .RenameFile != nil {
1251
+ oldPath := e .sandbox .Workdir .URIToPath (change .RenameFile .OldURI )
1252
+ newPath := e .sandbox .Workdir .URIToPath (change .RenameFile .NewURI )
1253
+
1254
+ return e .RenameFile (ctx , oldPath , newPath )
1255
+ }
1256
+ if change .TextDocumentEdit != nil {
1257
+ return e .applyTextDocumentEdit (ctx , * change .TextDocumentEdit )
1258
+ }
1259
+ panic ("Internal error: one of RenameFile or TextDocumentEdit must be set" )
1260
+ }
1261
+
1262
+ func (e * Editor ) applyTextDocumentEdit (ctx context.Context , change protocol.TextDocumentEdit ) error {
1170
1263
path := e .sandbox .Workdir .URIToPath (change .TextDocument .URI )
1171
1264
if ver := int32 (e .BufferVersion (path )); ver != change .TextDocument .Version {
1172
1265
return fmt .Errorf ("buffer versions for %q do not match: have %d, editing %d" , path , ver , change .TextDocument .Version )
0 commit comments