diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go index b5eaa15e..cdb1d490 100644 --- a/internal/lsp/cache/cache.go +++ b/internal/lsp/cache/cache.go @@ -28,9 +28,10 @@ type cache struct { func (c *cache) NewSession(log xlog.Logger) source.Session { return &session{ - cache: c, - log: log, - overlays: make(map[span.URI]*source.FileContent), + cache: c, + log: log, + overlays: make(map[span.URI]*source.FileContent), + filesWatchMap: NewWatchMap(), } } diff --git a/internal/lsp/cache/file.go b/internal/lsp/cache/file.go index 70c588bc..d0683ebe 100644 --- a/internal/lsp/cache/file.go +++ b/internal/lsp/cache/file.go @@ -18,7 +18,6 @@ import ( // viewFile extends source.File with helper methods for the view package. type viewFile interface { source.File - invalidate() filename() string addURI(uri span.URI) int } diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go index 53dee8a5..21091535 100644 --- a/internal/lsp/cache/session.go +++ b/internal/lsp/cache/session.go @@ -27,7 +27,8 @@ type session struct { overlayMu sync.Mutex overlays map[span.URI]*source.FileContent - openFiles sync.Map + openFiles sync.Map + filesWatchMap *WatchMap } func (s *session) Shutdown(ctx context.Context) { @@ -184,8 +185,10 @@ func (s *session) ReadFile(uri span.URI) *source.FileContent { func (s *session) SetOverlay(uri span.URI, data []byte) { s.overlayMu.Lock() - defer s.overlayMu.Unlock() - //TODO: we also need to invoke and watchers in here + defer func() { + s.overlayMu.Unlock() + s.filesWatchMap.Notify(uri) + }() if data == nil { delete(s.overlays, uri) return diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index 32204e84..7908f0b6 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -207,7 +207,7 @@ func (v *view) SetContent(ctx context.Context, uri span.URI, content []byte) err v.backgroundCtx, v.cancel = context.WithCancel(v.baseCtx) v.contentChanges[uri] = func() { - v.applyContentChange(uri, content) + v.session.SetOverlay(uri, content) } return nil @@ -232,17 +232,6 @@ func (v *view) applyContentChanges(ctx context.Context) error { return nil } -// applyContentChange applies a content update for a given file. It assumes that the -// caller is holding the view's mutex. -func (v *view) applyContentChange(uri span.URI, content []byte) { - v.session.SetOverlay(uri, content) - f, err := v.getFile(uri) - if err != nil { - return - } - f.invalidate() -} - func (f *goFile) invalidate() { // TODO(rstambler): Should we recompute these here? f.ast = nil @@ -331,7 +320,9 @@ func (v *view) getFile(uri span.URI) (viewFile, error) { default: return nil, fmt.Errorf("unsupported file extension: %s", ext) } - + v.session.filesWatchMap.Watch(uri, func() { + f.invalidate() + }) v.mapFile(uri, f) return f, nil } diff --git a/internal/lsp/cache/watcher.go b/internal/lsp/cache/watcher.go new file mode 100644 index 00000000..c8f619b1 --- /dev/null +++ b/internal/lsp/cache/watcher.go @@ -0,0 +1,56 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cache + +import ( + "sync" +) + +type watcher struct { + id uint64 + callback func() +} + +type WatchMap struct { + mu sync.Mutex + nextID uint64 + watchers map[interface{}][]watcher +} + +func NewWatchMap() *WatchMap { + return &WatchMap{watchers: make(map[interface{}][]watcher)} +} +func (w *WatchMap) Watch(key interface{}, callback func()) func() { + w.mu.Lock() + defer w.mu.Unlock() + id := w.nextID + w.nextID++ + w.watchers[key] = append(w.watchers[key], watcher{ + id: id, + callback: callback, + }) + return func() { + // unwatch if invoked + w.mu.Lock() + defer w.mu.Unlock() + // find and delete the watcher entry + entries := w.watchers[key] + for i, entry := range entries { + if entry.id == id { + // found it + entries[i] = entries[len(entries)-1] + entries = entries[:len(entries)-1] + } + } + } +} + +func (w *WatchMap) Notify(key interface{}) { + w.mu.Lock() + defer w.mu.Unlock() + for _, entry := range w.watchers[key] { + entry.callback() + } +}