143 lines
3.5 KiB
Go
143 lines
3.5 KiB
Go
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
|
|
})
|
|
}
|
|
}
|
|
}
|