Skip to content
Merged
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
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,16 @@ Copy the `zp` script somewhere on your `PATH`.
## Usage

```
usage: zp [root ...]
usage: zp [--print] [root ...]

zmx-picker - lists zmx sessions, plus repos under any roots
given as arguments or $ZP_ROOT (colon-separated)

options: -h/--help, -V/--version
options:
--print Print directory and complete session name, each NUL-terminated,
instead of attaching; cancellation emits nothing
-h, --help Show this help message
-V, --version Print the version
```

For example, to always pick from repositories under `~/src`, set:
Expand Down Expand Up @@ -89,6 +93,33 @@ Any [zmx] session labels (`zmx set <name> key=value`) are shown alongside each
session and are fuzzy-searchable, so typing `key=value` narrows the picker to
the sessions that carry that label.

### Selecting without attaching

Use `zp --print [root ...]` to select a session for another command, such as an
autossh wrapper that reconnects to the same selection. Selecting an entry prints
two NUL-terminated fields to stdout, with no trailing newline:

1. The directory from which to attach: the repository directory when selecting a
repository, or the directory where `zp` was launched when selecting an
existing session or typing a name. This is not the selected session's
original starting directory or its live `cwd` reported by zmx.
2. The complete session name, including `ZMX_SESSION_PREFIX` when set. Clear
that variable when attaching so zmx does not apply the prefix again.

Selection does not create or attach a session. Esc or Ctrl-C cancels with exit
status 0 and no output. The picker still supports Ctrl-X to kill sessions.

In Bash, read the fields directly; command substitution (`$(zp --print)`) cannot
preserve NUL bytes. For example, to attach to the selection:

```bash
if { IFS= read -r -d '' start_dir && IFS= read -r -d '' session_name; } < <(zp --print); then
(cd "$start_dir" && ZMX_SESSION_PREFIX= zmx attach "$session_name")
fi
```

The reads fail on cancellation, so the example does not attach in that case.

## License

zmx-picker is released under the [Zero Clause BSD License](LICENSE) (SPDX:
Expand Down
23 changes: 16 additions & 7 deletions tests/harness.exp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
# blocks until a terminal answers, so every expect below responds to
# them; without that the picker stalls and looks like zero matches.
#
# Usage: harness.exp <zp-path> <root-or-empty> [keys ...]
# Usage: harness.exp <zp-path> [--print] <root-or-empty> [keys ...]
# Each keys argument is sent after a settle period. Exits with zp's
# exit status.
# exit status. With --print, captures stdout in $ZP_TEST_DIR/selection.

log_user 0

Expand All @@ -18,16 +18,25 @@ proc idle {secs} {
}

set zp [lindex $argv 0]
set root [lindex $argv 1]
set argv [lrange $argv 1 end]
set print_mode [expr {[lindex $argv 0] eq "--print"}]
if {$print_mode} {
set argv [lrange $argv 1 end]
}
set root [lindex $argv 0]
set roots {}
if {$root ne ""} {
lappend roots $root
}

if {$root eq ""} {
spawn -noecho $zp
if {$print_mode} {
spawn -noecho sh -c {exec "$@" > "$ZP_TEST_DIR/selection"} sh $zp --print {*}$roots
} else {
spawn -noecho $zp $root
spawn -noecho $zp {*}$roots
}
catch { exec stty rows 40 columns 120 < $spawn_out(slave,name) 2> /dev/null }

foreach keys [lrange $argv 2 end] {
foreach keys [lrange $argv 1 end] {
idle 2
send -- $keys
}
Expand Down
38 changes: 38 additions & 0 deletions tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -214,5 +214,43 @@ else
not_ok '--version and -V report the version' "$out"
fi

# Compare files to preserve NUL delimiters and catch any extra stdout.
# assert_print <description> <root> <query> <key> <directory> <session>
# An empty expected directory means cancellation should emit no bytes.
assert_print() {
local description=$1 rc=0
reset_log
rm -f "$ZP_TEST_DIR/selection"
: >"$ZP_TEST_DIR/expected"
if [[ -n $5 ]]; then
printf '%s\0%s\0' "$5" "$6" >"$ZP_TEST_DIR/expected"
fi
run --print "$2" "$3" "$4" || rc=$?
if [[ $rc -eq 0 && -z $(log) ]] &&
cmp -s "$ZP_TEST_DIR/expected" "$ZP_TEST_DIR/selection"; then
ok "$description"
else
not_ok "$description" "rc=$rc log=$(log); output bytes: $(od -An -tx1 "$ZP_TEST_DIR/selection" 2>/dev/null)"
fi
}

ZMX_SESSION_PREFIX='' assert_print '--print emits the caller directory and existing name without attaching' \
'' 'beta' "$enter" "$PWD" beta
ZMX_SESSION_PREFIX=d. assert_print '--print never re-applies the prefix to an existing name' \
'' 'gamma' "$enter" "$PWD" d.gamma
ZMX_SESSION_PREFIX='' assert_print '--print emits an unmatched query without attaching' \
'' 'zzz-new' "$enter" "$PWD" zzz-new
ZMX_SESSION_PREFIX=d. assert_print '--print prefixes an unmatched query' \
'' 'zzz-new' "$enter" "$PWD" d.zzz-new
ZMX_SESSION_PREFIX=d. assert_print '--print ctrl-n uses the query even with a match highlighted' \
'' 'bet' "$ctrl_n" "$PWD" d.bet

print_root="$ZP_TEST_DIR/root with spaces"
mkdir -p "$print_root/omega/.git"
ZMX_SESSION_PREFIX=d. assert_print '--print preserves repo paths with spaces and numbers prefixed sessions' \
"$print_root" '[repo] omega' "$enter" "$print_root/omega" d.omega.10
assert_print '--print cancellation exits successfully with no output or attach' \
'' '' "$esc" '' ''

printf '\n%d passed, %d failed\n' "$pass" "$fail"
[[ $fail -eq 0 ]]
31 changes: 25 additions & 6 deletions zp
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,14 @@ next_session() {
printf '%s.%s\n' "$base" "$next"
}

attach() {
if [[ $mode == print ]]; then
printf '%s\0%s\0' "$PWD" "${ZMX_SESSION_PREFIX:-}$1"
else
zmx attach "$1"
fi
}

select_session() {
local output rc query key selected
local type value
Expand Down Expand Up @@ -324,7 +332,7 @@ select_session() {
printf 'ctrl-n needs a typed query to name the new session\n' >&2
return 1
fi
zmx attach "$query"
attach "$query"
return 0
fi

Expand All @@ -335,13 +343,13 @@ select_session() {
session)
# The listed name is already complete; keep zmx from
# applying $ZMX_SESSION_PREFIX to it again.
ZMX_SESSION_PREFIX='' zmx attach "$value"
ZMX_SESSION_PREFIX='' attach "$value"
;;

repo)
(
cd "$value"
zmx attach "$(next_session "$value")"
attach "$(next_session "$value")"
)
;;

Expand All @@ -356,7 +364,7 @@ select_session() {
if [[ -n $query ]]; then
# No candidate matched, so use the entered query as a new
# session name in the current directory.
zmx attach "$query"
attach "$query"
return
fi

Expand All @@ -375,11 +383,22 @@ case ${1:-} in
mode='kill'
shift
;;
--print)
# Print the selection as NUL-terminated start_dir and session_name
# instead of attaching. session_name is complete, callers should set
# ZMX_SESSION_PREFIX=.
mode='print'
shift
;;
-h | --help)
printf 'usage: %s [root ...]\n\n' "${0##*/}"
printf 'usage: %s [--print] [root ...]\n\n' "${0##*/}"
printf 'zmx-picker - lists zmx sessions, plus repos under any roots\n'
printf 'given as arguments or $ZP_ROOT (colon-separated)\n\n'
printf 'options: -h/--help, -V/--version\n'
printf 'options:\n'
printf ' --print Print directory and complete session name, each NUL-terminated,\n'
printf ' instead of attaching; cancellation emits nothing\n'
printf ' -h, --help Show this help message\n'
printf ' -V, --version Print the version\n'
exit 0
;;
-V | --version)
Expand Down
Loading