internal/filesystem/filesystem.go (211 lines of code) (raw):

package filesystem import ( "bytes" "fmt" "io" "io/fs" "log" "os" "sync" "github.com/Azure/azapi-lsp/internal/source" "github.com/spf13/afero" ) type fsystem struct { memFs afero.Fs osFs afero.Fs docMeta map[string]*documentMetadata docMetaMu *sync.RWMutex logger *log.Logger } func NewFilesystem() *fsystem { return &fsystem{ memFs: afero.NewMemMapFs(), osFs: afero.NewReadOnlyFs(afero.NewOsFs()), docMeta: make(map[string]*documentMetadata), docMetaMu: &sync.RWMutex{}, logger: log.New(io.Discard, "", 0), } } func (fs *fsystem) SetLogger(logger *log.Logger) { fs.logger = logger } func (fs *fsystem) CreateDocument(dh DocumentHandler, langId string, text []byte) error { _, err := fs.memFs.Stat(dh.Dir()) if err != nil { if os.IsNotExist(err) { err := fs.memFs.MkdirAll(dh.Dir(), 0o755) if err != nil { return fmt.Errorf("failed to create parent dir: %w", err) } } else { return err } } f, err := fs.memFs.Create(dh.FullPath()) if err != nil { return err } _, err = f.Write(text) if err != nil { return err } return fs.createDocumentMetadata(dh, langId, text) } func (fs *fsystem) CreateAndOpenDocument(dh DocumentHandler, langId string, text []byte) error { err := fs.CreateDocument(dh, langId, text) if err != nil { return err } return fs.markDocumentAsOpen(dh) } func (fs *fsystem) ChangeDocument(dh VersionedDocumentHandler, changes DocumentChanges) error { if len(changes) == 0 { return nil } isOpen, err := fs.isDocumentOpen(dh) if err != nil { return err } if !isOpen { return &DocumentNotOpenErr{dh} } f, err := fs.memFs.OpenFile(dh.FullPath(), os.O_RDWR, 0o700) if err != nil { return err } defer f.Close() var buf bytes.Buffer _, err = buf.ReadFrom(f) if err != nil { return err } for _, ch := range changes { err := fs.applyDocumentChange(&buf, ch) if err != nil { return err } } err = f.Truncate(0) if err != nil { return err } _, err = f.Seek(0, 0) if err != nil { return err } _, err = f.Write(buf.Bytes()) if err != nil { return err } return fs.updateDocumentMetadataLines(dh, buf.Bytes()) } func (fs *fsystem) applyDocumentChange(buf *bytes.Buffer, change DocumentChange) error { // if the range is nil, we assume it is full content change if change.Range() == nil { buf.Reset() _, err := buf.WriteString(change.Text()) return err } lines := source.MakeSourceLines("", buf.Bytes()) startByte, err := ByteOffsetForPos(lines, change.Range().Start) if err != nil { return err } endByte, err := ByteOffsetForPos(lines, change.Range().End) if err != nil { return err } diff := endByte - startByte if diff > 0 { buf.Grow(diff) } beforeChange := make([]byte, startByte) copy(beforeChange, buf.Bytes()) afterBytes := buf.Bytes()[endByte:] afterChange := make([]byte, len(afterBytes)) copy(afterChange, afterBytes) buf.Reset() _, err = buf.Write(beforeChange) if err != nil { return err } _, err = buf.WriteString(change.Text()) if err != nil { return err } _, err = buf.Write(afterChange) if err != nil { return err } return nil } func (fs *fsystem) CloseAndRemoveDocument(dh DocumentHandler) error { isOpen, err := fs.isDocumentOpen(dh) if err != nil { return err } if !isOpen { return &DocumentNotOpenErr{dh} } err = fs.memFs.Remove(dh.FullPath()) if err != nil { return err } return fs.removeDocumentMetadata(dh) } func (fs *fsystem) GetDocument(dh DocumentHandler) (Document, error) { dm, err := fs.getDocumentMetadata(dh) if err != nil { return nil, err } return &document{ meta: dm, fs: fs.memFs, }, nil } func (fs *fsystem) ReadFile(name string) ([]byte, error) { b, err := afero.ReadFile(fs.memFs, name) if err != nil && os.IsNotExist(err) { return afero.ReadFile(fs.osFs, name) } return b, err } func (fsys *fsystem) ReadDir(name string) ([]fs.DirEntry, error) { memList, err := afero.NewIOFS(fsys.memFs).ReadDir(name) if err != nil && !os.IsNotExist(err) { return nil, fmt.Errorf("memory FS: %w", err) } osList, err := afero.NewIOFS(fsys.osFs).ReadDir(name) if err != nil && !os.IsNotExist(err) { return nil, fmt.Errorf("OS FS: %w", err) } list := memList for _, osEntry := range osList { if fileIsInList(list, osEntry) { continue } list = append(list, osEntry) } return list, nil } func fileIsInList(list []fs.DirEntry, entry fs.DirEntry) bool { for _, di := range list { if di.Name() == entry.Name() { return true } } return false } func (fs *fsystem) Open(name string) (fs.File, error) { f, err := fs.memFs.Open(name) if err != nil && os.IsNotExist(err) { return fs.osFs.Open(name) } return f, err } func (fs *fsystem) Stat(name string) (os.FileInfo, error) { fi, err := fs.memFs.Stat(name) if err != nil && os.IsNotExist(err) { return fs.osFs.Stat(name) } return fi, err }