Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"strings"
"syscall"
"time"

"github.com/fsnotify/fsnotify"
)

type cmdItem struct {
Expand All @@ -23,6 +25,9 @@ type app struct {
ui *ui
nav *nav
ticker *time.Ticker
watcher *fsnotify.Watcher
watcherEvents <-chan fsnotify.Event
watcherErrors <-chan error
quitChan chan struct{}
cmd *exec.Cmd
cmdIn io.WriteCloser
Expand Down Expand Up @@ -457,6 +462,18 @@ func (app *app) loop() {
app.nav.renew()
app.ui.loadFile(app, false)
app.ui.draw(app.nav)
case ev := <-app.watcherEvents:
switch ev.Op {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the documentation it is better to use Event.Has instead of direct comparison for checking event types, as it's possible for a single event to have multiple flags set.

case fsnotify.Chmod, fsnotify.Write:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to see Write events being handled as well before merging this, otherwise this feature will end up in a half-implemented state that may or may not ever get done. From my testing, I think it is fine to just do the following in response, and renew is not necessary if no files have been added/renamed/deleted:

app.ui.loadFile(app, false)
app.ui.draw(app.nav)


default:
app.nav.renew()
app.ui.loadFile(app, false)
app.ui.draw(app.nav)
}
case err := <-app.watcherErrors:
app.ui.echoerrf("watcher: %s", err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to restrict the use of echoerr/echoerrf to things like invalid input from the user, as this could end up being very annoying. Use log.Printf here instead, which can be viewed when logging is enabled, and same goes for all the other places where the watcher runs into an error.

app.ui.draw(app.nav)
case <-app.nav.previewTimer.C:
app.nav.previewLoading = true
app.ui.draw(app.nav)
Expand Down
2 changes: 2 additions & 0 deletions complete.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ var (
"wrapscroll!",
"findlen",
"period",
"watch",
"nowatch",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bool option, so please follow the guidelines in #758 to maintain consistency with all the other existing options. You can copy and paste the code from some other option.

"scrolloff",
"tabstop",
"errorfmt",
Expand Down
4 changes: 4 additions & 0 deletions doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,10 @@ Note that directories are already updated automatically in many cases.
This option can be useful when there is an external process changing the displayed directory and you are not doing anything in lf.
Periodic checks are disabled when the value of this option is set to zero.

## watch (bool) (default false)

If enabled, watch visible directories for changes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please mention any limitations here, e.g. lack of support for certain types of filesystems, ignoring chmod events, etc..

Also I think this feature should be labelled as experimental for the time being, as it involves the use of another library. I have tested these changes and it works fine for me, but I am not sure about other platforms.


## preserve ([]string) (default `mode`)

List of attributes that are preserved when copying files.
Expand Down
85 changes: 85 additions & 0 deletions eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"unicode"
"unicode/utf8"

"github.com/fsnotify/fsnotify"
"github.com/gdamore/tcell/v2"
)

Expand Down Expand Up @@ -602,6 +603,55 @@ func (e *setExpr) eval(app *app, args []string) {
app.ticker.Stop()
app.ticker = time.NewTicker(time.Duration(gOpts.period) * time.Second)
}
case "watch":
if e.val != "" {
app.ui.echoerrf("watch: unexpected value: %s", e.val)
return
}

if app.watcher != nil {
break
}

watcher, err := fsnotify.NewWatcher()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the code for creating a new watcher should be moved into a separate function, which you'll probably want to do after implementing set watch!. The same goes for the code for cleaning up the existing watcher.

if err != nil {
app.ui.echoerrf("watch: new watcher: %s", err)
return
}
app.watcher = watcher
app.watcherEvents = watcher.Events
app.watcherErrors = watcher.Errors
gOpts.watch = true

for _, d := range app.nav.dirs {
if err := watcher.Add(d.path); err != nil {
app.ui.echoerrf("watch: watcher add: %s", err)
return
}
}
if len(app.nav.dirs) > 0 {
curr, err := app.nav.currFile()
if err == nil {
if curr.IsDir() {
if err := watcher.Add(curr.path); err != nil {
app.ui.echoerrf("watch: watcher add: %s", err)
return
}
}
}
}
case "nowatch":
if e.val != "" {
app.ui.echoerrf("nowatch: unexpected value: %s", e.val)
return
}
gOpts.watch = false
if app.watcher != nil {
app.watcher.Close()
app.watcher = nil
app.watcherEvents = nil
app.watcherErrors = nil
}
case "preview":
if e.val == "" || e.val == "true" {
if len(gOpts.ratios) < 2 {
Expand Down Expand Up @@ -1161,6 +1211,41 @@ func onSelect(app *app) {
if cmd, ok := gOpts.cmds["on-select"]; ok {
cmd.eval(app, nil)
}
if watcher := app.watcher; watcher != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, this entire block of code determines the desired set of paths to watch and then applies it to the watcher. I think it would be better to extract this into a separate function, which can be called during onSelect, but also when creating the watcher after the user enables the watch option.

dirsSet := map[string]struct{}{}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using bool as the value should suffice for sets, see https://go.dev/doc/effective_go#maps. I prefer to follow that recommendation if possible.

for _, d := range app.nav.dirs {
dirsSet[d.path] = struct{}{}
}
if len(app.nav.dirs) > 0 {
curr, err := app.nav.currFile()
if err == nil {
if curr.IsDir() {
dirsSet[curr.path] = struct{}{}
}
}
}

for _, dPath := range watcher.WatchList() {
if _, ok := dirsSet[dPath]; ok {
delete(dirsSet, dPath)
continue
}
Comment on lines +1229 to +1232
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this part might not be needed since adding a watch that already exists is a no-op anyway according to the documentation. Though I don't mind too much either way, I also couldn't find a method to simply specify the exact set the paths to watch.


if err := watcher.Remove(dPath); err != nil {
app.ui.echoerrf("watcher remove: %s", err)
app.ui.draw(app.nav)
return
}
}

for d := range dirsSet {
if err := watcher.Add(d); err != nil {
app.ui.echoerrf("watcher add: %s", err)
app.ui.draw(app.nav)
return
}
}
}
}

func splitKeys(s string) (keys []string) {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.18

require (
github.com/djherbis/times v1.6.0
github.com/fsnotify/fsnotify v1.7.0
github.com/gdamore/tcell/v2 v2.7.0
github.com/mattn/go-runewidth v0.0.15
golang.org/x/sys v0.15.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.7.0 h1:I5LiGTQuwrysAt1KS9wg1yFfOI3arI3ucFrxtd/xqaA=
Expand Down
2 changes: 2 additions & 0 deletions opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ var gOpts struct {
wrapscroll bool
findlen int
period int
watch bool
scrolloff int
tabstop int
errorfmt string
Expand Down Expand Up @@ -212,6 +213,7 @@ func init() {
gOpts.wrapscroll = false
gOpts.findlen = 1
gOpts.period = 0
gOpts.watch = false
gOpts.scrolloff = 0
gOpts.tabstop = 8
gOpts.errorfmt = "\033[7;31;47m"
Expand Down