diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go index 9e9be16623..90b023cc45 100644 --- a/internal/lsp/cache/session.go +++ b/internal/lsp/cache/session.go @@ -49,6 +49,9 @@ type overlay struct { // sameContentOnDisk is true if a file has been saved on disk, // and therefore does not need to be part of the overlay sent to go/packages. sameContentOnDisk bool + + // unchanged is true if a file has not yet been edited. + unchanged bool } func (s *session) Shutdown(ctx context.Context) { @@ -249,7 +252,7 @@ func (s *session) GetFile(uri span.URI) source.FileHandle { return s.Cache().GetFile(uri) } -func (s *session) SetOverlay(uri span.URI, data []byte) { +func (s *session) SetOverlay(uri span.URI, data []byte) bool { s.overlayMu.Lock() defer func() { s.overlayMu.Unlock() @@ -258,14 +261,20 @@ func (s *session) SetOverlay(uri span.URI, data []byte) { if data == nil { delete(s.overlays, uri) - return + return false } + + o := s.overlays[uri] + firstChange := o != nil && o.unchanged + s.overlays[uri] = &overlay{ - session: s, - uri: uri, - data: data, - hash: hashContents(data), + session: s, + uri: uri, + data: data, + hash: hashContents(data), + unchanged: o == nil, } + return firstChange } // openOverlay adds the file content to the overlay. @@ -277,10 +286,11 @@ func (s *session) openOverlay(ctx context.Context, uri span.URI, data []byte) { s.filesWatchMap.Notify(uri) }() s.overlays[uri] = &overlay{ - session: s, - uri: uri, - data: data, - hash: hashContents(data), + session: s, + uri: uri, + data: data, + hash: hashContents(data), + unchanged: true, } _, hash, err := s.cache.GetFile(uri).Read(ctx) if err != nil { diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index b42f2147bd..e4db451abe 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -328,7 +328,7 @@ func (v *view) buildBuiltinPkg(ctx context.Context) { } // SetContent sets the overlay contents for a file. -func (v *view) SetContent(ctx context.Context, uri span.URI, content []byte) error { +func (v *view) SetContent(ctx context.Context, uri span.URI, content []byte) (bool, error) { v.mu.Lock() defer v.mu.Unlock() @@ -338,10 +338,9 @@ func (v *view) SetContent(ctx context.Context, uri span.URI, content []byte) err v.backgroundCtx, v.cancel = context.WithCancel(v.baseCtx) if !v.Ignore(uri) { - v.session.SetOverlay(uri, content) + return v.session.SetOverlay(uri, content), nil } - - return nil + return false, nil } // invalidateContent invalidates the content of a Go file, diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index 9c67bad2ca..3a0fb8f62c 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -188,7 +188,7 @@ type Session interface { IsOpen(uri span.URI) bool // Called to set the effective contents of a file from this session. - SetOverlay(uri span.URI, data []byte) + SetOverlay(uri span.URI, data []byte) (wasFirstChange bool) } // View represents a single workspace. @@ -211,7 +211,7 @@ type View interface { GetFile(ctx context.Context, uri span.URI) (File, error) // Called to set the effective contents of a file from this view. - SetContent(ctx context.Context, uri span.URI, content []byte) error + SetContent(ctx context.Context, uri span.URI, content []byte) (wasFirstChange bool, err error) // BackgroundContext returns a context used for all background processing // on behalf of this view. diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go index 17d28692c1..d90b041c9a 100644 --- a/internal/lsp/text_synchronization.go +++ b/internal/lsp/text_synchronization.go @@ -31,14 +31,6 @@ func (s *Server) didOpen(ctx context.Context, params *protocol.DidOpenTextDocume view := s.session.ViewOf(uri) - // TODO: Ideally, we should be able to specify that a generated file should be opened as read-only. - if source.IsGenerated(ctx, view, uri) { - s.client.ShowMessage(ctx, &protocol.ShowMessageParams{ - Message: fmt.Sprintf("Do not edit this file! %s is a generated file.", uri.Filename()), - Type: protocol.Warning, - }) - } - // Run diagnostics on the newly-changed file. go func() { ctx := view.BackgroundContext() @@ -76,9 +68,20 @@ func (s *Server) didChange(ctx context.Context, params *protocol.DidChangeTextDo } // Cache the new file content and send fresh diagnostics. view := s.session.ViewOf(uri) - if err := view.SetContent(ctx, uri, []byte(text)); err != nil { + wasFirstChange, err := view.SetContent(ctx, uri, []byte(text)) + if err != nil { return err } + + // TODO: Ideally, we should be able to specify that a generated file should be opened as read-only. + // Tell the user that they should not be editing a generated file. + if source.IsGenerated(ctx, view, uri) && wasFirstChange { + s.client.ShowMessage(ctx, &protocol.ShowMessageParams{ + Message: fmt.Sprintf("Do not edit this file! %s is a generated file.", uri.Filename()), + Type: protocol.Warning, + }) + } + // Run diagnostics on the newly-changed file. go func() { ctx := view.BackgroundContext() @@ -140,7 +143,7 @@ func (s *Server) didClose(ctx context.Context, params *protocol.DidCloseTextDocu ctx = telemetry.File.With(ctx, uri) s.session.DidClose(uri) view := s.session.ViewOf(uri) - if err := view.SetContent(ctx, uri, nil); err != nil { + if _, err := view.SetContent(ctx, uri, nil); err != nil { return err } clear := []span.URI{uri} // by default, clear the closed URI