feat: auto-refresh directory listing, enabled by default every 5s

This commit is contained in:
vrubelroman 2026-05-04 01:07:22 +03:00
parent a9ccff8635
commit f3b2fe59c4
3 changed files with 53 additions and 0 deletions

View file

@ -70,6 +70,8 @@ type BehaviorConfig struct {
ConfirmOverwrite bool `toml:"confirm_overwrite"` ConfirmOverwrite bool `toml:"confirm_overwrite"`
CalculateDirSizeOnSpace bool `toml:"calculate_dir_size_on_space"` CalculateDirSizeOnSpace bool `toml:"calculate_dir_size_on_space"`
FollowSymlinks bool `toml:"follow_symlinks"` FollowSymlinks bool `toml:"follow_symlinks"`
AutoRefresh bool `toml:"auto_refresh"`
AutoRefreshInterval int `toml:"auto_refresh_interval"`
} }
func Default() Config { func Default() Config {
@ -111,6 +113,8 @@ func Default() Config {
ConfirmOverwrite: true, ConfirmOverwrite: true,
CalculateDirSizeOnSpace: true, CalculateDirSizeOnSpace: true,
FollowSymlinks: false, FollowSymlinks: false,
AutoRefresh: true,
AutoRefreshInterval: 5,
}, },
} }
} }
@ -205,6 +209,14 @@ func (c *Config) Validate() error {
if c.UI.CenterWidthPercent < 20 || c.UI.CenterWidthPercent > 60 { if c.UI.CenterWidthPercent < 20 || c.UI.CenterWidthPercent > 60 {
return errors.New("ui.center_width_percent must be between 20 and 60") return errors.New("ui.center_width_percent must be between 20 and 60")
} }
if c.Behavior.AutoRefresh {
if c.Behavior.AutoRefreshInterval < 1 {
c.Behavior.AutoRefreshInterval = 5
}
if c.Behavior.AutoRefreshInterval > 60 {
return errors.New("behavior.auto_refresh_interval must be between 1 and 60")
}
}
switch strings.ToLower(strings.TrimSpace(c.Browser.Sort.By)) { switch strings.ToLower(strings.TrimSpace(c.Browser.Sort.By)) {
case "", "name": case "", "name":
c.Browser.Sort.By = "name" c.Browser.Sort.By = "name"

View file

@ -167,6 +167,7 @@ type copyDoneMsg struct {
type dismissNoticeMsg struct{} type dismissNoticeMsg struct{}
type dismissYankFlashMsg struct{} type dismissYankFlashMsg struct{}
type tickMsg struct{}
type externalOpenMsg struct { type externalOpenMsg struct {
path string path string
err error err error
@ -333,6 +334,9 @@ func NewModel(cfg config.Config, configPath string) (Model, error) {
} }
func (m Model) Init() tea.Cmd { func (m Model) Init() tea.Cmd {
if m.cfg.Behavior.AutoRefresh {
return tea.Batch(m.loadPreviewCmd(), autoRefreshTickCmd(m.cfg.Behavior.AutoRefreshInterval))
}
return m.loadPreviewCmd() return m.loadPreviewCmd()
} }
@ -919,6 +923,20 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.syncPreviewContent() m.syncPreviewContent()
return m, nil return m, nil
case tickMsg:
if !m.cfg.Behavior.AutoRefresh ||
m.busy ||
m.copyJob != nil ||
m.archiveJob != nil ||
m.modal.kind != modalNone ||
m.filterMode ||
m.cursorMode || m.visualMode ||
m.viewMode {
return m, autoRefreshTickCmd(m.cfg.Behavior.AutoRefreshInterval)
}
m.autoRefreshPanes()
return m, tea.Batch(autoRefreshTickCmd(m.cfg.Behavior.AutoRefreshInterval), m.loadPreviewCmd())
case externalOpenMsg: case externalOpenMsg:
if msg.err != nil { if msg.err != nil {
m.status = fmt.Sprintf("Open failed: %v", msg.err) m.status = fmt.Sprintf("Open failed: %v", msg.err)
@ -1768,6 +1786,21 @@ func (m *Model) refreshAllPanes(status string) (tea.Model, tea.Cmd) {
return m, m.loadPreviewCmd() return m, m.loadPreviewCmd()
} }
func (m *Model) autoRefreshPanes() {
for _, id := range []PaneID{PaneLeft, PaneRight} {
pane := m.paneByID(id)
if pane.InRemote() || pane.InArchive() {
continue
}
if name := selectedName(pane); name != "" {
pane.SaveCursor(pane.Path, name)
}
if err := m.reloadPane(id, pane.LoadCursor(pane.Path)); err != nil {
log.Printf("[REFRESH] pane=%s path=%s err=%v", id, pane.Path, err)
}
}
}
func (m *Model) moveCursor(delta int) { func (m *Model) moveCursor(delta int) {
// When a filter query is active on this pane, move through filtered entries // When a filter query is active on this pane, move through filtered entries
// only, so the cursor always lands on a matching item. // only, so the cursor always lands on a matching item.
@ -4867,6 +4900,12 @@ func dismissYankFlashCmd(delay time.Duration) tea.Cmd {
}) })
} }
func autoRefreshTickCmd(seconds int) tea.Cmd {
return tea.Tick(time.Duration(seconds)*time.Second, func(time.Time) tea.Msg {
return tickMsg{}
})
}
func (m *Model) startCopyJob(kind fileOpKind, sourcePaths []string, targetDir string, overwrite bool, stats vfs.TransferStats) tea.Cmd { func (m *Model) startCopyJob(kind fileOpKind, sourcePaths []string, targetDir string, overwrite bool, stats vfs.TransferStats) tea.Cmd {
m.nextCopyJob++ m.nextCopyJob++
jobID := m.nextCopyJob jobID := m.nextCopyJob

View file

@ -41,3 +41,5 @@ confirm_delete = true
confirm_overwrite = true confirm_overwrite = true
calculate_dir_size_on_space = true calculate_dir_size_on_space = true
follow_symlinks = false follow_symlinks = false
auto_refresh = true
auto_refresh_interval = 5