260729.2 · 50e6c026ec23df76de316cca9fe2d9eea56cb38f
Separate floating pane border label from pane title
--border-label of a tmux floating pane was stored in the pane title and read back by pane-border-format as '#{pane_title}', so any program in the pane could replace the border text by setting the title. On Zellij the name was only set when the pane was created. Either way the built-in label actions could not touch it, as fzf draws no border of its own there.
- tmux: hold the label in pane-scoped @fzf-border-label. Set the option
even without a label, so a later change-border-label has somewhere to
write. Drop select-pane -T; the pane title is left to the user
- Zellij: run 'zellij action rename-pane' on label change. 'zellij run'
has no option to set the environment of the new pane, so the command
exports the target itself
- change-border-label and transform-border-label now update the native
border, through the setter nativeLabelSetter picks by multiplexer
- The pane is named in a __FZF_INTERNAL_ variable that runProxy withholds
from the environment replay, so the same name in the outer environment
cannot redirect the update to another pane
reading declarations
Changes to src/proxy_test.go. All files
@@ -0,0 +1,65 @@+package fzf++import (+ "strconv"+ "sync"+ "testing"+ "time"+)++func TestLabelUpdater(t *testing.T) {+ var mu sync.Mutex+ applied := []int{}+ // The goroutine can still be running after the test function returns, so+ // it records the label instead of reporting it, and -1 stands in for one+ // that could not be parsed+ updater := &labelUpdater{set: func(label string) {+ n, err := strconv.Atoi(label)+ if err != nil {+ n = -1+ }+ mu.Lock()+ applied = append(applied, n)+ mu.Unlock()+ // Long enough for the burst to pile up behind the first update+ time.Sleep(time.Millisecond)+ }}++ const last = 99+ for i := 0; i <= last; i++ {+ updater.update(strconv.Itoa(i))+ }++ // However many updates are dropped, the final one is always applied+ deadline := time.Now().Add(5 * time.Second)+ for {+ mu.Lock()+ done := len(applied) > 0 && applied[len(applied)-1] == last+ mu.Unlock()+ if done {+ break+ }+ if time.Now().After(deadline) {+ t.Fatalf("last update was not applied: %v", applied)+ }+ time.Sleep(time.Millisecond)+ }++ mu.Lock()+ defer mu.Unlock()+ // Updates are never applied out of order, so the border cannot be left+ // showing a label older than the last one requested+ for i, n := range applied {+ if i > 0 && n <= applied[i-1] {+ t.Errorf("applied out of order: %v", applied)+ break+ }+ }+ // A burst collapses instead of running one command per update. The+ // updates are queued in a tight loop while each one takes a millisecond+ // to apply, so all of them landing would mean no coalescing at all+ if len(applied) >= last+1 {+ t.Errorf("expected updates to be coalesced, applied all %d", len(applied))+ }+ t.Logf("applied %d of %d updates", len(applied), last+1)+}