-
Notifications
You must be signed in to change notification settings - Fork 358
Add watch option #1548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add watch option #1548
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,8 @@ import ( | |
| "strings" | ||
| "syscall" | ||
| "time" | ||
|
|
||
| "github.com/fsnotify/fsnotify" | ||
| ) | ||
|
|
||
| type cmdItem struct { | ||
|
|
@@ -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 | ||
|
|
@@ -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 { | ||
| case fsnotify.Chmod, fsnotify.Write: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like to see 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) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be better to restrict the use of |
||
| app.ui.draw(app.nav) | ||
| case <-app.nav.previewTimer.C: | ||
| app.nav.previewLoading = true | ||
| app.ui.draw(app.nav) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -196,6 +196,8 @@ var ( | |
| "wrapscroll!", | ||
| "findlen", | ||
| "period", | ||
| "watch", | ||
| "nowatch", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a |
||
| "scrolloff", | ||
| "tabstop", | ||
| "errorfmt", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ import ( | |
| "unicode" | ||
| "unicode/utf8" | ||
|
|
||
| "github.com/fsnotify/fsnotify" | ||
| "github.com/gdamore/tcell/v2" | ||
| ) | ||
|
|
||
|
|
@@ -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() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| 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 { | ||
|
|
@@ -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 { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| dirsSet := map[string]struct{}{} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using |
||
| 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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
|
||
There was a problem hiding this comment.
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.Hasinstead of direct comparison for checking event types, as it's possible for a single event to have multiple flags set.