| // Copyright 2022 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 ( |
| "path/filepath" |
| |
| "golang.org/x/tools/gopls/internal/file" |
| "golang.org/x/tools/gopls/internal/protocol" |
| "golang.org/x/tools/gopls/internal/util/persistent" |
| ) |
| |
| // A fileMap maps files in the snapshot, with some additional bookkeeping: |
| // It keeps track of overlays as well as directories containing any observed |
| // file. |
| type fileMap struct { |
| files *persistent.Map[protocol.DocumentURI, file.Handle] |
| overlays *persistent.Map[protocol.DocumentURI, *overlay] // the subset of files that are overlays |
| dirs *persistent.Set[string] // all dirs containing files; if nil, dirs have not been initialized |
| } |
| |
| func newFileMap() *fileMap { |
| return &fileMap{ |
| files: new(persistent.Map[protocol.DocumentURI, file.Handle]), |
| overlays: new(persistent.Map[protocol.DocumentURI, *overlay]), |
| dirs: new(persistent.Set[string]), |
| } |
| } |
| |
| // clone creates a copy of the fileMap, incorporating the changes specified by |
| // the changes map. |
| func (m *fileMap) clone(changes map[protocol.DocumentURI]file.Handle) *fileMap { |
| m2 := &fileMap{ |
| files: m.files.Clone(), |
| overlays: m.overlays.Clone(), |
| } |
| if m.dirs != nil { |
| m2.dirs = m.dirs.Clone() |
| } |
| |
| // Handle file changes. |
| // |
| // Note, we can't simply delete the file unconditionally and let it be |
| // re-read by the snapshot, as (1) the snapshot must always observe all |
| // overlays, and (2) deleting a file forces directories to be reevaluated, as |
| // it may be the last file in a directory. We want to avoid that work in the |
| // common case where a file has simply changed. |
| // |
| // For that reason, we also do this in two passes, processing deletions |
| // first, as a set before a deletion would result in pointless work. |
| for uri, fh := range changes { |
| if !fileExists(fh) { |
| m2.delete(uri) |
| } |
| } |
| for uri, fh := range changes { |
| if fileExists(fh) { |
| m2.set(uri, fh) |
| } |
| } |
| return m2 |
| } |
| |
| func (m *fileMap) destroy() { |
| m.files.Destroy() |
| m.overlays.Destroy() |
| if m.dirs != nil { |
| m.dirs.Destroy() |
| } |
| } |
| |
| // get returns the file handle mapped by the given key, or (nil, false) if the |
| // key is not present. |
| func (m *fileMap) get(key protocol.DocumentURI) (file.Handle, bool) { |
| return m.files.Get(key) |
| } |
| |
| // foreach calls f for each (uri, fh) in the map. |
| func (m *fileMap) foreach(f func(uri protocol.DocumentURI, fh file.Handle)) { |
| m.files.Range(f) |
| } |
| |
| // set stores the given file handle for key, updating overlays and directories |
| // accordingly. |
| func (m *fileMap) set(key protocol.DocumentURI, fh file.Handle) { |
| m.files.Set(key, fh, nil) |
| |
| // update overlays |
| if o, ok := fh.(*overlay); ok { |
| m.overlays.Set(key, o, nil) |
| } else { |
| // Setting a non-overlay must delete the corresponding overlay, to preserve |
| // the accuracy of the overlay set. |
| m.overlays.Delete(key) |
| } |
| |
| // update dirs, if they have been computed |
| if m.dirs != nil { |
| m.addDirs(key) |
| } |
| } |
| |
| // addDirs adds all directories containing u to the dirs set. |
| func (m *fileMap) addDirs(u protocol.DocumentURI) { |
| dir := filepath.Dir(u.Path()) |
| for dir != "" && !m.dirs.Contains(dir) { |
| m.dirs.Add(dir) |
| dir = filepath.Dir(dir) |
| } |
| } |
| |
| // delete removes a file from the map, and updates overlays and dirs |
| // accordingly. |
| func (m *fileMap) delete(key protocol.DocumentURI) { |
| m.files.Delete(key) |
| m.overlays.Delete(key) |
| |
| // Deleting a file may cause the set of dirs to shrink; therefore we must |
| // re-evaluate the dir set. |
| // |
| // Do this lazily, to avoid work if there are multiple deletions in a row. |
| if m.dirs != nil { |
| m.dirs.Destroy() |
| m.dirs = nil |
| } |
| } |
| |
| // getOverlays returns a new unordered array of overlay files. |
| func (m *fileMap) getOverlays() []*overlay { |
| var overlays []*overlay |
| m.overlays.Range(func(_ protocol.DocumentURI, o *overlay) { |
| overlays = append(overlays, o) |
| }) |
| return overlays |
| } |
| |
| // getDirs reports returns the set of dirs observed by the fileMap. |
| // |
| // This operation mutates the fileMap. |
| // The result must not be mutated by the caller. |
| func (m *fileMap) getDirs() *persistent.Set[string] { |
| if m.dirs == nil { |
| m.dirs = new(persistent.Set[string]) |
| m.files.Range(func(u protocol.DocumentURI, _ file.Handle) { |
| m.addDirs(u) |
| }) |
| } |
| return m.dirs |
| } |