2025-10-15 10:12:44 +03:00

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
})
}
}
}