Merge pull request #635 from peco/split-messagehub

consolidate SendStatusMsg
This commit is contained in:
lestrrat 2026-02-17 07:20:05 +09:00 committed by GitHub
commit 79bc52f006
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 92 additions and 44 deletions

View file

@ -62,7 +62,7 @@ func (a ActionFunc) RegisterKeySequence(name string, k keyseq.KeyList) {
func wrapDeprecated(fn func(context.Context, *Peco, Event), oldName, newName string) ActionFunc {
return ActionFunc(func(ctx context.Context, state *Peco, e Event) {
state.Hub().SendStatusMsg(ctx, fmt.Sprintf("%s is deprecated. Use %s", oldName, newName))
state.Hub().SendStatusMsg(ctx, fmt.Sprintf("%s is deprecated. Use %s", oldName, newName), 0)
fn(ctx, state, e)
})
}
@ -353,7 +353,7 @@ func doFinish(ctx context.Context, state *Peco, _ Event) {
})
var err error
state.Hub().SendStatusMsg(ctx, "Executing "+ccarg)
state.Hub().SendStatusMsg(ctx, "Executing "+ccarg, 0)
cmd := util.Shell(ccarg)
cmd.Stdin = &stdin
cmd.Stdout = state.Stdout
@ -732,7 +732,7 @@ func doToggleQuery(ctx context.Context, state *Peco, _ Event) {
}
func doKonamiCommand(ctx context.Context, state *Peco, e Event) {
state.Hub().SendStatusMsg(ctx, "All your filters are belongs to us")
state.Hub().SendStatusMsg(ctx, "All your filters are belongs to us", 0)
}
func doToggleSingleKeyJump(ctx context.Context, state *Peco, e Event) {
@ -784,7 +784,7 @@ func doGoToAdjacentSelection(ctx context.Context, state *Peco, forward bool) {
selection := state.Selection()
if selection.Len() == 0 {
state.Hub().SendStatusMsg(ctx, "No Selection")
state.Hub().SendStatusMsg(ctx, "No Selection", 0)
return
}
@ -833,11 +833,11 @@ func doGoToAdjacentSelection(ctx context.Context, state *Peco, forward bool) {
}
if found {
state.Hub().SendStatusMsg(ctx, label+" Selection")
state.Hub().SendStatusMsg(ctx, label+" Selection", 0)
state.Hub().SendPaging(ctx, hub.ToScrollFirstItem)
state.Hub().SendPaging(ctx, hub.JumpToLineRequest(target))
} else {
state.Hub().SendStatusMsg(ctx, label+" Selection (first)")
state.Hub().SendStatusMsg(ctx, label+" Selection (first)", 0)
state.Hub().SendPaging(ctx, hub.ToScrollFirstItem)
state.Hub().SendPaging(ctx, hub.JumpToLineRequest(wrapTarget))
}
@ -862,7 +862,7 @@ func doFreezeResults(ctx context.Context, state *Peco, _ Event) {
b := state.CurrentLineBuffer()
if b.Size() == 0 {
state.Hub().SendStatusMsg(ctx, "Nothing to freeze")
state.Hub().SendStatusMsg(ctx, "Nothing to freeze", 0)
return
}
@ -877,7 +877,7 @@ func doFreezeResults(ctx context.Context, state *Peco, _ Event) {
state.SetFrozenSource(frozen)
resetQueryState(state)
state.SetCurrentLineBuffer(frozen)
state.Hub().SendStatusMsg(ctx, "Results frozen")
state.Hub().SendStatusMsg(ctx, "Results frozen", 0)
state.Hub().SendDrawPrompt(ctx)
}
@ -888,14 +888,14 @@ func doUnfreezeResults(ctx context.Context, state *Peco, _ Event) {
}
if state.FrozenSource() == nil {
state.Hub().SendStatusMsg(ctx, "No frozen results")
state.Hub().SendStatusMsg(ctx, "No frozen results", 0)
return
}
state.ClearFrozenSource()
resetQueryState(state)
state.ResetCurrentLineBuffer()
state.Hub().SendStatusMsg(ctx, "Results unfrozen")
state.Hub().SendStatusMsg(ctx, "Results unfrozen", 0)
state.Hub().SendDrawPrompt(ctx)
}
@ -907,7 +907,7 @@ func doZoomIn(ctx context.Context, state *Peco, _ Event) {
// Already zoomed in?
if state.PreZoomBuffer() != nil {
state.Hub().SendStatusMsg(ctx, "Already zoomed in")
state.Hub().SendStatusMsg(ctx, "Already zoomed in", 0)
return
}
@ -916,7 +916,7 @@ func doZoomIn(ctx context.Context, state *Peco, _ Event) {
// If the current buffer is the source (no active filter), nothing to zoom into
if currentBuf == state.source {
state.Hub().SendStatusMsg(ctx, "Nothing to zoom into")
state.Hub().SendStatusMsg(ctx, "Nothing to zoom into", 0)
return
}
@ -925,7 +925,7 @@ func doZoomIn(ctx context.Context, state *Peco, _ Event) {
contextBuf := NewContextBuffer(currentBuf, source, contextSize)
if contextBuf.Size() == 0 {
state.Hub().SendStatusMsg(ctx, "Nothing to zoom into")
state.Hub().SendStatusMsg(ctx, "Nothing to zoom into", 0)
return
}
@ -957,7 +957,7 @@ func doZoomOut(ctx context.Context, state *Peco, _ Event) {
preZoom := state.PreZoomBuffer()
if preZoom == nil {
state.Hub().SendStatusMsg(ctx, "Not zoomed in")
state.Hub().SendStatusMsg(ctx, "Not zoomed in", 0)
return
}

View file

@ -32,7 +32,7 @@ func (h *recordingHub) SendPaging(_ context.Context, v hub.PagingRequest) {
h.pagingArgs = append(h.pagingArgs, v)
}
func (h *recordingHub) SendStatusMsg(_ context.Context, msg string) {
func (h *recordingHub) SendStatusMsg(_ context.Context, msg string, _ time.Duration) {
h.mu.Lock()
defer h.mu.Unlock()
h.statusMsgs = append(h.statusMsgs, msg)

View file

@ -443,7 +443,7 @@ func (f *Filter) Work(ctx context.Context, q *hub.Payload[string]) {
go func(ctx context.Context) {
defer state.Hub().SendDraw(ctx, &hub.DrawOptions{RunningQuery: true})
if err := p.Run(ctx); err != nil {
state.Hub().SendStatusMsg(ctx, err.Error())
state.Hub().SendStatusMsg(ctx, err.Error(), 0)
}
}(ctx)
@ -454,7 +454,7 @@ func (f *Filter) Work(ctx context.Context, q *hub.Payload[string]) {
}
t := time.NewTicker(50 * time.Millisecond)
defer t.Stop()
defer state.Hub().SendStatusMsg(ctx, "")
defer state.Hub().SendStatusMsg(ctx, "", 0)
defer state.Hub().SendDraw(ctx, &hub.DrawOptions{RunningQuery: true})
for {
select {
@ -512,7 +512,7 @@ func (f *Filter) Loop(ctx context.Context, cancel func()) error {
previous = workcancel
mutex.Unlock()
f.state.Hub().SendStatusMsg(ctx, "Running query...")
f.state.Hub().SendStatusMsg(ctx, "Running query...", 0)
go f.Work(workctx, q)
}

View file

@ -151,9 +151,11 @@ func (h *Hub) StatusMsgCh() chan *Payload[StatusMsg] {
return h.statusMsgCh
}
// SendStatusMsg sends a string to be displayed in the status message
func (h *Hub) SendStatusMsg(ctx context.Context, q string) {
h.SendStatusMsgAndClear(ctx, q, 0)
// SendStatusMsg sends a string to be displayed in the status message.
// If clearDelay is non-zero, the message will be cleared after that duration.
func (h *Hub) SendStatusMsg(ctx context.Context, q string, clearDelay time.Duration) {
msg := newStatusMsgReq(q, clearDelay)
send(ctx, h.StatusMsgCh(), NewPayload[StatusMsg](msg, isBatchCtx(ctx)))
}
// StatusMsg is an interface for status message requests.
@ -182,13 +184,6 @@ func newStatusMsgReq(s string, d time.Duration) *statusMsgReq {
}
}
// SendStatusMsgAndClear sends a string to be displayed in the status message,
// as well as a delay until the message should be cleared
func (h *Hub) SendStatusMsgAndClear(ctx context.Context, q string, clearDelay time.Duration) {
msg := newStatusMsgReq(q, clearDelay)
send(ctx, h.StatusMsgCh(), NewPayload[StatusMsg](msg, isBatchCtx(ctx)))
}
// PagingCh returns the channel to page through the results
func (h *Hub) PagingCh() chan *Payload[PagingRequest] {
return h.pagingCh

View file

@ -6,6 +6,7 @@ import (
"time"
"github.com/peco/peco/hub"
"github.com/stretchr/testify/require"
)
func TestHub(t *testing.T) {
@ -59,7 +60,7 @@ func TestHub(t *testing.T) {
h.Batch(ctx, func(ctx context.Context) {
h.SendQuery(ctx, "Hello World!")
h.SendDraw(ctx, &hub.DrawOptions{})
h.SendStatusMsg(ctx, "Hello, World!")
h.SendStatusMsg(ctx, "Hello, World!", 0)
h.SendPaging(ctx, hub.PagingRequestType(1))
}, true)
@ -85,3 +86,35 @@ func TestHub(t *testing.T) {
}
}
}
func TestSendStatusMsg(t *testing.T) {
t.Run("zero delay", func(t *testing.T) {
h := hub.New(5)
ctx := context.Background()
go func() {
h.SendStatusMsg(ctx, "hello", 0)
}()
p := <-h.StatusMsgCh()
defer p.Done()
require.Equal(t, "hello", p.Data().Message())
require.Equal(t, time.Duration(0), p.Data().Delay())
})
t.Run("non-zero delay", func(t *testing.T) {
h := hub.New(5)
ctx := context.Background()
go func() {
h.SendStatusMsg(ctx, "temporary", 500*time.Millisecond)
}()
p := <-h.StatusMsgCh()
defer p.Done()
require.Equal(t, "temporary", p.Data().Message())
require.Equal(t, 500*time.Millisecond, p.Data().Delay())
})
}

View file

@ -519,23 +519,36 @@ type Input struct {
state *Peco
}
// MessageHub is the interface that must be satisfied by the
// message hub component. Unless we're in testing, github.com/peco/peco/hub.Hub
// is used.
type MessageHub interface {
// HubSender provides methods for sending messages to the hub.
// Most code (actions, input handling, source setup) only needs
// the sender side.
type HubSender interface {
Batch(context.Context, func(context.Context), bool)
DrawCh() chan *hub.Payload[*hub.DrawOptions]
PagingCh() chan *hub.Payload[hub.PagingRequest]
QueryCh() chan *hub.Payload[string]
SendDraw(context.Context, *hub.DrawOptions)
SendDrawPrompt(context.Context)
SendPaging(context.Context, hub.PagingRequest)
SendQuery(context.Context, string)
SendStatusMsg(context.Context, string)
SendStatusMsgAndClear(context.Context, string, time.Duration)
SendStatusMsg(context.Context, string, time.Duration)
}
// HubReceiver provides methods for receiving messages from the hub.
// Only the view loop and filter loop consume from these channels.
type HubReceiver interface {
DrawCh() chan *hub.Payload[*hub.DrawOptions]
PagingCh() chan *hub.Payload[hub.PagingRequest]
QueryCh() chan *hub.Payload[string]
StatusMsgCh() chan *hub.Payload[hub.StatusMsg]
}
// MessageHub is the interface that must be satisfied by the
// message hub component. Unless we're in testing, github.com/peco/peco/hub.Hub
// is used. It combines HubSender (for dispatching messages) and
// HubReceiver (for consuming them via channels).
type MessageHub interface {
HubSender
HubReceiver
}
type filterProcessor struct {
filter filter.Filter
query string

View file

@ -79,7 +79,7 @@ func wrapRememberSequence(a Action) Action {
if s, err := keyseq.KeyEventToString(ev.Key, ev.Ch, ev.Mod); err == nil {
seq := state.Inputseq()
seq.Add(s)
state.Hub().SendStatusMsg(ctx, strings.Join(seq.KeyNames(), " "))
state.Hub().SendStatusMsg(ctx, strings.Join(seq.KeyNames(), " "), 0)
}
a.Execute(ctx, state, ev)
})
@ -94,7 +94,7 @@ func wrapClearSequence(a Action) Action {
if seq.Len() > 0 {
msg := strings.Join(seq.KeyNames(), " ")
state.Hub().SendStatusMsgAndClear(ctx, msg, 500*time.Millisecond)
state.Hub().SendStatusMsg(ctx, msg, 500*time.Millisecond)
seq.Reset()
}

View file

@ -32,10 +32,17 @@ func (h nullHub) SendDraw(_ context.Context, _ *hub.DrawOptions)
func (h nullHub) SendDrawPrompt(context.Context) {}
func (h nullHub) SendPaging(_ context.Context, _ hub.PagingRequest) {}
func (h nullHub) SendQuery(_ context.Context, _ string) {}
func (h nullHub) SendStatusMsg(_ context.Context, _ string) {}
func (h nullHub) SendStatusMsgAndClear(_ context.Context, _ string, _ time.Duration) {}
func (h nullHub) SendStatusMsg(_ context.Context, _ string, _ time.Duration) {}
func (h nullHub) StatusMsgCh() chan *hub.Payload[hub.StatusMsg] { return nil }
// Compile-time interface compliance checks.
var (
_ MessageHub = (*hub.Hub)(nil)
_ HubSender = (*hub.Hub)(nil)
_ HubReceiver = (*hub.Hub)(nil)
_ MessageHub = nullHub{}
)
type interceptorArgs []interface{}
type interceptor struct {
m sync.Mutex

View file

@ -84,7 +84,7 @@ func (s *Source) Setup(ctx context.Context, state *Peco) {
notifycb := func() {
// close the ready channel so others can be notified
// that there's at least 1 line in the buffer
state.Hub().SendStatusMsg(ctx, "")
state.Hub().SendStatusMsg(ctx, "", 0)
close(s.ready)
}
@ -131,7 +131,7 @@ func (s *Source) Setup(ctx context.Context, state *Peco) {
}
}()
state.Hub().SendStatusMsg(ctx, "Waiting for input...")
state.Hub().SendStatusMsg(ctx, "Waiting for input...", 0)
readCount := 0
for loop := true; loop; {