package project import ( "sync" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/project/dirty" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/cachedvfs" "github.com/zeebo/xxh3" ) type FileSource interface { FS() vfs.FS GetFile(fileName string) FileHandle } var ( _ FileSource = (*snapshotFSBuilder)(nil) _ FileSource = (*snapshotFS)(nil) ) type snapshotFS struct { toPath func(fileName string) tspath.Path fs vfs.FS overlays map[tspath.Path]*overlay diskFiles map[tspath.Path]*diskFile readFiles collections.SyncMap[tspath.Path, memoizedDiskFile] } type memoizedDiskFile func() FileHandle func (s *snapshotFS) FS() vfs.FS { return s.fs } func (s *snapshotFS) GetFile(fileName string) FileHandle { if file, ok := s.overlays[s.toPath(fileName)]; ok { return file } if file, ok := s.diskFiles[s.toPath(fileName)]; ok { return file } newEntry := memoizedDiskFile(sync.OnceValue(func() FileHandle { if contents, ok := s.fs.ReadFile(fileName); ok { return newDiskFile(fileName, contents) } return nil })) entry, _ := s.readFiles.LoadOrStore(s.toPath(fileName), newEntry) return entry() } type snapshotFSBuilder struct { fs vfs.FS overlays map[tspath.Path]*overlay diskFiles *dirty.SyncMap[tspath.Path, *diskFile] toPath func(string) tspath.Path } func newSnapshotFSBuilder( fs vfs.FS, overlays map[tspath.Path]*overlay, diskFiles map[tspath.Path]*diskFile, positionEncoding lsproto.PositionEncodingKind, toPath func(fileName string) tspath.Path, ) *snapshotFSBuilder { cachedFS := cachedvfs.From(fs) cachedFS.Enable() return &snapshotFSBuilder{ fs: cachedFS, overlays: overlays, diskFiles: dirty.NewSyncMap(diskFiles, nil), toPath: toPath, } } func (s *snapshotFSBuilder) FS() vfs.FS { return s.fs } func (s *snapshotFSBuilder) Finalize() (*snapshotFS, bool) { diskFiles, changed := s.diskFiles.Finalize() return &snapshotFS{ fs: s.fs, overlays: s.overlays, diskFiles: diskFiles, toPath: s.toPath, }, changed } func (s *snapshotFSBuilder) GetFile(fileName string) FileHandle { path := s.toPath(fileName) return s.GetFileByPath(fileName, path) } func (s *snapshotFSBuilder) GetFileByPath(fileName string, path tspath.Path) FileHandle { if file, ok := s.overlays[path]; ok { return file } entry, _ := s.diskFiles.LoadOrStore(path, &diskFile{fileBase: fileBase{fileName: fileName}, needsReload: true}) if entry != nil { entry.Locked(func(entry dirty.Value[*diskFile]) { if entry.Value() != nil && !entry.Value().MatchesDiskText() { if content, ok := s.fs.ReadFile(fileName); ok { entry.Change(func(file *diskFile) { file.content = content file.hash = xxh3.Hash128([]byte(content)) file.needsReload = false }) } else { entry.Delete() } } }) } if entry == nil || entry.Value() == nil { return nil } return entry.Value() } func (s *snapshotFSBuilder) markDirtyFiles(change FileChangeSummary) { for uri := range change.Changed.Keys() { path := s.toPath(uri.FileName()) if entry, ok := s.diskFiles.Load(path); ok { entry.Change(func(file *diskFile) { file.needsReload = true }) } } for uri := range change.Deleted.Keys() { path := s.toPath(uri.FileName()) if entry, ok := s.diskFiles.Load(path); ok { entry.Change(func(file *diskFile) { file.needsReload = true }) } } }