Skip to content

Commit af3b8cf

Browse files
committed
feat: add --follow flag and improve resize reliability
- Add --follow (-f) flag to read command for continuous output streaming - Add --follow-ms flag to customize poll interval (default 100ms) - Send explicit SIGWINCH on resize for better signal delivery to child processes
1 parent c4a546b commit af3b8cf

File tree

2 files changed

+63
-0
lines changed

2 files changed

+63
-0
lines changed

cmd/read.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package cmd
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
7+
"os"
8+
"os/signal"
9+
"syscall"
10+
"time"
611

712
"github.com/schovi/shelli/internal/ansi"
813
"github.com/schovi/shelli/internal/daemon"
@@ -31,6 +36,8 @@ var (
3136
readTimeoutFlag int
3237
readStripAnsiFlag bool
3338
readJsonFlag bool
39+
readFollowFlag bool
40+
readFollowMsFlag int
3441
)
3542

3643
func init() {
@@ -42,6 +49,8 @@ func init() {
4249
readCmd.Flags().IntVar(&readTimeoutFlag, "timeout", 10, "Max wait time in seconds (for blocking modes)")
4350
readCmd.Flags().BoolVar(&readStripAnsiFlag, "strip-ansi", false, "Strip ANSI escape codes")
4451
readCmd.Flags().BoolVar(&readJsonFlag, "json", false, "Output as JSON")
52+
readCmd.Flags().BoolVarP(&readFollowFlag, "follow", "f", false, "Follow output continuously (like tail -f)")
53+
readCmd.Flags().IntVar(&readFollowMsFlag, "follow-ms", 100, "Poll interval for --follow in milliseconds")
4554
}
4655

4756
func runRead(cmd *cobra.Command, args []string) error {
@@ -76,6 +85,13 @@ func runRead(cmd *cobra.Command, args []string) error {
7685
return fmt.Errorf("--wait and --settle are mutually exclusive")
7786
}
7887

88+
if readFollowFlag {
89+
if readAllFlag || readHeadFlag > 0 || readTailFlag > 0 || blocking || readJsonFlag {
90+
return fmt.Errorf("--follow cannot be combined with --all, --head, --tail, --wait, --settle, or --json")
91+
}
92+
return runReadFollow(name)
93+
}
94+
7995
client := daemon.NewClient()
8096
if err := client.EnsureDaemon(); err != nil {
8197
return fmt.Errorf("daemon: %w", err)
@@ -138,3 +154,42 @@ func runRead(cmd *cobra.Command, args []string) error {
138154

139155
return nil
140156
}
157+
158+
func runReadFollow(name string) error {
159+
client := daemon.NewClient()
160+
if err := client.EnsureDaemon(); err != nil {
161+
return fmt.Errorf("daemon: %w", err)
162+
}
163+
164+
ctx, cancel := context.WithCancel(context.Background())
165+
defer cancel()
166+
167+
sigCh := make(chan os.Signal, 1)
168+
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
169+
go func() {
170+
<-sigCh
171+
cancel()
172+
}()
173+
174+
pollInterval := time.Duration(readFollowMsFlag) * time.Millisecond
175+
ticker := time.NewTicker(pollInterval)
176+
defer ticker.Stop()
177+
178+
for {
179+
select {
180+
case <-ctx.Done():
181+
return nil
182+
case <-ticker.C:
183+
output, _, err := client.Read(name, daemon.ReadModeNew, 0, 0)
184+
if err != nil {
185+
return err
186+
}
187+
if output != "" {
188+
if readStripAnsiFlag {
189+
output = ansi.Strip(output)
190+
}
191+
fmt.Print(output)
192+
}
193+
}
194+
}
195+
}

internal/daemon/server.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,14 @@ func (s *Server) handleResize(req Request) Response {
822822
return Response{Success: false, Error: fmt.Sprintf("resize: %v", err)}
823823
}
824824

825+
// Send SIGWINCH explicitly to ensure the process receives it
826+
// (pty.Setsize should trigger this via kernel, but explicit signal is more reliable)
827+
s.mu.Lock()
828+
if cmd, ok := s.cmds[req.Name]; ok && cmd.Process != nil {
829+
cmd.Process.Signal(syscall.SIGWINCH)
830+
}
831+
s.mu.Unlock()
832+
825833
meta.Cols = cols
826834
meta.Rows = rows
827835
if err := storage.SaveMeta(req.Name, meta); err != nil {

0 commit comments

Comments
 (0)