Skip to content
Open
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ end
- Value length: Length of found value string in bytes (64-bit little-endian unsigned integer)
- Value string: String of above-specified length (UTF-8 encoded)

#### Reload Definition

You can specify automatic reloading behavior for modes by using the `reload` keyword.

```shell
mode <name>
reload -- <cmd>
end
```

- `cmd`: A shell command to run for the lifetime of each pager running in this mode. Each line printed to STDOUT by this process will trigger a reload of the pager. The process will be sent a SIGTERM when the pager is closed.

For example, this works great with `inotifywait` (or similar for your OS) if you want to reload when certain files are modified.

To reload on a consistent interval, try something like `reload -- while sleep 5; do echo; done`.

Note: Reloads are debounced so that many lines printed quickly will only trigger one reload.

#### Keybinding Definitions

The `bindkey` keyword starts a keybinding definition.
Expand Down
17 changes: 17 additions & 0 deletions lib/configurer.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ loop:
switch directive {
case "annotate":
c.ProcessModeAnnotate(mode, args)
case "reload":
c.ProcessModeReload(mode, args)
case "bindkey":
c.ProcessModeBindkey(mode, args)
case "end":
Expand Down Expand Up @@ -156,6 +158,21 @@ func (c *configurer) ProcessModeAnnotate(mode Mode, args []string) {
}
}

func (c *configurer) ProcessModeReload(mode Mode, args []string) {
switch len(args) {
case 1:
mode.RegisterReloadWatcher(func(ctx Context) func(Pager) {
return func(p Pager) {
if rw, err := NewReloadWatcher(p, args[0], ctx); err == nil {
p.AddReloadWatcher(rw)
}
}
})
default:
panic("Expected 1 arg for 'reload'")
}
}

func (c *configurer) ProcessModeBindkey(mode Mode, args []string) {
switch len(args) {
case 2:
Expand Down
19 changes: 17 additions & 2 deletions lib/mode.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package rat

type Mode interface {
RegisterAnnotator(func(Context) Annotator)
RegisterEventHandler(func(Context) func(Pager))
InitAnnotators(Context) func() []Annotator
AddEventHandlers(Context) func(Pager)
AddReloadWatcher(Context) func(Pager)
RegisterAnnotator(func(Context) Annotator)
RegisterEventHandler(func(Context) func(Pager))
RegisterReloadWatcher(func(Context) func(Pager))
}

type mode struct {
annotatorCtors []func(Context) Annotator
eventHandlerAdders []func(Context) func(Pager)
reloadWatcherAdder func(Context) func(Pager)
}

func NewMode() Mode {
Expand Down Expand Up @@ -41,10 +44,22 @@ func (m *mode) AddEventHandlers(ctx Context) func(Pager) {
}
}

func (m *mode) AddReloadWatcher(ctx Context) func(Pager) {
return func(p Pager) {
if m.reloadWatcherAdder != nil {
m.reloadWatcherAdder(ctx)(p)
}
}
}

func (m *mode) RegisterAnnotator(ctor func(Context) Annotator) {
m.annotatorCtors = append(m.annotatorCtors, ctor)
}

func (m *mode) RegisterEventHandler(adder func(Context) func(Pager)) {
m.eventHandlerAdders = append(m.eventHandlerAdders, adder)
}

func (m *mode) RegisterReloadWatcher(adder func(Context) func(Pager)) {
m.reloadWatcherAdder = adder
}
40 changes: 29 additions & 11 deletions lib/pager.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Pager interface {
Widget
AddEventHandler(keyStr string, handler EventHandler)
Reload()
AddReloadWatcher(ReloadWatcher)
CursorUp()
CursorDown()
CursorFirstLine()
Expand All @@ -23,14 +24,15 @@ type Pager interface {
}

type pager struct {
title string
modes []Mode
ctx Context
buffer Buffer
scrollOffsetY int
cursorY int
stop chan bool
eventHandlers HandlerRegistry
title string
modes []Mode
ctx Context
buffer Buffer
scrollOffsetY int
cursorY int
stop chan bool
eventHandlers HandlerRegistry
reloadWatchers []ReloadWatcher

box Box
headerBox Box
Expand All @@ -46,6 +48,7 @@ func newPager(title string, modeNames string, ctx Context) *pager {

splitModeNames := strings.Split(modeNames, ",")
p.modes = make([]Mode, 0, len(splitModeNames))
p.reloadWatchers = make([]ReloadWatcher, 0, 2)

for _, modeName := range splitModeNames {
if mode, ok := modes[modeName]; ok {
Expand Down Expand Up @@ -73,7 +76,11 @@ func (p *pager) Stop() {
}

func (p *pager) Destroy() {
p.Stop()
for _, rw := range p.reloadWatchers {
rw.Stop()
}

p.buffer.Close()
}

func (p *pager) HandleEvent(ks []keyEvent) bool {
Expand Down Expand Up @@ -209,11 +216,16 @@ func (p *pager) PageDown() {
func (p *pager) Reload() {
}

func (p *pager) AddReloadWatcher(rw ReloadWatcher) {
p.reloadWatchers = append(p.reloadWatchers, rw)
}

func NewReadPager(rd io.Reader, title string, modeNames string, ctx Context) Pager {
p := newPager(title, modeNames, ctx)

for _, mode := range p.modes {
mode.AddEventHandlers(ctx)(p)
mode.AddReloadWatcher(ctx)(p)
}

p.buffer = NewBuffer(rd)
Expand All @@ -234,12 +246,13 @@ func NewCmdPager(modeNames string, cmd string, ctx Context) Pager {
cp.cmd = cmd
cp.pager = newPager(cmd, modeNames, ctx)

cp.RunCommand()

for _, mode := range cp.modes {
mode.AddEventHandlers(ctx)(cp)
mode.AddReloadWatcher(ctx)(cp)
}

cp.RunCommand()

return cp
}

Expand All @@ -248,6 +261,11 @@ func (cp *cmdPager) Stop() {
cp.pager.Stop()
}

func (cp *cmdPager) Destroy() {
cp.command.Close()
cp.pager.Destroy()
}

func (cp *cmdPager) Reload() {
cp.Stop()
cp.RunCommand()
Expand Down
62 changes: 62 additions & 0 deletions lib/reload_watcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package rat

import (
"bufio"
"io"
"os"
"os/exec"
"syscall"
"time"
)

type ReloadWatcher interface {
Stop() error
}

type reloadWatcher struct {
cmd *exec.Cmd
}

func NewReloadWatcher(p Pager, cmd string, ctx Context) (ReloadWatcher, error) {
rw := &reloadWatcher{}

rw.cmd = exec.Command(os.Getenv("SHELL"), "-c", cmd)
rw.cmd.Env = ContextEnvironment(ctx)
rw.cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

var (
stdout io.Reader
err error
)

if stdout, err = rw.cmd.StdoutPipe(); err != nil {
return rw, err
}

err = rw.cmd.Start()
go func() { rw.cmd.Wait() }()

go rw.scanLines(stdout, p)

return rw, err
}

func (rw *reloadWatcher) scanLines(rd io.Reader, p Pager) {
scanner := bufio.NewScanner(rd)

var timer *time.Timer

for scanner.Scan() {
if timer != nil {
timer.Stop()
}

timer = time.AfterFunc(500*time.Millisecond, p.Reload)
}
}

func (rw *reloadWatcher) Stop() error {
err := syscall.Kill(-rw.cmd.Process.Pid, syscall.SIGTERM)
rw.cmd.Wait()
return err
}