@kecan0406/ttheme 1.1.5 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/shell/ttheme.zsh CHANGED
@@ -3,6 +3,7 @@ typeset -g TTHEME_HOME=${${(%):-%x}:A:h} TTHEME_PALETTES_AT=""
3
3
  zmodload -F zsh/stat b:zstat 2>/dev/null
4
4
 
5
5
  __tt_palettes_load() {
6
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
6
7
  local -a at
7
8
  unset TTHEME_PALETTE TTHEME_GROUP TTHEME_CATALOG TTHEME_NATIVE TTHEME_SRC
8
9
  source $TTHEME_HOME/palettes.zsh || return 1
@@ -11,6 +12,7 @@ __tt_palettes_load() {
11
12
  }
12
13
 
13
14
  __tt_fresh() {
15
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
14
16
  local -a at
15
17
  local was=$TTHEME_STARTUP
16
18
  if ! zstat -F %s.%N -A at +mtime -- $TTHEME_HOME/palettes.zsh 2>/dev/null; then
@@ -64,6 +66,7 @@ typeset -g TTHEME_PIN="" TTHEME_PIN_SPEC="" TTHEME_BASE_SPEC=""
64
66
  __tt_tilde() { REPLY=${1/#$HOME\//\~/} }
65
67
 
66
68
  __tt_pins_load() {
69
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
67
70
  setopt localoptions extendedglob
68
71
  local raw="" line key name
69
72
  [[ -r $TTHEME_PINS_FILE ]] && raw="$(<$TTHEME_PINS_FILE)"
@@ -74,6 +77,7 @@ __tt_pins_load() {
74
77
  line=${line%%[[:space:]]##}
75
78
  [[ $line == [~/]*[[:space:]]* ]] || continue
76
79
  name=${line##*[[:space:]]} key=${${line%[[:space:]]*}%%[[:space:]]##}
80
+ [[ $key == \~ ]] && key=$HOME
77
81
  TTHEME_PINS[${key/#\~\//$HOME/}]=$name
78
82
  [[ -n ${TTHEME_PALETTE[$name]} ]] || print -u2 "ttheme: unknown palette '$name' pinned to $key"
79
83
  done
@@ -93,50 +97,35 @@ __tt_pins_load
93
97
 
94
98
  typeset -g TTHEME_STATE_DIR=${XDG_STATE_HOME:-$HOME/.local/state}/ttheme
95
99
 
96
- if [[ $TERM_PROGRAM == WarpTerminal ]]; then
97
- typeset -g TTHEME_ADAPTER=warp
98
- elif [[ -n $GHOSTTY_RESOURCES_DIR || $TERM_PROGRAM == ghostty ]]; then
99
- typeset -g TTHEME_ADAPTER=ghostty
100
- elif [[ -n $KITTY_WINDOW_ID ]]; then
101
- typeset -g TTHEME_ADAPTER=kitty
102
- elif [[ -n $WEZTERM_PANE ]]; then
103
- typeset -g TTHEME_ADAPTER=wezterm
104
- elif [[ -n $ALACRITTY_WINDOW_ID ]]; then
105
- typeset -g TTHEME_ADAPTER=alacritty
106
- elif [[ -n $ITERM_SESSION_ID || $TERM_PROGRAM == iTerm.app ]]; then
107
- typeset -g TTHEME_ADAPTER=iterm2
108
- elif [[ $TERM_PROGRAM == Apple_Terminal ]]; then
109
- typeset -g TTHEME_ADAPTER=terminal-app
110
- elif [[ -n $WT_SESSION && -z $TERM_PROGRAM ]]; then
111
- typeset -g TTHEME_ADAPTER=windows-terminal
112
- elif [[ $TERM == foot* ]]; then
113
- typeset -g TTHEME_ADAPTER=foot
114
- else
115
- typeset -g TTHEME_ADAPTER=unknown
116
- fi
117
-
118
- typeset -g TTHEME_GHOSTTY_PID="" TTHEME_TMUX=0 TTHEME_MUXED=0
119
-
120
- __tt_reload() {
121
- (( ${TTHEME_TERMINALS[(Ie)ghostty]} )) || return 0
122
- local pid=$PPID ppid comm owner=$TTHEME_GHOSTTY_PID
123
- (( TTHEME_TMUX )) && { pid=$(tmux display -p '#{client_pid}' 2>/dev/null) owner="" }
124
- if [[ -z $owner ]]; then
125
- owner=0
126
- while (( pid > 1 )); do
127
- read -r ppid comm <<< "$(ps -o ppid=,comm= -p $pid)"
128
- if [[ ${comm:t} == ghostty ]]; then
129
- owner=$pid
130
- break
131
- fi
132
- pid=$ppid
133
- done
134
- (( TTHEME_TMUX )) || TTHEME_GHOSTTY_PID=$owner
100
+ () {
101
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
102
+ if [[ $TERM_PROGRAM == WarpTerminal ]]; then
103
+ typeset -g TTHEME_ADAPTER=warp
104
+ elif [[ -n $GHOSTTY_RESOURCES_DIR || $TERM_PROGRAM == ghostty ]]; then
105
+ typeset -g TTHEME_ADAPTER=ghostty
106
+ elif [[ -n $KITTY_WINDOW_ID ]]; then
107
+ typeset -g TTHEME_ADAPTER=kitty
108
+ elif [[ -n $WEZTERM_PANE ]]; then
109
+ typeset -g TTHEME_ADAPTER=wezterm
110
+ elif [[ -n $ALACRITTY_WINDOW_ID ]]; then
111
+ typeset -g TTHEME_ADAPTER=alacritty
112
+ elif [[ -n $KONSOLE_VERSION ]]; then
113
+ typeset -g TTHEME_ADAPTER=konsole
114
+ elif [[ -n $ITERM_SESSION_ID || $TERM_PROGRAM == iTerm.app ]]; then
115
+ typeset -g TTHEME_ADAPTER=iterm2
116
+ elif [[ $TERM_PROGRAM == Apple_Terminal ]]; then
117
+ typeset -g TTHEME_ADAPTER=terminal-app
118
+ elif [[ -n $WT_SESSION && -z $TERM_PROGRAM ]]; then
119
+ typeset -g TTHEME_ADAPTER=windows-terminal
120
+ elif [[ $TERM == foot* ]]; then
121
+ typeset -g TTHEME_ADAPTER=foot
122
+ else
123
+ typeset -g TTHEME_ADAPTER=unknown
135
124
  fi
136
- (( owner )) && kill -USR2 $owner 2>/dev/null && return
137
- pkill -USR2 -x ghostty 2>/dev/null
138
125
  }
139
126
 
127
+ typeset -g TTHEME_TMUX=0 TTHEME_MUXED=0
128
+
140
129
  __tt_tmux() {
141
130
  [[ $(tmux if -t "$TMUX_PANE" -F '#{==:#{allow-passthrough},off}' 'set -p allow-passthrough on' \; set -sq focus-events on \; display -p '#{client_control_mode}' 2>/dev/null) == 1 ]] && return 0
142
131
  TTHEME_TMUX=1 TTHEME_SPEC=
@@ -145,12 +134,24 @@ __tt_tmux() {
145
134
  __tt_active() {
146
135
  [[ -o interactive ]] || return 1
147
136
  (( ${#TTHEME_PALETTE} )) || return 1
148
- [[ $TTHEME_ADAPTER != (unknown|warp) || -n $TTHEME_FORCE ]]
137
+ [[ -n $TTHEME_FORCE ]] || { [[ $TTHEME_ADAPTER != unknown ]] && __tt_paints }
149
138
  }
150
139
 
151
140
  typeset -g TTHEME_HEARD=""
152
141
  typeset -gi TTHEME_RECHECK=0 TTHEME_FOCUS=0
153
142
 
143
+ __tt_put() {
144
+ local f=${1:A}
145
+ shift
146
+ if zmodload -F zsh/files b:zf_mv 2>/dev/null; then
147
+ print -rl -- "$@" >| $f.$$ && zf_mv -f -- $f.$$ $f && return 0
148
+ else
149
+ print -rl -- "$@" >| $f.$$ && command mv -f -- $f.$$ $f && return 0
150
+ fi
151
+ command rm -f -- $f.$$ 2>/dev/null
152
+ return 1
153
+ }
154
+
154
155
  __tt_recall() {
155
156
  local f=$TTHEME_STATE_DIR/colors.$TTHEME_ADAPTER line
156
157
  REPLY=""
@@ -165,7 +166,7 @@ __tt_remember() {
165
166
  [[ -n $1 && -z $TTHEME_PAINTED ]] && (( ! TTHEME_TMUX )) || return 0
166
167
  [[ -r $f && "$(<$f)" == "$line" ]] && return 0
167
168
  [[ -d $TTHEME_STATE_DIR ]] || mkdir -p $TTHEME_STATE_DIR 2>/dev/null
168
- print -r -- "$line" 2>/dev/null > $f
169
+ __tt_put $f "$line" 2>/dev/null
169
170
  }
170
171
 
171
172
  __tt_listen() {
@@ -212,6 +213,7 @@ __tt_recheck() {
212
213
  }
213
214
 
214
215
  source $TTHEME_HOME/adapters/_osc.zsh
216
+ source $TTHEME_HOME/adapters/_wired.zsh
215
217
  [[ -r $TTHEME_HOME/adapters/$TTHEME_ADAPTER.zsh ]] &&
216
218
  source $TTHEME_HOME/adapters/$TTHEME_ADAPTER.zsh
217
219
 
@@ -274,11 +276,12 @@ __tt_dir_rule() {
274
276
  [[ -n ${TTHEME_PALETTE[$TTHEME_PINS[$k]]} ]] || continue
275
277
  base=${${k%/\*\*}:A}
276
278
  if [[ $k == *"/**" ]]; then
277
- [[ $p == "$base" || $p == "$base"/* ]] || continue
279
+ [[ $p == "$base" || $p == "${base%/}"/* ]] || continue
278
280
  else
279
281
  [[ $p == "$base" ]] || continue
280
282
  fi
281
- (( ${#base} > bestlen )) && { best=$k; bestlen=${#base} }
283
+ (( ${#base} > bestlen )) || [[ ${#base} == $bestlen && $k != *"/**" ]] || continue
284
+ best=$k bestlen=${#base}
282
285
  done
283
286
  [[ -n $best ]] || return 1
284
287
  REPLY=$best
@@ -304,6 +307,7 @@ __tt_dir_sync() {
304
307
  }
305
308
 
306
309
  __tt_chpwd() {
310
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
307
311
  __tt_pins_load
308
312
  __tt_dir_sync
309
313
  }
@@ -316,11 +320,17 @@ __tt_sync() {
316
320
  __tt_shown "$REPLY" && __tt_reload
317
321
  }
318
322
 
319
- __tt_precmd() { __tt_sync }
323
+ __tt_precmd() {
324
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
325
+ __tt_sync
326
+ }
320
327
 
321
328
  __tt_focus_on() { printf '\e[?1004h' }
322
329
 
323
- __tt_preexec() { printf '\e[?1004l' }
330
+ __tt_preexec() {
331
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
332
+ printf '\e[?1004l'
333
+ }
324
334
 
325
335
  __tt_rewear() {
326
336
  local REPLY
@@ -332,6 +342,7 @@ __tt_rewear() {
332
342
  }
333
343
 
334
344
  __tt_focus() {
345
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
335
346
  __tt_fresh
336
347
  if (( TTHEME_TMUX )); then
337
348
  __tt_rewear
@@ -340,17 +351,25 @@ __tt_focus() {
340
351
  fi
341
352
  }
342
353
 
343
- __tt_mux() { [[ ${${(z)3}[1]:t} == tmux ]] && TTHEME_MUXED=1 }
354
+ __tt_mux() {
355
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
356
+ [[ ${${(z)3}[1]:t} == tmux ]] && TTHEME_MUXED=1
357
+ }
344
358
 
345
359
  __tt_unmux() {
360
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
346
361
  (( TTHEME_MUXED )) || return 0
347
362
  __tt_rewear force
348
363
  TTHEME_MUXED=0
349
364
  }
350
365
 
351
- __tt_blur() { : }
366
+ __tt_blur() {
367
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
368
+ :
369
+ }
352
370
 
353
371
  __tt_line_init() {
372
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
354
373
  (( TTHEME_RECHECK )) && __tt_recheck
355
374
  __tt_typed
356
375
  (( TTHEME_FOCUS )) && __tt_focus_on
@@ -385,56 +404,673 @@ __tt_pins_write() {
385
404
  return 0
386
405
  fi
387
406
  mkdir -p ${TTHEME_PINS_FILE:h} || return 1
388
- print -rl -- "${lines[@]}" > $TTHEME_PINS_FILE || return 1
407
+ __tt_put $TTHEME_PINS_FILE "${lines[@]}" || return 1
389
408
  TTHEME_PINS_RAW="$(<$TTHEME_PINS_FILE)"
390
409
  }
391
410
 
411
+ __tt_pin_base() { REPLY=${${${1%/\*\*}:-/}:a} }
412
+
413
+ __tt_dir_label() {
414
+ if [[ $1 == "${HOME:a}" ]]; then
415
+ REPLY='~'
416
+ else
417
+ __tt_tilde "$1"
418
+ fi
419
+ }
420
+
421
+ __tt_pin_scope() {
422
+ if [[ $1 == *"/**" ]]; then
423
+ REPLY="and below"
424
+ else
425
+ REPLY="this directory"
426
+ fi
427
+ }
428
+
429
+ __tt_repo() {
430
+ local at=${1:a}
431
+ while [[ $at != / ]]; do
432
+ [[ -e $at/.git ]] && break
433
+ at=${at:h}
434
+ done
435
+ [[ $at != / && $at != "${HOME:a}" ]] || return 1
436
+ REPLY=$at
437
+ }
438
+
439
+ __tt_pin_cover() {
440
+ local k b best=""
441
+ local -i len=-1
442
+ for k in ${(k)TTHEME_PINS}; do
443
+ [[ $k == /* && $k == *"/**" && -n ${TTHEME_PALETTE[$TTHEME_PINS[$k]]} ]] || continue
444
+ __tt_pin_base "$k"
445
+ b=$REPLY
446
+ [[ $1 == "$b" || $1 == "${b%/}"/* ]] || continue
447
+ (( ${#b} > len )) && { best=$k len=${#b} }
448
+ done
449
+ [[ -n $best ]] || return 1
450
+ REPLY=$best
451
+ }
452
+
453
+ __tt_pin_scopes() {
454
+ local d=${1:a} k
455
+ plabel=(" This directory " " And below ") pkeys=("$d" "${d%/}/**") pkdef=2
456
+ if __tt_repo "$d" && [[ $REPLY != "$d" ]]; then
457
+ plabel+=(" Repository ") pkeys+=("$REPLY/**")
458
+ fi
459
+ for k in ${(k)TTHEME_PINS}; do
460
+ [[ $k == /* ]] || continue
461
+ __tt_pin_base "$k"
462
+ [[ $REPLY == "$d" ]] || continue
463
+ if [[ $k == *"/**" ]]; then pkdef=2; else pkdef=1; fi
464
+ done
465
+ }
466
+
392
467
  __tt_pin_save() {
393
- local name=$1 key=$PWD REPLY
394
- (( $2 == 2 )) && key="$PWD/**"
395
- unset "TTHEME_PINS[$PWD]" "TTHEME_PINS[$PWD/**]"
468
+ local name=$1 key=$2 k base spec REPLY
469
+ __tt_pin_base "$key"
470
+ base=$REPLY
471
+ for k in ${(k)TTHEME_PINS}; do
472
+ [[ $k == /* ]] || continue
473
+ __tt_pin_base "$k"
474
+ [[ $REPLY == "$base" ]] && unset "TTHEME_PINS[$k]"
475
+ done
396
476
  TTHEME_PINS[$key]=$name
397
477
  if ! __tt_pins_write; then
398
478
  print -u2 "ttheme pin: could not write $TTHEME_PINS_FILE"
399
479
  return 1
400
480
  fi
401
481
  [[ -n $TTHEME_PIN ]] || TTHEME_BASE_SPEC=$3
402
- TTHEME_PIN=$key TTHEME_PIN_SPEC=${TTHEME_PALETTE[$name]}
403
- __tt_tilde "$key"
482
+ if __tt_dir_rule "$PWD"; then
483
+ spec=${TTHEME_PALETTE[$TTHEME_PINS[$REPLY]]}
484
+ TTHEME_PIN=$REPLY TTHEME_PIN_SPEC=$spec
485
+ else
486
+ spec=$TTHEME_BASE_SPEC
487
+ TTHEME_PIN="" TTHEME_PIN_SPEC=""
488
+ fi
489
+ if [[ $spec != "$TTHEME_SPEC" ]]; then
490
+ if [[ -n $spec ]]; then
491
+ __tt_apply "$spec"
492
+ else
493
+ __tt_osc_reset
494
+ fi
495
+ TTHEME_SPEC=$spec
496
+ fi
497
+ __tt_dir_label "$base"
498
+ k=$REPLY
499
+ __tt_pin_scope "$key"
404
500
  if __tt_color; then
405
- printf '\033[2mPinned · %s → %s\033[0m\n' "$REPLY" "$name"
501
+ printf '\033[2mPinned · %s → %s · %s\033[0m\n' "$name" "$k" "$REPLY"
406
502
  else
407
- print -r -- "Pinned · $REPLY → $name"
503
+ print -r -- "Pinned · $name → $k · $REPLY"
504
+ fi
505
+ if [[ $TTHEME_PIN != "$key" ]]; then
506
+ __tt_here_line Here
507
+ print -r -- "$REPLY"
408
508
  fi
409
509
  }
410
510
 
411
- __tt_unpin() {
412
- local key REPLY
413
- for key in "$PWD" "$PWD/**"; do
414
- [[ -n ${TTHEME_PINS[$key]} ]] || continue
415
- unset "TTHEME_PINS[$key]"
416
- if ! __tt_pins_write; then
417
- print -u2 "ttheme unpin: could not write $TTHEME_PINS_FILE"
418
- return 1
419
- fi
420
- __tt_dir_sync
421
- __tt_tilde "$key"
511
+ __tt_unpin_drop() {
512
+ local k REPLY where
513
+ local -a gone=()
514
+ for k in "$@"; do
515
+ [[ -n ${TTHEME_PINS[$k]} ]] || continue
516
+ __tt_pin_base "$k"
517
+ __tt_dir_label "$REPLY"
518
+ where=$REPLY
519
+ __tt_pin_scope "$k"
520
+ gone+=("Unpinned · ${TTHEME_PINS[$k]} on $where · $REPLY")
521
+ unset "TTHEME_PINS[$k]"
522
+ done
523
+ if ! __tt_pins_write; then
524
+ print -u2 "ttheme unpin: could not write $TTHEME_PINS_FILE"
525
+ return 1
526
+ fi
527
+ __tt_dir_sync
528
+ for k in $gone; do
422
529
  if __tt_color; then
423
- printf '\033[2mUnpinned · %s\033[0m\n' "$REPLY"
530
+ print -r -- $'\e[2m'"$k"$'\e[0m'
424
531
  else
425
- print -r -- "Unpinned · $REPLY"
532
+ print -r -- "$k"
426
533
  fi
427
- return 0
428
534
  done
429
- if __tt_dir_rule "$PWD"; then
430
- __tt_tilde "$REPLY"
431
- print -u2 "ttheme unpin: nothing pinned here — $REPLY covers this directory"
535
+ __tt_here_line Here
536
+ print -r -- "$REPLY"
537
+ }
538
+
539
+ __tt_unpin_then() {
540
+ local k
541
+ local -A TTHEME_PINS=("${(@kv)TTHEME_PINS}")
542
+ for k in "${@:2}"; do
543
+ unset "TTHEME_PINS[$k]"
544
+ done
545
+ __tt_here_fit "Then here" $1
546
+ }
547
+
548
+ __tt_key() {
549
+ local c seq=""
550
+ REPLY=""
551
+ read -sk 1 c || return 1
552
+ case $c in
553
+ $'\e')
554
+ if ! read -sk 1 -t 0.05 c; then
555
+ REPLY=esc
556
+ elif [[ $c == '[' || $c == O ]]; then
557
+ while read -sk 1 -t 0.05 c; do
558
+ seq+=$c
559
+ [[ $c == [A-Za-z~] ]] && break
560
+ done
561
+ case $seq in
562
+ C) REPLY=right ;;
563
+ D) REPLY=left ;;
564
+ Z) REPLY=stab ;;
565
+ *) REPLY=nop ;;
566
+ esac
567
+ else
568
+ REPLY=nop
569
+ fi
570
+ ;;
571
+ $'\t') REPLY=tab ;;
572
+ $'\r'|$'\n') REPLY=enter ;;
573
+ q|$'\x03') REPLY=esc ;;
574
+ *) REPLY=$c ;;
575
+ esac
576
+ }
577
+
578
+ __tt_unpin_block() {
579
+ local b=$'\e[1m' d=$'\e[2m' z=$'\e[0m' on=$'\e[7;1m' bar plain keys top k home=${HOME:a} here=${PWD:a} htip="" REPLY
580
+ local -A own=() below=() parent=() mark=() tstrip=()
581
+ local -a roots=() lp=() lpw=() ll=() lat=() lpal=() lnote=() lflag=() reply gone=(${(f)drops[$1]})
582
+ local -i i width=$(( (COLUMNS > 0 ? COLUMNS : 80) - 1 )) room=$(( (LINES > 0 ? LINES : 24) - 3 ))
583
+ (( color )) || b= d= z= on=
584
+ if (( color )); then
585
+ bar=$'\e[7;1m UNPIN \e[0m ' plain=" UNPIN "
432
586
  else
587
+ bar="[UNPIN]" plain=$bar
588
+ fi
589
+ for (( i = 1; i <= ${#lab}; i++ )); do
590
+ plain+=" ${lab[i]}"
591
+ if (( i != $1 )); then
592
+ bar+=" "$d${lab[i]}$z
593
+ elif (( color )); then
594
+ bar+=" "$on${lab[i]}$z
595
+ else
596
+ bar+=" [${${lab[i]# }% }]"
597
+ fi
598
+ done
599
+ keys="←→ choose · enter unpin · esc keep"
600
+ (( ${(m)#plain} + 3 + ${(m)#keys} <= width )) && bar+=" "$d$keys$z
601
+ __tt_clip "$bar" $width
602
+ bar=$REPLY
603
+ for k in $gone; do
604
+ __tt_pin_base "$k"
605
+ mark[$REPLY]=drop
606
+ done
607
+ top=${PWD:a}
608
+ [[ -n $cover ]] && __tt_pin_base "$cover" && top=$REPLY
609
+ __tt_map_build "$top"
610
+ __tt_map_fit $width
611
+ (( ${#reply} > room )) && reply=("${(@)reply[1,room-1]}" " …")
612
+ block=("$bar" "${reply[@]}")
613
+ __tt_unpin_then $width $gone
614
+ block+=("${reply[@]}")
615
+ }
616
+
617
+ __tt_unpin_paint() {
618
+ local out=$'\r'
619
+ (( $1 > 1 )) && out+=$'\e['$(( $1 - 1 ))'A'
620
+ out+=${(pj:\e[K\n:)block}$'\e[K\e[J'
621
+ print -rn -- "$out"
622
+ }
623
+
624
+ __tt_unpin_choose() {
625
+ local tty="" REPLY
626
+ local -i n=0 at=1
627
+ local -a block=()
628
+ chosen=0
629
+ {
630
+ tty=$(stty -g 2>/dev/null && stty -echo -icanon min 1 time 0 2>/dev/null)
631
+ print -rn -- $'\e[?25l'
632
+ while :; do
633
+ __tt_unpin_block $at
634
+ __tt_unpin_paint $n
635
+ n=${#block}
636
+ __tt_key || break
637
+ case $REPLY in
638
+ left|stab) at=$(( (at + ${#lab} - 2) % ${#lab} + 1 )) ;;
639
+ right|tab) at=$(( at % ${#lab} + 1 )) ;;
640
+ enter) chosen=$at; break ;;
641
+ esc) break ;;
642
+ esac
643
+ done
644
+ } always {
645
+ REPLY=$'\r'
646
+ (( n > 1 )) && REPLY+=$'\e['$(( n - 1 ))'A'
647
+ print -rn -- "$REPLY"$'\e[J\e[?25h'
648
+ [[ -n $tty ]] && stty "$tty" 2>/dev/null
649
+ }
650
+ (( chosen ))
651
+ }
652
+
653
+ __tt_unpin_options() {
654
+ local d=${PWD:a} k REPLY
655
+ local -a both
656
+ mine=() under=() lab=() drops=() cover=""
657
+ for k in ${(k)TTHEME_PINS}; do
658
+ [[ $k == /* ]] || continue
659
+ __tt_pin_base "$k"
660
+ if [[ $REPLY == "$d" ]]; then
661
+ mine+=("$k")
662
+ elif [[ $REPLY == "${d%/}"/* ]]; then
663
+ under+=("$k")
664
+ fi
665
+ done
666
+ [[ $d != / ]] && __tt_pin_cover "${d:h}" && cover=$REPLY
667
+ if (( ${#mine} )); then
668
+ lab+=(" This directory ") drops+=("${(pj:\n:)mine}")
669
+ fi
670
+ if (( ${#under} )); then
671
+ both=($mine $under)
672
+ lab+=(" And below ") drops+=("${(pj:\n:)both}")
673
+ fi
674
+ if [[ -n $cover ]]; then
675
+ __tt_pin_base "$cover"
676
+ __tt_dir_label "$REPLY"
677
+ lab+=(" $REPLY ") drops+=("$cover")
678
+ fi
679
+ return 0
680
+ }
681
+
682
+ __tt_unpin() {
683
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
684
+ local d k cover="" REPLY
685
+ local -a mine=() under=() lab=() drops=()
686
+ local -i chosen=0 color=0
687
+ __tt_color && color=1
688
+ __tt_pins_load
689
+ if (( $# )); then
690
+ d=$1
691
+ [[ $d == \~ || $d == \~/* ]] && d=$HOME${d#\~}
692
+ d=${d:a}
693
+ for k in ${(k)TTHEME_PINS}; do
694
+ [[ $k == /* ]] || continue
695
+ __tt_pin_base "$k"
696
+ [[ $REPLY == "$d" ]] && mine+=("$k")
697
+ done
698
+ if (( ${#mine} )); then
699
+ __tt_unpin_drop $mine
700
+ return
701
+ fi
702
+ __tt_dir_label "$d"
703
+ k=$REPLY
704
+ if __tt_pin_cover "$d"; then
705
+ __tt_pin_base "$REPLY"
706
+ __tt_dir_label "$REPLY"
707
+ print -u2 -r -- "ttheme unpin: nothing pinned to $k — the pin on $REPLY and below covers it"
708
+ else
709
+ print -u2 -r -- "ttheme unpin: nothing pinned to $k"
710
+ fi
711
+ return 1
712
+ fi
713
+ __tt_unpin_options
714
+ if (( ! ${#lab} )); then
433
715
  print -u2 "ttheme unpin: nothing pinned here"
716
+ return 1
717
+ fi
718
+ if (( ${#lab} == 1 && ${#mine} )); then
719
+ __tt_unpin_drop $mine
720
+ return
434
721
  fi
722
+ if [[ ! -t 0 || ! -t 1 ]]; then
723
+ if (( ${#mine} )); then
724
+ __tt_unpin_drop $mine
725
+ return
726
+ fi
727
+ if [[ -n $cover ]]; then
728
+ __tt_pin_base "$cover"
729
+ __tt_dir_label "$REPLY"
730
+ print -u2 -r -- "ttheme unpin: nothing pinned here — the pin on $REPLY and below covers it: ttheme unpin $REPLY"
731
+ elif (( ${#under} == 1 )); then
732
+ print -u2 -r -- "ttheme unpin: nothing pinned here — 1 pin below it: ttheme pins maps it"
733
+ else
734
+ print -u2 -r -- "ttheme unpin: nothing pinned here — ${#under} pins below it: ttheme pins maps them"
735
+ fi
736
+ return 1
737
+ fi
738
+ __tt_unpin_choose || return 1
739
+ __tt_unpin_drop ${(f)drops[chosen]}
740
+ }
741
+
742
+ __tt_clip() {
743
+ local c out=""
744
+ local -i i n=${#1} w=0 cw
745
+ REPLY=$1
746
+ for (( i = 1; i <= n; i++ )); do
747
+ c=${1[i]}
748
+ if [[ $c == $'\e' ]]; then
749
+ while (( i < n )) && [[ ${1[i]} != [A-Za-z] || ${1[i]} == $'\e' ]]; do (( i++ )); done
750
+ continue
751
+ fi
752
+ (( w += ${(m)#c} ))
753
+ done
754
+ (( w <= $2 )) && return 0
755
+ w=0
756
+ for (( i = 1; i <= n; i++ )); do
757
+ c=${1[i]}
758
+ if [[ $c == $'\e' ]]; then
759
+ out+=$c
760
+ while (( i < n )); do
761
+ (( i++ ))
762
+ out+=${1[i]}
763
+ [[ ${1[i]} == [A-Za-z] ]] && break
764
+ done
765
+ continue
766
+ fi
767
+ cw=${(m)#c}
768
+ (( w + cw > $2 - 1 )) && break
769
+ out+=$c
770
+ (( w += cw ))
771
+ done
772
+ REPLY=$out…
773
+ [[ $1 == *$'\e'* ]] && REPLY+=$'\e[0m'
435
774
  return 1
436
775
  }
437
776
 
777
+ __tt_map_tip() {
778
+ local -a p=(${=TTHEME_PALETTE[$1]})
779
+ local c=${p[3]#\#}
780
+ REPLY=""
781
+ (( color && ${#p} >= 20 )) || return 0
782
+ printf -v REPLY '\e[38;2;%d;%d;%dm' $((16#${c:0:2})) $((16#${c:2:2})) $((16#${c:4:2}))
783
+ }
784
+
785
+ __tt_map_chip() {
786
+ local -a p=(${=TTHEME_PALETTE[$1]})
787
+ local bg=${p[1]#\#} fg=${p[2]#\#}
788
+ if (( ! color )); then
789
+ printf -v REPLY '%-*s' $2 "$1"
790
+ elif (( ${#p} >= 20 )); then
791
+ printf -v REPLY '\e[48;2;%d;%d;%d;38;2;%d;%d;%dm %-*s \e[0m' $((16#${bg:0:2})) $((16#${bg:2:2})) $((16#${bg:4:2})) \
792
+ $((16#${fg:0:2})) $((16#${fg:2:2})) $((16#${fg:4:2})) $2 "$1"
793
+ else
794
+ printf -v REPLY '\e[2m %-*s \e[0m' $2 "$1"
795
+ fi
796
+ }
797
+
798
+ __tt_here_line() {
799
+ local rule name where
800
+ if ! __tt_dir_rule "$PWD"; then
801
+ reply=("$1 · no pin" "the tab's own palette")
802
+ REPLY="$1 · no pin — the tab's own palette"
803
+ return 0
804
+ fi
805
+ rule=$REPLY name=$TTHEME_PINS[$REPLY]
806
+ __tt_pin_base "$rule"
807
+ if [[ ${REPLY:A} == "${PWD:A}" ]]; then
808
+ where="this directory"
809
+ else
810
+ __tt_dir_label "$REPLY"
811
+ where=$REPLY
812
+ fi
813
+ [[ $rule == *"/**" ]] && where+=" and below"
814
+ __tt_map_chip "$name" ${#name}
815
+ reply=("$1 · $REPLY" "pinned to $where")
816
+ REPLY="$1 · $REPLY pinned to $where"
817
+ }
818
+
819
+ __tt_here_fit() {
820
+ __tt_here_line "$1"
821
+ __tt_clip "$REPLY" $2 && { reply=("$REPLY"); return 0 }
822
+ __tt_clip "${reply[1]}" $2
823
+ reply[1]=$REPLY
824
+ __tt_clip " ${reply[2]}" $2
825
+ reply[2]=$REPLY
826
+ }
827
+
828
+ __tt_map_under() {
829
+ local b name
830
+ local -i len=-1
831
+ REPLY=""
832
+ for b in ${(k)below}; do
833
+ name=${below[$b]}
834
+ [[ -n ${TTHEME_PALETTE[$name]} && ( $1 == "$b" || $1 == "${b%/}"/* ) ]] || continue
835
+ (( ${#b} > len )) && { REPLY=$name len=${#b} }
836
+ done
837
+ }
838
+
839
+ __tt_map_link() {
840
+ local at=$1 root=/
841
+ if [[ -n $top ]]; then
842
+ root=$top
843
+ elif [[ $at == "$home" || $at == "$home"/* ]]; then
844
+ root=$home
845
+ fi
846
+ (( ${roots[(Ie)$root]} )) || roots+=("$root")
847
+ while [[ $at != "$root" && -z ${parent[$at]} ]]; do
848
+ parent[$at]=${at:h}
849
+ at=${at:h}
850
+ done
851
+ }
852
+
853
+ __tt_map_chain() {
854
+ local at=$1 lbl=$2
855
+ local -a ks
856
+ while [[ -z ${own[$at]}${below[$at]} && $at != "$here" ]]; do
857
+ ks=(${(k)parent[(Re)$at]})
858
+ (( ${#ks} == 1 )) || break
859
+ at=$ks[1] lbl+=/${at:t}
860
+ done
861
+ reply=("$at" "${lbl:-/}")
862
+ }
863
+
864
+ __tt_map_line() {
865
+ local at=$1 name=${own[$1]:-${below[$1]}} note="" flag=""
866
+ if [[ -n $name ]]; then
867
+ if [[ -n ${own[$at]} ]]; then
868
+ note="this directory"
869
+ [[ -n ${below[$at]} ]] && note+=" · ${below[$at]} below"
870
+ else
871
+ note="and below"
872
+ fi
873
+ [[ -n ${TTHEME_PALETTE[$name]} ]] || flag="not installed"
874
+ [[ -d $at ]] || flag+="${flag:+ · }no such directory"
875
+ fi
876
+ lp+=("$2") lpw+=($3) ll+=("$4") lat+=("$at") lpal+=("$name") lnote+=("$note") lflag+=("$flag")
877
+ }
878
+
879
+ __tt_map_walk() {
880
+ local at=$1 pre=$2 tip z=$'\e[0m' REPLY
881
+ local -i i
882
+ local -a ks=(${(oi)${(k)parent[(Re)$1]}}) reply
883
+ (( color )) || z=
884
+ __tt_map_under "$at"
885
+ __tt_map_tip "$REPLY"
886
+ tip=$REPLY
887
+ (( color )) && [[ -z $tip ]] && tip=$'\e[2m'
888
+ for (( i = 1; i <= ${#ks}; i++ )); do
889
+ __tt_map_chain "$ks[i]" "${ks[i]:t}"
890
+ if (( i < ${#ks} )); then
891
+ __tt_map_line "$reply[1]" "$pre$tip├─ $z" $(( $3 + 3 )) "$reply[2]"
892
+ __tt_map_walk "$reply[1]" "$pre$tip│ $z" $(( $3 + 3 ))
893
+ else
894
+ __tt_map_line "$reply[1]" "$pre$tip└─ $z" $(( $3 + 3 )) "$reply[2]"
895
+ __tt_map_walk "$reply[1]" "$pre " $(( $3 + 3 ))
896
+ fi
897
+ done
898
+ }
899
+
900
+ __tt_map_build() {
901
+ local top=$1 k base root REPLY
902
+ local -a order=()
903
+ own=() below=() parent=() roots=() lp=() lpw=() ll=() lat=() lpal=() lnote=() lflag=() htip=""
904
+ for k in ${(k)TTHEME_PINS}; do
905
+ [[ $k == /* ]] || continue
906
+ __tt_pin_base "$k"
907
+ base=$REPLY
908
+ if [[ $k == *"/**" ]]; then
909
+ below[$base]=$TTHEME_PINS[$k]
910
+ else
911
+ own[$base]=$TTHEME_PINS[$k]
912
+ fi
913
+ [[ -z $top || $base == "$top" || $base == "${top%/}"/* ]] && __tt_map_link "$base"
914
+ done
915
+ [[ -z $top || $here == "$top" || $here == "${top%/}"/* ]] && __tt_map_link "$here"
916
+ __tt_dir_rule "$PWD" && __tt_map_tip "$TTHEME_PINS[$REPLY]" && htip=$REPLY
917
+ if [[ -n $top ]]; then
918
+ order=("$top")
919
+ else
920
+ (( ${roots[(Ie)$home]} )) && order+=("$home")
921
+ [[ $home != / ]] && (( ${roots[(Ie)/]} )) && order+=(/)
922
+ fi
923
+ for root in $order; do
924
+ if [[ $root == / ]]; then
925
+ __tt_map_chain / ""
926
+ else
927
+ __tt_dir_label "$root"
928
+ __tt_map_chain "$root" "$REPLY"
929
+ fi
930
+ __tt_map_line "$reply[1]" "" 0 "$reply[2]"
931
+ __tt_map_walk "$reply[1]" "" 0
932
+ done
933
+ }
934
+
935
+ __tt_map_fit() {
936
+ local b=$'\e[1m' d=$'\e[2m' y=$'\e[33m' r=$'\e[31m' z=$'\e[0m' lab line tag tip note REPLY
937
+ local -i width=$1 i lvl maxw namew need wr cap
938
+ local -a lw=() rw=() cut=()
939
+ (( color )) || b= d= y= r= z=
940
+ reply=()
941
+ for (( i = 1; i <= ${#ll}; i++ )); do
942
+ lw+=($(( lpw[i] + ${(m)#ll[i]} )))
943
+ [[ ${lat[i]} == "$here" ]] && (( lw[i] += 7 ))
944
+ (( ${#lpal[i]} > namew )) && namew=${#lpal[i]}
945
+ done
946
+ for (( lvl = 0; lvl < 4; lvl++ )); do
947
+ rw=() maxw=0 need=0
948
+ for (( i = 1; i <= ${#ll}; i++ )); do
949
+ wr=0
950
+ if [[ -n $lpal[i] ]]; then
951
+ wr=$(( 2 + namew + 2 * color ))
952
+ (( lvl == 0 && color )) && (( wr += 13 ))
953
+ note=${lnote[i]}
954
+ (( lvl == 2 )) && note=${${note//this directory/only}//and below/below}
955
+ (( lvl < 3 )) && (( wr += 2 + ${(m)#note} ))
956
+ [[ -n $lflag[i] ]] && (( wr += 3 + ${(m)#lflag[i]} ))
957
+ tag=${mark[$lat[i]]}
958
+ [[ $tag == new ]] && (( wr += 5 ))
959
+ [[ $tag == drop ]] && (( wr += 9 ))
960
+ (( lw[i] > maxw )) && maxw=$lw[i]
961
+ fi
962
+ rw+=($wr)
963
+ done
964
+ for (( i = 1; i <= ${#ll}; i++ )); do
965
+ if (( rw[i] )); then
966
+ (( maxw + rw[i] > need )) && need=$(( maxw + rw[i] ))
967
+ else
968
+ (( lw[i] > need )) && need=$lw[i]
969
+ fi
970
+ done
971
+ (( need <= width )) && break
972
+ done
973
+ (( lvl > 3 )) && lvl=3
974
+ cut=()
975
+ if (( need > width )); then
976
+ cap=0
977
+ for (( i = 1; i <= ${#ll}; i++ )); do
978
+ (( rw[i] > cap )) && cap=$rw[i]
979
+ done
980
+ cap=$(( width - cap ))
981
+ maxw=0
982
+ for (( i = 1; i <= ${#ll}; i++ )); do
983
+ wr=$(( rw[i] ? cap : width ))
984
+ lab=${ll[i]}
985
+ if (( lw[i] > wr )); then
986
+ while [[ -n $lab ]] && (( lw[i] - ${(m)#ll[i]} + ${(m)#lab} + 1 > wr )); do
987
+ lab=${lab[2,-1]}
988
+ done
989
+ lab=…$lab
990
+ lw[i]=$(( lw[i] - ${(m)#ll[i]} + ${(m)#lab} ))
991
+ fi
992
+ cut+=("$lab")
993
+ (( rw[i] && lw[i] > maxw )) && maxw=$lw[i]
994
+ done
995
+ else
996
+ cut=("${ll[@]}")
997
+ fi
998
+ for (( i = 1; i <= ${#ll}; i++ )); do
999
+ if [[ -n $lpal[i] ]]; then
1000
+ line=${lp[i]}$b${cut[i]}$z
1001
+ else
1002
+ line=${lp[i]}$d${cut[i]}$z
1003
+ fi
1004
+ [[ ${lat[i]} == "$here" ]] && line+=" "$htip$b"← here"$z
1005
+ if [[ -n $lpal[i] ]]; then
1006
+ __tt_map_chip "$lpal[i]" $namew
1007
+ line+=${(l:maxw - lw[i] + 2:: :)}$REPLY
1008
+ if (( lvl == 0 && color )); then
1009
+ if [[ -n ${TTHEME_PALETTE[$lpal[i]]} ]]; then
1010
+ [[ -n ${tstrip[$lpal[i]]} ]] || __tt_pv_strip "$lpal[i]"
1011
+ line+=" "${tstrip[$lpal[i]]}
1012
+ else
1013
+ line+=${(l:13:: :)}
1014
+ fi
1015
+ fi
1016
+ note=${lnote[i]}
1017
+ (( lvl == 2 )) && note=${${note//this directory/only}//and below/below}
1018
+ (( lvl < 3 )) && line+=" "$d$note$z
1019
+ [[ -n $lflag[i] ]] && line+=$y" · "$lflag[i]$z
1020
+ tag=${mark[$lat[i]]}
1021
+ if [[ $tag == new ]]; then
1022
+ __tt_map_tip "$lpal[i]"
1023
+ line+=" "$b$REPLY"new"$z
1024
+ elif [[ $tag == drop ]]; then
1025
+ line+=" "$r"✕ unpin"$z
1026
+ fi
1027
+ fi
1028
+ while [[ $line == *' ' ]]; do line=${line% }; done
1029
+ reply+=("$line")
1030
+ done
1031
+ }
1032
+
1033
+ __tt_map_tree() {
1034
+ local home=${HOME:a} here=${PWD:a} htip="" REPLY
1035
+ local -A own=() below=() parent=() mark=() tstrip=()
1036
+ local -a roots=() lp=() lpw=() ll=() lat=() lpal=() lnote=() lflag=() reply
1037
+ local -i width=$(( COLUMNS > 0 ? COLUMNS : 80 ))
1038
+ __tt_map_build ""
1039
+ __tt_map_fit $width
1040
+ print -rl -- "${reply[@]}"
1041
+ print
1042
+ __tt_here_line Here
1043
+ __tt_clip "$REPLY" $width
1044
+ print -r -- "$REPLY"
1045
+ __tt_tilde "$TTHEME_PINS_FILE"
1046
+ if (( color )); then
1047
+ __tt_clip $'\e[2m'"ttheme pin picks one here · ttheme unpin drops one · $REPLY"$'\e[0m' $width
1048
+ else
1049
+ __tt_clip "ttheme pin picks one here · ttheme unpin drops one · $REPLY" $width
1050
+ fi
1051
+ print -r -- "$REPLY"
1052
+ }
1053
+
1054
+ __tt_pins_map() {
1055
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
1056
+ local k REPLY
1057
+ local -i color=0
1058
+ __tt_pins_load
1059
+ if [[ ! -t 1 ]]; then
1060
+ for k in ${(oi)${(k)TTHEME_PINS}}; do
1061
+ __tt_tilde "$k"
1062
+ print -r -- "$REPLY"$'\t'"$TTHEME_PINS[$k]"
1063
+ done
1064
+ return 0
1065
+ fi
1066
+ if (( ! ${#TTHEME_PINS} )); then
1067
+ print -r -- 'No pins yet — `ttheme pin` picks a palette for this directory'
1068
+ return 0
1069
+ fi
1070
+ __tt_color && color=1
1071
+ __tt_map_tree
1072
+ }
1073
+
438
1074
  __tt_keep() {
439
1075
  local note
440
1076
  note=$(__tt_cli default "$1") || return 1
@@ -577,7 +1213,7 @@ __tt_config() {
577
1213
  local blur
578
1214
  if [[ ! -e $TTHEME_CONFIG ]]; then
579
1215
  mkdir -p ${TTHEME_CONFIG:h} || return 1
580
- print -r -- "$TTHEME_CONFIG_TEMPLATE" > $TTHEME_CONFIG
1216
+ __tt_put $TTHEME_CONFIG "$TTHEME_CONFIG_TEMPLATE"
581
1217
  fi
582
1218
  blur=${(M)${(@f)"$(<$TTHEME_CONFIG)"}:#': ${TTHEME_BG_BLUR:='*}
583
1219
  ${=${VISUAL:-${EDITOR:-vi}}} $TTHEME_CONFIG || return
@@ -607,7 +1243,7 @@ __tt_config_write() {
607
1243
  local -i i
608
1244
  if [[ ! -e $TTHEME_CONFIG ]]; then
609
1245
  mkdir -p ${TTHEME_CONFIG:h} 2>/dev/null || return 1
610
- print -r -- "$TTHEME_CONFIG_TEMPLATE" 2>/dev/null > $TTHEME_CONFIG || return 1
1246
+ __tt_put $TTHEME_CONFIG "$TTHEME_CONFIG_TEMPLATE" 2>/dev/null || return 1
611
1247
  fi
612
1248
  [[ -r $TTHEME_CONFIG && -w $TTHEME_CONFIG ]] || return 1
613
1249
  for line in "${(@f)$(<$TTHEME_CONFIG)}"; do
@@ -631,7 +1267,7 @@ __tt_config_write() {
631
1267
  __tt_config_line $name ${want[$name]}
632
1268
  out+=("" "$doc" "$REPLY")
633
1269
  done
634
- print -rl -- "${out[@]}" 2>/dev/null > $TTHEME_CONFIG
1270
+ __tt_put $TTHEME_CONFIG "${out[@]}" 2>/dev/null
635
1271
  }
636
1272
 
637
1273
  __tt_resolve() {
@@ -979,7 +1615,7 @@ __tt_pv_row() {
979
1615
  __tt_pv_bg_title() {
980
1616
  local by=${bgby[$tpick]} ref=${bgfrom[$tpick]} url=${bgurl[$tpick]} mark="" sgr=""
981
1617
  local -i room=$1
982
- [[ -n $ref && $url == http(s|)://* && $TTHEME_ADAPTER == (ghostty|kitty|wezterm|alacritty|iterm2|windows-terminal|foot) ]] && mark="⧉ "
1618
+ [[ -n $ref && $url == http(s|)://* ]] && (( ${TTHEME_LINKS[(Ie)$TTHEME_ADAPTER]} )) && mark="⧉ "
983
1619
  REPLY=Background${by:+ · $by}${ref:+ · $mark$ref}$2
984
1620
  (( ${#REPLY} > room )) && REPLY=Background${by:+ · $by}
985
1621
  (( ${#REPLY} > room )) && REPLY=Background
@@ -1009,19 +1645,23 @@ __tt_pv_foot() {
1009
1645
  printf -v y '\e[38;2;%d;%d;%dm' $((16#${hex:0:2})) $((16#${hex:2:2})) $((16#${hex:4:2}))
1010
1646
  fi
1011
1647
  if (( help )); then
1012
- badge=KEYS right=$b"? esc"$z$d" close"$z
1648
+ badge=HELP right=$b"? esc"$z$d" close"$z
1013
1649
  elif [[ -n $pick ]]; then
1014
- local a=$plabel[1] c=$plabel[2]
1015
- if (( color )); then
1016
- if (( pk == 1 )); then a=$on$a$z c=$d$c$z; else a=$d$a$z c=$on$c$z; fi
1017
- else
1018
- if (( pk == 1 )); then a="[${${a# }% }]"; else c="[${${c# }% }]"; fi
1019
- fi
1020
- badge=APPLY lead="$pick → $a $c"
1650
+ badge='PREVIEW (APPLY)' lead="$pick →"
1651
+ for (( i = 1; i <= ${#plabel}; i++ )); do
1652
+ if (( i != pk )); then
1653
+ lead+=" "$d${plabel[i]}$z
1654
+ elif (( color )); then
1655
+ lead+=" "$on${plabel[i]}$z
1656
+ else
1657
+ lead+=" [${${plabel[i]# }% }]"
1658
+ fi
1659
+ done
1021
1660
  if [[ $mode == pin ]]; then
1022
- __tt_tilde "$PWD"
1661
+ badge='PREVIEW (PIN)'
1662
+ __tt_pin_base "${pkeys[pk]}"
1663
+ __tt_dir_label "$REPLY"
1023
1664
  note=$REPLY
1024
- (( pk == 2 )) && note+="/**"
1025
1665
  elif (( pk == 1 )); then
1026
1666
  note="Until this tab closes"
1027
1667
  else
@@ -1030,7 +1670,7 @@ __tt_pv_foot() {
1030
1670
  kk=(enter) kl=(confirm)
1031
1671
  right=$b"esc"$z$d" back"$z
1032
1672
  elif [[ -n $tune ]]; then
1033
- badge=TUNE kk=(↑↓ ←→ '=' +) kl=(field step reset "reset all")
1673
+ badge='IMAGE EDIT' kk=(↑↓ ←→ '=' +) kl=(field step reset "reset all")
1034
1674
  if (( tf == 2 )); then
1035
1675
  kl[2]=move kk+=(1-9) kl+=(place)
1036
1676
  else
@@ -1046,7 +1686,8 @@ __tt_pv_foot() {
1046
1686
  badge=CONFIG kk=(↑↓ ←→ enter) kl=(setting value save)
1047
1687
  right=$b"esc"$z$d" undo"$z
1048
1688
  else
1049
- [[ -n $flt ]] && badge=FILTER
1689
+ badge=PREVIEW
1690
+ [[ -n $flt ]] && badge='PREVIEW (FILTER)'
1050
1691
  if (( ! ${#rval} )); then
1051
1692
  kk=(bksp) kl=(edit)
1052
1693
  elif [[ ${rtype[cur]} == (hdr|cat) ]]; then
@@ -1153,7 +1794,7 @@ __tt_pv_help() {
1153
1794
  x=$(( (pw - w) / 2 + 1 )) y=$(( (ph - hh) / 2 ))
1154
1795
  (( y < 4 )) && y=4
1155
1796
  blank=${(l:w:: :)}
1156
- out+=$'\e['$y';'$x'H'"╭─ Keys ${(l:$(( w - 9 ))::─:)}╮"
1797
+ out+=$'\e['$y';'$x'H'"╭─ Help ${(l:$(( w - 9 ))::─:)}╮"
1157
1798
  for (( r = y + 1; r < y + hh - 1; r++ )); do
1158
1799
  out+=$'\e['$r';'$x'H'$blank$'\e['$r';'$x'H│'$'\e['$r';'$(( x + w - 1 ))'H│'
1159
1800
  done
@@ -1470,10 +2111,51 @@ __tt_pv_flush() {
1470
2111
  printf '\e[1;%dH\e[?2026l' $(( 4 + ${(m)#flt} ))
1471
2112
  }
1472
2113
 
2114
+ __tt_pv_reach() {
2115
+ local key=${pkeys[pk]} base top k home=${HOME:a} here=${PWD:a} htip="" d=$'\e[2m' z=$'\e[0m' REPLY
2116
+ local -A own=() below=() parent=() mark=() tstrip=()
2117
+ local -a roots=() lp=() lpw=() ll=() lat=() lpal=() lnote=() lflag=() reply was=()
2118
+ local -A TTHEME_PINS=("${(@kv)TTHEME_PINS}")
2119
+ local -i room=$2
2120
+ (( color )) || d= z=
2121
+ (( room < 2 )) && room=2
2122
+ __tt_pin_base "$key"
2123
+ base=$REPLY
2124
+ for k in ${(k)TTHEME_PINS}; do
2125
+ [[ $k == /* ]] || continue
2126
+ __tt_pin_base "$k"
2127
+ [[ $REPLY == "$base" ]] || continue
2128
+ if [[ $k != "$key" || $TTHEME_PINS[$k] != "$pick" ]]; then
2129
+ __tt_pin_scope "$k"
2130
+ was+=("${TTHEME_PINS[$k]} · $REPLY")
2131
+ fi
2132
+ unset "TTHEME_PINS[$k]"
2133
+ done
2134
+ TTHEME_PINS[$key]=$pick
2135
+ top=$base
2136
+ [[ $base != / ]] && __tt_pin_cover "${base:h}" && __tt_pin_base "$REPLY" && top=$REPLY
2137
+ mark[$base]=new
2138
+ __tt_map_build "$top"
2139
+ __tt_map_fit $1
2140
+ (( ${#reply} > room )) && reply=("${(@)reply[1,room-1]}" " …")
2141
+ reach=("${reply[@]}" "")
2142
+ __tt_dir_label "$base"
2143
+ base=$REPLY
2144
+ for k in $was; do
2145
+ __tt_clip "${d}Replaces ${k% · *} on $base · ${k##* · }$z" $1
2146
+ reach+=("$REPLY")
2147
+ done
2148
+ __tt_pin_scope "$key"
2149
+ __tt_clip "→ $base · $REPLY" $(( $1 - ${(m)#pick} - 2 ))
2150
+ rsub=$REPLY
2151
+ __tt_here_fit "Then here" $1
2152
+ reach+=("${reply[@]}")
2153
+ }
2154
+
1473
2155
  __tt_pv_draw() {
1474
- local out line cnt ex ag="" acat="" ac="" sb="" dd="" zz="" state=on src="" nflt="" REPLY
2156
+ local out line cnt ex ag="" acat="" ac="" sb="" dd="" zz="" state=on src="" nflt="" rsub="" REPLY
1475
2157
  local -i lw sw split sc se=$(( pw - 2 )) h k i N=${#rval} mt=${#TTHEME_ORDER} wiped=0
1476
- local -a pk
2158
+ local -a held
1477
2159
  bgstrip=""
1478
2160
  if [[ -n $flt ]]; then
1479
2161
  __tt_pv_norm "$flt"
@@ -1485,6 +2167,15 @@ __tt_pv_draw() {
1485
2167
  h=$(( ph - 5 ))
1486
2168
  [[ -n $tune ]] && (( ! split )) && h=$(( ph - 14 ))
1487
2169
  (( conf && ! split )) && h=$(( ph - 12 ))
2170
+ reach=()
2171
+ if [[ -n $pick && $mode == pin ]]; then
2172
+ if (( split )); then
2173
+ __tt_pv_reach $sw $(( ph - 6 ))
2174
+ else
2175
+ __tt_pv_reach $se $(( ph - 13 ))
2176
+ h=$(( ph - 6 - ${#reach} ))
2177
+ fi
2178
+ fi
1488
2179
  (( h < 1 )) && h=1
1489
2180
  (( cur < top )) && top=$cur
1490
2181
  (( cur >= top + h )) && top=$(( cur - h + 1 ))
@@ -1580,8 +2271,13 @@ __tt_pv_draw() {
1580
2271
  __tt_pv_foot $(( split ? se : pw ))
1581
2272
  if (( split )); then
1582
2273
  if (( help && sw >= 48 )); then
1583
- __tt_pv_head 1 $sc $se Keys
2274
+ __tt_pv_head 1 $sc $se Help
1584
2275
  __tt_pv_help
2276
+ elif (( ${#reach} )); then
2277
+ __tt_pv_head 1 $sc $se "$pick" "$rsub"
2278
+ for (( k = 1; k <= ${#reach} && k + 2 < ph; k++ )); do
2279
+ out+=$'\e['$(( k + 2 ))';'$sc'H'${reach[k]}
2280
+ done
1585
2281
  elif [[ -n $tune ]]; then
1586
2282
  (( bgoff[$tpick] )) && state=off
1587
2283
  __tt_pv_bg_title $(( sw - ${#tune} - 6 ))
@@ -1601,14 +2297,19 @@ __tt_pv_draw() {
1601
2297
  fi
1602
2298
  (( help && sw < 48 )) && __tt_pv_help
1603
2299
  else
1604
- if [[ -n $tune ]]; then
2300
+ if (( ${#reach} )); then
2301
+ __tt_pv_head $(( ph - 1 - ${#reach} )) 1 $se "$pick" "$rsub"
2302
+ for (( k = 1; k <= ${#reach}; k++ )); do
2303
+ out+=$'\e['$(( ph - 1 - ${#reach} + k ))';1H'${reach[k]}
2304
+ done
2305
+ elif [[ -n $tune ]]; then
1605
2306
  (( bgoff[$tpick] )) && state=off
1606
- pk=(${=bgpics[$tune]})
2307
+ held=(${=bgpics[$tune]})
1607
2308
  src=""
1608
- if (( ${#pk} > 1 )); then
1609
- k=${pk[(Ie)${${tpick#$tune}#:}]}
1610
- (( k )) || k=${pk[(Ie)${bgact[$tune]}]}
1611
- src=" · $k/${#pk}"
2309
+ if (( ${#held} > 1 )); then
2310
+ k=${held[(Ie)${${tpick#$tune}#:}]}
2311
+ (( k )) || k=${held[(Ie)${bgact[$tune]}]}
2312
+ src=" · $k/${#held}"
1612
2313
  fi
1613
2314
  __tt_pv_bg_title $(( lw - ${#tune} - 6 )) "$src"
1614
2315
  __tt_pv_head $(( ph - 9 )) 1 $lw $tune "$REPLY" $state
@@ -1755,7 +2456,8 @@ __tt_pv_pick() {
1755
2456
  case $key in
1756
2457
  $'\x03') return 1 ;;
1757
2458
  esc) pick="" ;;
1758
- left|right|$'\t'|stab) pk=$(( 3 - pk )) ;;
2459
+ left|stab) pk=$(( (pk + ${#plabel} - 2) % ${#plabel} + 1 )) ;;
2460
+ right|$'\t') pk=$(( pk % ${#plabel} + 1 )) ;;
1759
2461
  $'\r'|$'\n')
1760
2462
  sel=$pick picked=$pk
1761
2463
  return 1
@@ -1897,8 +2599,8 @@ __tt_pv_handle() {
1897
2599
  }
1898
2600
 
1899
2601
  __tt_preview() {
1900
- emulate -L zsh
1901
- local mode=$1
2602
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
2603
+ local mode=$1 pdir=${2:-$PWD}
1902
2604
  if [[ ! -t 0 || ! -t 1 ]]; then
1903
2605
  print -u2 "ttheme ${mode:-preview}: needs a terminal"
1904
2606
  return 1
@@ -1911,7 +2613,7 @@ __tt_preview() {
1911
2613
  local -A bgfrom=() bgurl=() bgby=() bgsent=() bgcost=() bgdim=() bgsrc=() bgfill=() bgfocus=() bgsize=() bgpos=() bgop=() bgdef=() bgoff=() bgbase=() bgload=() bgshot=() bgshotkey=() bgedit=() bgtunef=() bgofff=() bgimages=()
1912
2614
  local -A bgpic=() bgpics=() bgact=() bgview=() bgswap=() bgthumb=() tsnaps=() pvseek=()
1913
2615
  local conf=0 cf=1
1914
- local -a plabel=(" This tab " " Default ") csnap=()
2616
+ local -a plabel=(" This tab " " Default ") pkeys=() reach=() csnap=()
1915
2617
  local -a cvars=(TTHEME_TAB_PALETTE TTHEME_ANNOUNCE TTHEME_FX TTHEME_SORT TTHEME_BG_BLUR) clabel=("New tabs" Announce "Search fx" Sort Blur)
1916
2618
  local -a cchoice=("off seq" "1 0" "typewriter decode glitch" "abc series" "0 1 2 3 4") cshow=("off seq" "on off" "typewriter decode glitch" "abc series" "off 1px 2px 3px 4px")
1917
2619
  local -A cnote=(
@@ -1923,7 +2625,7 @@ __tt_preview() {
1923
2625
  TTHEME_BG_BLUR:2 "Pictures soften behind the text" TTHEME_BG_BLUR:3 "Pictures blur behind the text"
1924
2626
  TTHEME_BG_BLUR:4 "Pictures blur well behind the text"
1925
2627
  )
1926
- [[ $mode == pin ]] && pkdef=2 plabel=(" This directory " " And below ")
2628
+ [[ $mode == pin ]] && __tt_pin_scopes "$pdir"
1927
2629
  [[ $mode == init ]] && pkdef=2
1928
2630
  __tt_pv_canpick
1929
2631
  __tt_pv_size
@@ -1939,8 +2641,8 @@ __tt_preview() {
1939
2641
  {
1940
2642
  tty=$(stty -g 2>/dev/null && stty -echo -icanon min 1 time 0 2>/dev/null)
1941
2643
  TTHEME_RAW=$(( ${#tty} > 0 ))
1942
- printf '\e[?2026h\e[?1049h\e[?7l\e[?25l'
1943
2644
  __tt_pv_bg_open
2645
+ printf '\e[?2026h\e[?1049h\e[?7l\e[?25l'
1944
2646
  while :; do
1945
2647
  printf '\e[?2026h'
1946
2648
  if (( tick )); then
@@ -1983,13 +2685,15 @@ __tt_preview() {
1983
2685
  __tt_pv_bg_save
1984
2686
  if [[ -n $spec ]]; then
1985
2687
  TTHEME_SPEC=$spec
1986
- __tt_announce
1987
2688
  if [[ $mode == pin ]]; then
1988
- __tt_pin_save "$sel" $picked "$orig"
1989
- __tt_shown "$sel" && __tt_reload
2689
+ __tt_pin_save "$sel" "${pkeys[picked]}" "$orig"
2690
+ [[ $TTHEME_SPEC == "$orig" ]] || __tt_announce
2691
+ __tt_sync
1990
2692
  elif (( picked == 2 )); then
2693
+ __tt_announce
1991
2694
  __tt_keep "$sel"
1992
2695
  else
2696
+ __tt_announce
1993
2697
  __tt_shown "$sel" force && __tt_reload
1994
2698
  fi
1995
2699
  else
@@ -2000,6 +2704,7 @@ __tt_preview() {
2000
2704
  }
2001
2705
 
2002
2706
  ttheme() {
2707
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
2003
2708
  if (( $# > 1 && ${${argv[2,-1]}[(I)(-h|--help)]} )); then
2004
2709
  __tt_verb_help $1
2005
2710
  return
@@ -2014,8 +2719,8 @@ ttheme() {
2014
2719
  print -u2 "ttheme: unknown command '$1' — see \`ttheme help\`"
2015
2720
  return 1
2016
2721
  fi
2017
- if [[ $TTHEME_ADAPTER == warp && $1 != (browse|list|add|remove|update|market|new|edit|check|share|config|default|on|off) ]]; then
2018
- print -u2 "ttheme: Warp wears one theme app-wide and paints no tab background of its own — \`ttheme default <palette>\` puts one on every Warp window"
2722
+ if ! __tt_paints && (( ! $# || ${TTHEME_TAB_VERBS[(Ie)$1]} )); then
2723
+ __tt_unpainted
2019
2724
  return 1
2020
2725
  fi
2021
2726
  if (( ! $# )); then
@@ -2046,17 +2751,25 @@ ttheme() {
2046
2751
  __tt_resolve "$2" || return 1
2047
2752
  __tt_keep "$REPLY" ;;
2048
2753
  preview) __tt_preview ;;
2049
- pin) __tt_preview pin ;;
2050
- unpin) __tt_unpin ;;
2754
+ pin)
2755
+ if [[ -n $2 ]]; then
2756
+ REPLY=$2
2757
+ [[ $REPLY == \~ || $REPLY == \~/* ]] && REPLY=$HOME${REPLY#\~}
2758
+ [[ -d $REPLY ]] || { print -u2 -r -- "ttheme pin: no such directory: $2"; return 1 }
2759
+ fi
2760
+ __tt_preview pin ${REPLY:+${REPLY:a}} ;;
2761
+ unpin) __tt_unpin "${@:2}" ;;
2762
+ pins) __tt_pins_map ;;
2051
2763
  esac
2052
2764
  }
2053
2765
 
2054
- if (( $+functions[compdef] )); then
2766
+ if (( ${+functions[compdef]} )); then
2055
2767
  __tt_complete() {
2768
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
2056
2769
  local -a reply
2057
2770
  if (( CURRENT == 2 )); then
2058
2771
  compadd -- $TTHEME_VERBS help
2059
- elif [[ $words[2] == remove || ( $words[2] == (use|default|edit|check|share) && CURRENT == 3 ) ]]; then
2772
+ elif [[ $words[2] == remove || ( $words[2] == (use|default|edit|check|share) && $CURRENT == 3 ) ]]; then
2060
2773
  compadd -- $TTHEME_ORDER
2061
2774
  elif [[ $words[2] == new && $words[CURRENT-1] == --from ]]; then
2062
2775
  compadd -- $TTHEME_ORDER
@@ -2064,9 +2777,11 @@ if (( $+functions[compdef] )); then
2064
2777
  _files -/
2065
2778
  elif [[ $words[2] == new ]]; then
2066
2779
  compadd -- --from --in
2067
- elif [[ $words[2] == market && CURRENT == 3 ]]; then
2780
+ elif [[ $words[2] == market && $CURRENT == 3 ]]; then
2068
2781
  compadd -- add remove search init
2069
- elif [[ $words[2] == market && $words[3] == (add|init) && CURRENT == 4 ]]; then
2782
+ elif [[ $words[2] == market && $words[3] == (add|init) && $CURRENT == 4 ]]; then
2783
+ _files -/
2784
+ elif [[ $words[2] == (pin|unpin) && $CURRENT == 3 ]]; then
2070
2785
  _files -/
2071
2786
  elif [[ $words[2] == add && $words[CURRENT-1] == --market ]]; then
2072
2787
  _files -/
@@ -2078,7 +2793,9 @@ if (( $+functions[compdef] )); then
2078
2793
  compdef __tt_complete ttheme
2079
2794
  fi
2080
2795
 
2081
- if __tt_active; then
2796
+ () {
2797
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
2798
+ __tt_active || return 0
2082
2799
  [[ -n $TMUX ]] && __tt_tmux
2083
2800
  () {
2084
2801
  local REPLY
@@ -2103,7 +2820,7 @@ if __tt_active; then
2103
2820
  add-zsh-hook preexec __tt_mux
2104
2821
  add-zsh-hook precmd __tt_unmux
2105
2822
  fi
2106
- if [[ $TTHEME_ADAPTER == (ghostty|kitty|windows-terminal) ]] || (( TTHEME_TMUX )); then
2823
+ if __tt_follows_focus || (( TTHEME_TMUX )); then
2107
2824
  add-zsh-hook precmd __tt_precmd
2108
2825
  add-zsh-hook preexec __tt_preexec
2109
2826
  __tt_bind_focus
@@ -2111,11 +2828,12 @@ if __tt_active; then
2111
2828
  __tt_sync
2112
2829
  fi
2113
2830
  __tt_announce
2114
- fi
2831
+ }
2115
2832
 
2116
2833
  () {
2834
+ emulate -L zsh ${=${options[xtrace]:#off}:+-o xtrace}
2117
2835
  local f
2118
- for f in $TTHEME_HOME/ttheme.zsh $TTHEME_HOME/palettes.zsh $TTHEME_HOME/adapters/_osc.zsh $TTHEME_HOME/adapters/_bg.zsh $TTHEME_HOME/adapters/$TTHEME_ADAPTER.zsh; do
2836
+ for f in $TTHEME_HOME/ttheme.zsh $TTHEME_HOME/palettes.zsh $TTHEME_HOME/adapters/_osc.zsh $TTHEME_HOME/adapters/_wired.zsh $TTHEME_HOME/adapters/_bg.zsh $TTHEME_HOME/adapters/$TTHEME_ADAPTER.zsh; do
2119
2837
  [[ -r $f && ! $f.zwc -nt $f ]] || continue
2120
2838
  zcompile -UR -- $f.$$.zwc $f 2>/dev/null && command mv -f -- $f.$$.zwc $f.zwc 2>/dev/null
2121
2839
  [[ ! -e $f.$$.zwc ]] || command rm -f -- $f.$$.zwc