Revert "fix option processing, async dirs & start select (#759)"

This reverts commit fb66a21577.
This commit is contained in:
Gokcehan 2022-03-11 02:43:38 +03:00
parent 07b35def77
commit 8954e9f16a
3 changed files with 142 additions and 215 deletions

105
app.go
View File

@ -32,24 +32,6 @@ type app struct {
cmdHistoryInd int cmdHistoryInd int
} }
var gInvalidate struct {
sort bool
pos bool
dir bool
navSize bool
mouse bool
period bool
}
func init() {
gInvalidate.sort = true
gInvalidate.pos = true
gInvalidate.dir = true
gInvalidate.navSize = true
gInvalidate.mouse = true
gInvalidate.period = true
}
func newApp(ui *ui, nav *nav) *app { func newApp(ui *ui, nav *nav) *app {
quitChan := make(chan struct{}, 1) quitChan := make(chan struct{}, 1)
@ -263,6 +245,19 @@ func (app *app) loop() {
app.ui.readExpr() app.ui.readExpr()
if gSelect != "" {
go func() {
lstat, err := os.Lstat(gSelect)
if err != nil {
app.ui.exprChan <- &callExpr{"echoerr", []string{err.Error()}, 1}
} else if lstat.IsDir() {
app.ui.exprChan <- &callExpr{"cd", []string{gSelect}, 1}
} else {
app.ui.exprChan <- &callExpr{"select", []string{gSelect}, 1}
}
}()
}
if gConfigPath != "" { if gConfigPath != "" {
if _, err := os.Stat(gConfigPath); !os.IsNotExist(err) { if _, err := os.Stat(gConfigPath); !os.IsNotExist(err) {
app.readFile(gConfigPath) app.readFile(gConfigPath)
@ -277,30 +272,6 @@ func (app *app) loop() {
} }
} }
// config has been read, now initialize nav with the wd
wd, err := os.Getwd()
if err != nil {
log.Printf("getting current directory: %s", err)
}
if gSelect != "" {
_, err := os.Lstat(gSelect)
if err != nil {
app.ui.exprChan <- &callExpr{"echoerr", []string{err.Error()}, 1}
} else if abs, err := filepath.Abs(gSelect); err == nil {
// if gSelect contains the /. suffix, the directory itself
// should be selected
if len(gSelect) > 2 && gSelect[len(gSelect)-2:] == "/." {
wd = abs
} else {
wd = filepath.Dir(abs)
app.ui.exprChan <- &callExpr{"select", []string{abs}, 1}
}
}
}
app.nav.getDirs(wd)
app.nav.addJumpList()
// execute commands from args
for _, cmd := range gCommands { for _, cmd := range gCommands {
p := newParser(strings.NewReader(cmd)) p := newParser(strings.NewReader(cmd))
@ -314,49 +285,6 @@ func (app *app) loop() {
} }
for { for {
// process invalidate flags
if gInvalidate.sort {
app.nav.sort()
app.ui.sort()
gInvalidate.sort = false
}
if gInvalidate.pos {
app.nav.position()
gInvalidate.pos = false
}
if gInvalidate.dir {
app.ui.loadFile(app.nav, true)
app.ui.loadFileInfo(app.nav)
app.ui.draw(app.nav)
gInvalidate.dir = false
}
if gInvalidate.navSize {
app.ui.renew()
if app.nav.height != app.ui.wins[0].h {
app.nav.height = app.ui.wins[0].h
app.nav.regCache = make(map[string]*reg)
}
gInvalidate.navSize = false
}
if gInvalidate.mouse {
if gOpts.mouse {
app.ui.screen.EnableMouse()
} else {
app.ui.screen.DisableMouse()
}
gInvalidate.mouse = false
}
if gInvalidate.period {
if gOpts.period == 0 {
app.ticker.Stop()
} else {
app.ticker.Stop()
app.ticker = time.NewTicker(time.Duration(gOpts.period) * time.Second)
}
gInvalidate.period = false
}
select { select {
case <-app.quitChan: case <-app.quitChan:
if app.nav.copyTotal > 0 { if app.nav.copyTotal > 0 {
@ -445,12 +373,7 @@ func (app *app) loop() {
} }
app.ui.draw(app.nav) app.ui.draw(app.nav)
case d := <-app.nav.dirChan: case d := <-app.nav.dirChan:
app.nav.checkDir(d)
if !app.nav.checkDir(d) {
log.Printf("debug: dirChan skip/reload %s", d.path)
continue
}
log.Printf("debug: dirChan %s %t", d.path, d.loading)
if gOpts.dircache { if gOpts.dircache {
prev, ok := app.nav.dirCache[d.path] prev, ok := app.nav.dirCache[d.path]

181
eval.go
View File

@ -7,12 +7,12 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"time"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
) )
func (e *setExpr) eval(app *app, args []string) { func (e *setExpr) eval(app *app, args []string) {
// from app only app.ui.echo*() may be used inside this function
switch e.opt { switch e.opt {
case "anchorfind": case "anchorfind":
gOpts.anchorfind = true gOpts.anchorfind = true
@ -40,70 +40,91 @@ func (e *setExpr) eval(app *app, args []string) {
gOpts.dircounts = !gOpts.dircounts gOpts.dircounts = !gOpts.dircounts
case "dironly": case "dironly":
gOpts.dironly = true gOpts.dironly = true
gInvalidate.sort = true app.nav.sort()
gInvalidate.pos = true app.nav.position()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "nodironly": case "nodironly":
gOpts.dironly = false gOpts.dironly = false
gInvalidate.sort = true app.nav.sort()
gInvalidate.pos = true app.nav.position()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "dironly!": case "dironly!":
gOpts.dironly = !gOpts.dironly gOpts.dironly = !gOpts.dironly
gInvalidate.sort = true app.nav.sort()
gInvalidate.pos = true app.nav.position()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "dirfirst": case "dirfirst":
gOpts.sortType.option |= dirfirstSort gOpts.sortType.option |= dirfirstSort
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
case "nodirfirst": case "nodirfirst":
gOpts.sortType.option &= ^dirfirstSort gOpts.sortType.option &= ^dirfirstSort
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
case "dirfirst!": case "dirfirst!":
gOpts.sortType.option ^= dirfirstSort gOpts.sortType.option ^= dirfirstSort
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
case "drawbox": case "drawbox":
gOpts.drawbox = true gOpts.drawbox = true
gInvalidate.navSize = true app.ui.renew()
gInvalidate.dir = true if app.nav.height != app.ui.wins[0].h {
app.nav.height = app.ui.wins[0].h
app.nav.regCache = make(map[string]*reg)
}
app.ui.loadFile(app.nav, true)
case "nodrawbox": case "nodrawbox":
gOpts.drawbox = false gOpts.drawbox = false
gInvalidate.navSize = true app.ui.renew()
gInvalidate.dir = true if app.nav.height != app.ui.wins[0].h {
app.nav.height = app.ui.wins[0].h
app.nav.regCache = make(map[string]*reg)
}
app.ui.loadFile(app.nav, true)
case "drawbox!": case "drawbox!":
gOpts.drawbox = !gOpts.drawbox gOpts.drawbox = !gOpts.drawbox
gInvalidate.navSize = true app.ui.renew()
gInvalidate.dir = true if app.nav.height != app.ui.wins[0].h {
app.nav.height = app.ui.wins[0].h
app.nav.regCache = make(map[string]*reg)
}
app.ui.loadFile(app.nav, true)
case "globsearch": case "globsearch":
gOpts.globsearch = true gOpts.globsearch = true
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "noglobsearch": case "noglobsearch":
gOpts.globsearch = false gOpts.globsearch = false
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "globsearch!": case "globsearch!":
gOpts.globsearch = !gOpts.globsearch gOpts.globsearch = !gOpts.globsearch
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "hidden": case "hidden":
gOpts.sortType.option |= hiddenSort gOpts.sortType.option |= hiddenSort
gInvalidate.sort = true app.nav.sort()
gInvalidate.pos = true app.nav.position()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "nohidden": case "nohidden":
gOpts.sortType.option &= ^hiddenSort gOpts.sortType.option &= ^hiddenSort
gInvalidate.sort = true app.nav.sort()
gInvalidate.pos = true app.nav.position()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "hidden!": case "hidden!":
gOpts.sortType.option ^= hiddenSort gOpts.sortType.option ^= hiddenSort
gInvalidate.sort = true app.nav.sort()
gInvalidate.pos = true app.nav.position()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "icons": case "icons":
gOpts.icons = true gOpts.icons = true
case "noicons": case "noicons":
@ -112,28 +133,31 @@ func (e *setExpr) eval(app *app, args []string) {
gOpts.icons = !gOpts.icons gOpts.icons = !gOpts.icons
case "ignorecase": case "ignorecase":
gOpts.ignorecase = true gOpts.ignorecase = true
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "noignorecase": case "noignorecase":
gOpts.ignorecase = false gOpts.ignorecase = false
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "ignorecase!": case "ignorecase!":
gOpts.ignorecase = !gOpts.ignorecase gOpts.ignorecase = !gOpts.ignorecase
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "ignoredia": case "ignoredia":
gOpts.ignoredia = true gOpts.ignoredia = true
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
case "noignoredia": case "noignoredia":
gOpts.ignoredia = false gOpts.ignoredia = false
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
case "ignoredia!": case "ignoredia!":
gOpts.ignoredia = !gOpts.ignoredia gOpts.ignoredia = !gOpts.ignoredia
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
case "incfilter": case "incfilter":
gOpts.incfilter = true gOpts.incfilter = true
case "noincfilter": case "noincfilter":
@ -149,20 +173,21 @@ func (e *setExpr) eval(app *app, args []string) {
case "mouse": case "mouse":
if !gOpts.mouse { if !gOpts.mouse {
gOpts.mouse = true gOpts.mouse = true
gInvalidate.mouse = true app.ui.screen.EnableMouse()
} }
case "nomouse": case "nomouse":
if gOpts.mouse { if gOpts.mouse {
gOpts.mouse = false gOpts.mouse = false
gInvalidate.mouse = true app.ui.screen.DisableMouse()
} }
case "mouse!": case "mouse!":
if gOpts.mouse { if gOpts.mouse {
gOpts.mouse = false gOpts.mouse = false
app.ui.screen.DisableMouse()
} else { } else {
gOpts.mouse = true gOpts.mouse = true
app.ui.screen.EnableMouse()
} }
gInvalidate.mouse = true
case "number": case "number":
gOpts.number = true gOpts.number = true
case "nonumber": case "nonumber":
@ -191,28 +216,31 @@ func (e *setExpr) eval(app *app, args []string) {
gOpts.relativenumber = !gOpts.relativenumber gOpts.relativenumber = !gOpts.relativenumber
case "reverse": case "reverse":
gOpts.sortType.option |= reverseSort gOpts.sortType.option |= reverseSort
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
case "noreverse": case "noreverse":
gOpts.sortType.option &= ^reverseSort gOpts.sortType.option &= ^reverseSort
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
case "reverse!": case "reverse!":
gOpts.sortType.option ^= reverseSort gOpts.sortType.option ^= reverseSort
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
case "smartcase": case "smartcase":
gOpts.smartcase = true gOpts.smartcase = true
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "nosmartcase": case "nosmartcase":
gOpts.smartcase = false gOpts.smartcase = false
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "smartcase!": case "smartcase!":
gOpts.smartcase = !gOpts.smartcase gOpts.smartcase = !gOpts.smartcase
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "smartdia": case "smartdia":
gOpts.smartdia = true gOpts.smartdia = true
case "nosmartdia": case "nosmartdia":
@ -255,7 +283,12 @@ func (e *setExpr) eval(app *app, args []string) {
return return
} }
gOpts.period = n gOpts.period = n
gInvalidate.period = true if n == 0 {
app.ticker.Stop()
} else {
app.ticker.Stop()
app.ticker = time.NewTicker(time.Duration(gOpts.period) * time.Second)
}
case "scrolloff": case "scrolloff":
n, err := strconv.Atoi(e.val) n, err := strconv.Atoi(e.val)
if err != nil { if err != nil {
@ -296,9 +329,10 @@ func (e *setExpr) eval(app *app, args []string) {
} }
} }
gOpts.hiddenfiles = toks gOpts.hiddenfiles = toks
gInvalidate.sort = true app.nav.sort()
gInvalidate.pos = true app.nav.position()
gInvalidate.dir = true app.ui.sort()
app.ui.loadFile(app.nav, true)
case "ifs": case "ifs":
gOpts.ifs = e.val gOpts.ifs = e.val
case "info": case "info":
@ -343,7 +377,7 @@ func (e *setExpr) eval(app *app, args []string) {
} }
gOpts.ratios = rats gOpts.ratios = rats
app.ui.wins = getWins(app.ui.screen) app.ui.wins = getWins(app.ui.screen)
gInvalidate.dir = true app.ui.loadFile(app.nav, true)
case "shell": case "shell":
gOpts.shell = e.val gOpts.shell = e.val
case "shellflag": case "shellflag":
@ -374,8 +408,8 @@ func (e *setExpr) eval(app *app, args []string) {
app.ui.echoerr("sortby: value should either be 'natural', 'name', 'size', 'time', 'atime', 'ctime' or 'ext'") app.ui.echoerr("sortby: value should either be 'natural', 'name', 'size', 'time', 'atime', 'ctime' or 'ext'")
return return
} }
gInvalidate.sort = true app.nav.sort()
gInvalidate.dir = true app.ui.sort()
case "tempmarks": case "tempmarks":
if e.val != "" { if e.val != "" {
gOpts.tempmarks = "'" + e.val gOpts.tempmarks = "'" + e.val
@ -399,6 +433,7 @@ func (e *setExpr) eval(app *app, args []string) {
app.ui.echoerrf("unknown option: %s", e.opt) app.ui.echoerrf("unknown option: %s", e.opt)
return return
} }
app.ui.loadFileInfo(app.nav)
} }
func (e *mapExpr) eval(app *app, args []string) { func (e *mapExpr) eval(app *app, args []string) {
@ -407,6 +442,7 @@ func (e *mapExpr) eval(app *app, args []string) {
} else { } else {
gOpts.keys[e.keys] = e.expr gOpts.keys[e.keys] = e.expr
} }
app.ui.loadFileInfo(app.nav)
} }
func (e *cmapExpr) eval(app *app, args []string) { func (e *cmapExpr) eval(app *app, args []string) {
@ -415,6 +451,7 @@ func (e *cmapExpr) eval(app *app, args []string) {
} else { } else {
gOpts.cmdkeys[e.key] = e.expr gOpts.cmdkeys[e.key] = e.expr
} }
app.ui.loadFileInfo(app.nav)
} }
func (e *cmdExpr) eval(app *app, args []string) { func (e *cmdExpr) eval(app *app, args []string) {
@ -423,6 +460,7 @@ func (e *cmdExpr) eval(app *app, args []string) {
} else { } else {
gOpts.cmds[e.name] = e.expr gOpts.cmds[e.name] = e.expr
} }
app.ui.loadFileInfo(app.nav)
} }
func preChdir(app *app) { func preChdir(app *app) {
@ -1256,7 +1294,6 @@ func (e *callExpr) eval(app *app, args []string) {
if err != nil { if err != nil {
log.Printf("getting current directory: %s", err) log.Printf("getting current directory: %s", err)
} }
log.Printf("todo-cd-eval %s", wd)
path = replaceTilde(path) path = replaceTilde(path)
if !filepath.IsAbs(path) { if !filepath.IsAbs(path) {

71
nav.go
View File

@ -163,10 +163,6 @@ func normalize(s1, s2 string, ignorecase, ignoredia bool) (string, string) {
} }
func (dir *dir) sort() { func (dir *dir) sort() {
if dir.loading {
log.Printf("debug: sort/dir still loading: %s", dir.path)
return
}
dir.sortType = gOpts.sortType dir.sortType = gOpts.sortType
dir.dironly = gOpts.dironly dir.dironly = gOpts.dironly
dir.hiddenfiles = gOpts.hiddenfiles dir.hiddenfiles = gOpts.hiddenfiles
@ -310,10 +306,6 @@ func (dir *dir) name() string {
} }
func (dir *dir) sel(name string, height int) { func (dir *dir) sel(name string, height int) {
if dir.loading {
//log.Printf("debug: sel/dir still loading: %s", dir.path)
return
}
if len(dir.files) == 0 { if len(dir.files) == 0 {
dir.ind, dir.pos = 0, 0 dir.ind, dir.pos = 0, 0
return return
@ -399,13 +391,11 @@ func (nav *nav) loadDir(path string) *dir {
if gOpts.dircache { if gOpts.dircache {
d, ok := nav.dirCache[path] d, ok := nav.dirCache[path]
if !ok { if !ok {
log.Printf("debug: dirCache-new: %s", path)
d = nav.loadDirInternal(path) d = nav.loadDirInternal(path)
nav.dirCache[path] = d nav.dirCache[path] = d
return d return d
} }
log.Printf("debug: dirCache-found: %s", path)
nav.checkDir(d) nav.checkDir(d)
return d return d
@ -414,16 +404,11 @@ func (nav *nav) loadDir(path string) *dir {
} }
} }
func (nav *nav) checkDir(dir *dir) bool { func (nav *nav) checkDir(dir *dir) {
if dir.loading {
log.Printf("debug: skip loading: %s", dir.path)
return false
}
s, err := os.Stat(dir.path) s, err := os.Stat(dir.path)
if err != nil { if err != nil {
log.Printf("getting directory info: %s", err) log.Printf("getting directory info: %s", err)
return true return
} }
switch { switch {
@ -433,10 +418,9 @@ func (nav *nav) checkDir(dir *dir) bool {
// XXX: Linux builtin exFAT drivers are able to predict modifications in the future // XXX: Linux builtin exFAT drivers are able to predict modifications in the future
// https://bugs.launchpad.net/ubuntu/+source/ubuntu-meta/+bug/1872504 // https://bugs.launchpad.net/ubuntu/+source/ubuntu-meta/+bug/1872504
if s.ModTime().After(now) { if s.ModTime().After(now) {
return true return
} }
log.Printf("debug: checkDir-reload: %s time", dir.path)
dir.loading = true dir.loading = true
dir.loadTime = now dir.loadTime = now
go func() { go func() {
@ -445,23 +429,18 @@ func (nav *nav) checkDir(dir *dir) bool {
nd.sort() nd.sort()
nav.dirChan <- nd nav.dirChan <- nd
}() }()
return false
case dir.sortType != gOpts.sortType || case dir.sortType != gOpts.sortType ||
dir.dironly != gOpts.dironly || dir.dironly != gOpts.dironly ||
!reflect.DeepEqual(dir.hiddenfiles, gOpts.hiddenfiles) || !reflect.DeepEqual(dir.hiddenfiles, gOpts.hiddenfiles) ||
dir.ignorecase != gOpts.ignorecase || dir.ignorecase != gOpts.ignorecase ||
dir.ignoredia != gOpts.ignoredia: dir.ignoredia != gOpts.ignoredia:
log.Printf("debug: checkDir-reload: %s opt", dir.path)
sd := dir
dir.loading = true dir.loading = true
dir.loadTime = time.Now()
go func() { go func() {
sd.sort() dir.sort()
nav.dirChan <- sd dir.loading = false
nav.dirChan <- dir
}() }()
return false
} }
return true
} }
func (nav *nav) getDirs(wd string) { func (nav *nav) getDirs(wd string) {
@ -481,6 +460,10 @@ func (nav *nav) getDirs(wd string) {
} }
func newNav(height int) *nav { func newNav(height int) *nav {
wd, err := os.Getwd()
if err != nil {
log.Printf("getting current directory: %s", err)
}
nav := &nav{ nav := &nav{
copyBytesChan: make(chan int64, 1024), copyBytesChan: make(chan int64, 1024),
@ -503,7 +486,8 @@ func newNav(height int) *nav {
jumpListInd: -1, jumpListInd: -1,
} }
// do not call nav.getDirs() as our configuration isn't set up yet nav.getDirs(wd)
nav.addJumpList()
return nav return nav
} }
@ -589,14 +573,7 @@ func (nav *nav) exportFiles() {
currSelections := nav.currSelections() currSelections := nav.currSelections()
var wd string exportFiles(currFile, currSelections, nav.currDir().path)
if currDir := nav.currDir(); currDir != nil {
wd = currDir.path
} else {
wd, _ = os.Getwd()
}
exportFiles(currFile, currSelections, wd)
} }
func (nav *nav) previewLoop(ui *ui) { func (nav *nav) previewLoop(ui *ui) {
@ -1275,26 +1252,19 @@ func (nav *nav) sync() error {
} }
func (nav *nav) cd(wd string) error { func (nav *nav) cd(wd string) error {
currDir := nav.currDir()
wd = replaceTilde(wd) wd = replaceTilde(wd)
wd = filepath.Clean(wd) wd = filepath.Clean(wd)
if !filepath.IsAbs(wd) && currDir != nil { if !filepath.IsAbs(wd) {
wd = filepath.Join(currDir.path, wd) wd = filepath.Join(nav.currDir().path, wd)
} }
if err := os.Chdir(wd); err != nil { if err := os.Chdir(wd); err != nil {
return fmt.Errorf("cd: %s", err) return fmt.Errorf("cd: %s", err)
} }
if currDir == nil || wd != currDir.path { nav.getDirs(wd)
nav.getDirs(wd) nav.addJumpList()
nav.addJumpList()
} else {
log.Printf("debug: skip cd: %s", wd)
}
return nil return nil
} }
@ -1572,16 +1542,13 @@ func (nav *nav) writeMarks() error {
} }
func (nav *nav) currDir() *dir { func (nav *nav) currDir() *dir {
if len(nav.dirs) == 0 {
return nil
}
return nav.dirs[len(nav.dirs)-1] return nav.dirs[len(nav.dirs)-1]
} }
func (nav *nav) currFile() (*file, error) { func (nav *nav) currFile() (*file, error) {
dir := nav.currDir() dir := nav.dirs[len(nav.dirs)-1]
if dir == nil || len(dir.files) == 0 { if len(dir.files) == 0 {
return nil, fmt.Errorf("empty directory") return nil, fmt.Errorf("empty directory")
} }