github.com/elves/elvish@v0.15.0/pkg/cli/addons/navigation/cursor.go (about) 1 package navigation 2 3 import ( 4 "errors" 5 "io" 6 "os" 7 "path/filepath" 8 "unicode/utf8" 9 10 "github.com/elves/elvish/pkg/cli/lscolors" 11 "github.com/elves/elvish/pkg/ui" 12 ) 13 14 // Cursor represents a cursor for navigating in a potentially virtual filesystem. 15 type Cursor interface { 16 // Current returns a File that represents the current directory. 17 Current() (File, error) 18 // Parent returns a File that represents the parent directory. It may return 19 // nil if the current directory is the root of the filesystem. 20 Parent() (File, error) 21 // Ascend navigates to the parent directory. 22 Ascend() error 23 // Descend navigates to the named child directory. 24 Descend(name string) error 25 } 26 27 // File represents a potentially virtual file. 28 type File interface { 29 // Name returns the name of the file. 30 Name() string 31 // ShowName returns a styled filename. 32 ShowName() ui.Text 33 // IsDirDeep returns whether the file is itself a directory or a symlink to 34 // a directory. 35 IsDirDeep() bool 36 // Read returns either a list of File's if the File represents a directory, 37 // a (possibly incomplete) slice of bytes if the File represents a normal 38 // file, or an error if the File cannot be read. 39 Read() ([]File, []byte, error) 40 } 41 42 // NewOSCursor returns a Cursor backed by the OS. 43 func NewOSCursor() Cursor { return osCursor{lscolors.GetColorist()} } 44 45 type osCursor struct{ colorist lscolors.Colorist } 46 47 func (c osCursor) Current() (File, error) { 48 abs, err := filepath.Abs(".") 49 if err != nil { 50 return nil, err 51 } 52 return file{filepath.Base(abs), abs, os.ModeDir, c.colorist}, nil 53 } 54 55 func (c osCursor) Parent() (File, error) { 56 if abs, _ := filepath.Abs("."); abs == "/" { 57 return emptyDir{}, nil 58 } 59 abs, err := filepath.Abs("..") 60 if err != nil { 61 return nil, err 62 } 63 return file{filepath.Base(abs), abs, os.ModeDir, c.colorist}, nil 64 } 65 66 func (c osCursor) Ascend() error { return os.Chdir("..") } 67 68 func (c osCursor) Descend(name string) error { return os.Chdir(name) } 69 70 type emptyDir struct{} 71 72 func (emptyDir) Name() string { return "" } 73 func (emptyDir) ShowName() ui.Text { return nil } 74 func (emptyDir) IsDirDeep() bool { return true } 75 func (emptyDir) Read() ([]File, []byte, error) { return []File{}, nil, nil } 76 77 type file struct { 78 name string 79 path string 80 mode os.FileMode 81 colorist lscolors.Colorist 82 } 83 84 func (f file) Name() string { return f.name } 85 86 func (f file) ShowName() ui.Text { 87 sgrStyle := f.colorist.GetStyle(f.path) 88 return ui.Text{&ui.Segment{ 89 Style: ui.StyleFromSGR(sgrStyle), Text: f.name}} 90 } 91 92 func (f file) IsDirDeep() bool { 93 if f.mode.IsDir() { 94 // File itself is a directory; return true and save a stat call. 95 return true 96 } 97 info, err := os.Stat(f.path) 98 return err == nil && info.IsDir() 99 } 100 101 const previewBytes = 64 * 1024 102 103 var ( 104 errDevice = errors.New("no preview for device file") 105 errNamedPipe = errors.New("no preview for named pipe") 106 errSocket = errors.New("no preview for socket file") 107 errCharDevice = errors.New("no preview for char device") 108 errNonUTF8 = errors.New("no preview for non-utf8 file") 109 ) 110 111 var specialFileModes = []struct { 112 mode os.FileMode 113 err error 114 }{ 115 {os.ModeDevice, errDevice}, 116 {os.ModeNamedPipe, errNamedPipe}, 117 {os.ModeSocket, errSocket}, 118 {os.ModeCharDevice, errCharDevice}, 119 } 120 121 func (f file) Read() ([]File, []byte, error) { 122 ff, err := os.Open(f.path) 123 if err != nil { 124 return nil, nil, err 125 } 126 defer ff.Close() 127 128 info, err := ff.Stat() 129 if err != nil { 130 return nil, nil, err 131 } 132 133 if info.IsDir() { 134 infos, err := ff.Readdir(0) 135 if err != nil { 136 return nil, nil, err 137 } 138 files := make([]File, len(infos)) 139 for i, info := range infos { 140 files[i] = file{ 141 info.Name(), 142 filepath.Join(f.path, info.Name()), 143 info.Mode(), 144 f.colorist, 145 } 146 } 147 return files, nil, err 148 } 149 150 for _, special := range specialFileModes { 151 if info.Mode()&special.mode != 0 { 152 return nil, nil, special.err 153 } 154 } 155 156 var buf [previewBytes]byte 157 nr, err := ff.Read(buf[:]) 158 if err != nil && err != io.EOF { 159 return nil, nil, err 160 } 161 162 content := buf[:nr] 163 if !utf8.Valid(content) { 164 return nil, nil, errNonUTF8 165 } 166 167 return nil, content, nil 168 }