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.go. All files
@@ -11,6 +11,7 @@ import ( "path/filepath" "regexp" "strings"+ "sync" "time" "github.com/junegunn/fzf/src/tui"@@ -19,10 +20,75 @@ import ( const becomeSuffix = ".become" +// Withheld from the environment replay below, so an outer value cannot+// override what the floating pane set+const internalEnvPrefix = "__FZF_INTERNAL_"+ func escapeSingleQuote(str string) string { return "'" + strings.ReplaceAll(str, "'", "'\\''") + "'" } +// Read and remove, so preview and execute commands do not inherit it+func takeEnv(name string) string {+ value := os.Getenv(name)+ os.Unsetenv(name)+ return value+}++// Applies label updates off the event loop, which would block on the fork+type labelUpdater struct {+ set func(string)+ mu sync.Mutex+ pending *string+ running bool+}++// A queue of one: a burst collapses to the newest label. A goroutine per+// update could apply them out of order+func (u *labelUpdater) update(label string) {+ u.mu.Lock()+ defer u.mu.Unlock()+ u.pending = &label+ if u.running {+ return+ }+ u.running = true+ go u.run()+}++// Ends once caught up, so nothing needs cancelling when Run returns+func (u *labelUpdater) run() {+ for {+ u.mu.Lock()+ label := u.pending+ if label == nil {+ u.running = false+ u.mu.Unlock()+ return+ }+ u.pending = nil+ u.mu.Unlock()+ u.set(*label)+ }+}++// Updates the label on the native border, or nil when it is not fzf's to update+func nativeLabelSetter() func(string) {+ // Read both, so neither is left behind+ tmuxPane, zellijPane := takeEnv(tmuxBorderLabelEnv), takeEnv(zellijBorderLabelEnv)+ var set func(string)+ switch {+ case tmuxPane != "":+ set = func(label string) { setTmuxBorderLabel(tmuxPane, label) }+ case zellijPane != "":+ set = func(label string) { setZellijBorderLabel(zellijPane, label) }+ default:+ return nil+ }+ updater := &labelUpdater{set: set}+ return updater.update+}+ func popupArgStr(args []string, opts *Options) (string, string) { fzf, rest := args[0], args[1:] args = []string{"--bind=ctrl-z:ignore"}@@ -125,6 +191,9 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool) validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`) for _, pairStr := range os.Environ() { pair := strings.SplitN(pairStr, "=", 2)+ if strings.HasPrefix(pair[0], internalEnvPrefix) {+ continue+ } if validIdentifier.MatchString(pair[0]) { exports = append(exports, fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1]))) } else if strings.HasPrefix(pair[0], "BASH_FUNC_") && strings.HasSuffix(pair[0], "%%") {