+++ /dev/null
-Xft.dpi: 96
-Xft.antialias: true
-Xft.hinting: true
-Xft.rgba: rgb
-Xft.autohint: false
-Xft.hintstyle: hintslight
-Xft.lcdfilter: lcddefault
-
-XTerm*background: #222D31
-XTerm*foreground: #d8d8d8
-XTerm*pointerColor: #1ABB9B
-XTerm*faceName: Fixed
-XTerm*faceSize: 11
-XTerm*reverseVideo: on
-XTerm*selectToClipboard: true
-
-*background: #222D31
-*foreground: #d8d8d8
-*fading: 8
-*fadeColor: black
-*cursorColor: #1ABB9B
-*pointerColorBackground: #2B2C2B
-*pointerColorForeground: #16A085
-
-!! black dark/light
-*color0: #222D31
-*color8: #585858
-
-!! red dark/light
-*color1: #ab4642
-*color9: #ab4642
-
-!! green dark/light
-*color2: #7E807E
-*color10: #8D8F8D
-
-!! yellow dark/light
-*color3: #f7ca88
-*color11: #f7ca88
-
-!! blue dark/light
-*color4: #7cafc2
-*color12: #7cafc2
-
-!! magenta dark/light
-*color5: #ba8baf
-*color13: #ba8baf
-
-!! cyan dark/light
-*color6: #1ABB9B
-*color14: #1ABB9B
-
-!! white dark/light
-*color7: #d8d8d8
-*color15: #f8f8f8
-
-Xcursor.theme: xcursor-breeze
-Xcursor.size: 0
-
-URxvt*font: xft:DejaVuSansMono:size=10
-! URxvt.font: xft:TerminessTTFNerdFontMono:size:11
-! URxvt.font: 9x15,xft:TerminessTTFNerdFontMono
-
-! alternative font settings with 'terminus':
-! URxvt.font: -xos4-terminus-medium-r-normal--16-160-72-72-c-80-iso10646-1
-! URxvt.bold.font: -xos4-terminus-bold-r-normal--16-160-72-72-c-80-iso10646-1
-!! terminus names see end of file!
-
-URxvt.depth: 32
-URxvt.background: [100]#222D31
-URxvt*scrollBar: false
-URxvt*mouseWheelScrollPage: false
-URxvt*cursorBlink: true
-URxvt*background: black
-URxvt*foreground: grey
-URxvt*saveLines: 5000
-
-! for 'fake' transparency (without Compton) uncomment the following three lines
-! URxvt*inheritPixmap: true
-! URxvt*transparent: true
-! URxvt*shading: 138
-
-! Normal copy-paste keybindings without perls
-URxvt.iso14755: false
-URxvt.keysym.Shift-Control-V: eval:paste_clipboard
-URxvt.keysym.Shift-Control-C: eval:selection_to_clipboard
-!Xterm escape codes, word by word movement
-URxvt.keysym.Control-Left: \033[1;5D
-URxvt.keysym.Shift-Control-Left: \033[1;6D
-URxvt.keysym.Control-Right: \033[1;5C
-URxvt.keysym.Shift-Control-Right: \033[1;6C
-URxvt.keysym.Control-Up: \033[1;5A
-URxvt.keysym.Shift-Control-Up: \033[1;6A
-URxvt.keysym.Control-Down: \033[1;5B
-URxvt.keysym.Shift-Control-Down: \033[1;6B
-! Rxvt.perl-ext-common: ...,clipboard
-! URxvt.keysym.M-C-c: perl:clipboard:copy
-! URxvt.keysym.M-v: perl:clipboard:paste
-! URxvt.keysym.M-C-v: perl:clipboard:paste_escaped
-! URxvt*termName: string
-! URxvt*geometry: geometry
-! URxvt*chdir: string
-! URxvt*loginShell: boolean
-! URxvt*multiClickTime: number
-! URxvt*jumpScroll: boolean
-! URxvt*skipScroll: boolean
-! URxvt*pastableTabs: boolean
-! URxvt*scrollstyle: plain
-! URxvt*scrollBar_right: boolean
-! URxvt*scrollBar_floating: true
-! URxvt*scrollBar_align: mode
-! URxvt*thickness: number
-! URxvt*scrollTtyOutput: boolean
-! URxvt*scrollTtyKeypress: boolean
-! URxvt*scrollWithBuffer: boolean
-! URxvt*tintColor: !7DA55
-! URxvt*blurRadius: HxV
-! URxvt*fading: number
-! URxvt*fadeColor: color
-! URxvt*utmpInhibit: boolean
-! URxvt*urgentOnBell: boolean
-! URxvt*visualBell: boolean
-! URxvt*mapAlert: boolean
-! URxvt*meta8: boolean
-! URxvt*tripleclickwords: boolean
-! URxvt*insecure: boolean
-! URxvt*cursorUnderline: boolean
-! URxvt*pointerBlank: boolean
-! URxvt*color0: color
-! URxvt*color1: color
-! URxvt*color2: color
-! URxvt*color3: color
-! URxvt*color4: color
-! URxvt*color5: color
-! URxvt*color6: color
-! URxvt*color7: color
-! URxvt*color8: color
-! URxvt*color9: color
-! URxvt*color10: color
-! URxvt*color11: color
-! URxvt*color12: color
-! URxvt*color13: color
-! URxvt*color14: color
-! URxvt*color15: color
-! URxvt*colorBD: color
-! URxvt*colorIT: color
-! URxvt*colorUL: color
-! URxvt*colorRV: color
-! URxvt*underlineColor: color
-! URxvt*scrollColor: color
-! URxvt*troughColor: color
-! URxvt*highlightColor: color
-! URxvt*highlightTextColor: color
-! URxvt*cursorColor: color
-! URxvt*cursorColor2: color
-! URxvt*pointerColor: color
-! URxvt*pointerColor2: color
-! URxvt*borderColor: color
-! URxvt*iconFile: file
-! URxvt*font: fontname
-! URxvt*boldFont: fontname
-! URxvt*italicFont: fontname
-! URxvt*boldItalicFont: fontname
-! URxvt*intensityStyles: boolean
-! URxvt*inputMethod: name
-! URxvt*preeditType: style
-! URxvt*imLocale: string
-! URxvt*imFont: fontname
-! URxvt*title: string
-! URxvt*iconName: string
-! URxvt*buffered: boolean
-! URxvt*depth: number
-! URxvt*visual: number
-! URxvt*transient-for: windowid
-! URxvt*override-redirect: boolean
-! URxvt*hold: boolean
-! URxvt*externalBorder: number
-! URxvt*internalBorder: number
-! URxvt*borderLess: true
-! URxvt*lineSpace: number
-! URxvt*letterSpace: number
-! URxvt*skipBuiltinGlyphs: boolean
-! URxvt*pointerBlankDelay: number
-! URxvt*backspacekey: string
-! URxvt*deletekey: string
-! URxvt*print-pipe: string
-! URxvt*modifier: modifier
-! URxvt*cutchars: string
-! URxvt*answerbackString: string
-! URxvt*secondaryScreen: boolean
-! URxvt*secondaryScroll: boolean
-! URxvt*perl-lib: string
-! URxvt*perl-eval: perl-eval
-! URxvt*perl-ext-common: string
-! URxvt*perl-ext: string
-! URxvt*iso14755: boolean
-! URxvt*iso14755_52: boolean
-! URxvt*xrm: string
-! URxvt*keysym.sym: keysym
-! URxvt*background.border: boolean
-! URxvt*background.expr: string
-! URxvt*background.interval: seconds
-! URxvt*bell-command: string
-! URxvt*kuake.hotkey: string
-! URxvt*matcher.button: string
-! URxvt*matcher.launcher: string
-! URxvt*matcher.launcher.*: string
-! URxvt*matcher.pattern.*: string
-! URxvt*matcher.rend.*: string
-! URxvt*remote-clipboard.fetch: string
-! URxvt*remote-clipboard.store: string
-! URxvt*searchable-scrollback: string
-! URxvt*selection-autotransform.*: string
-! URxvt*selection-pastebin.cmd: string
-! URxvt*selection-pastebin.url: string
-! URxvt*selection.pattern-0: string
-! URxvt*tab-bg: colour
-! URxvt*tab-fg: colour
-! URxvt*tabbar-bg: colour
-! URxvt*tabbar-fg: colour
-! URxvt*url-launcher: string
-
-! The Terminus font uses the following X-names:
-! -xos4-terminus-medium-r-normal--12-120-72-72-c-60-iso10646-1
-! -xos4-terminus-medium-r-normal--14-140-72-72-c-80-iso10646-1
-! -xos4-terminus-medium-r-normal--16-160-72-72-c-80-iso10646-1
-! -xos4-terminus-medium-r-normal--20-200-72-72-c-100-iso10646-1
-! -xos4-terminus-medium-r-normal--22-220-72-72-c-110-iso10646-1
-! -xos4-terminus-medium-r-normal--24-240-72-72-c-120-iso10646-1
-! -xos4-terminus-medium-r-normal--28-280-72-72-c-140-iso10646-1
-! -xos4-terminus-medium-r-normal--32-320-72-72-c-160-iso10646-1
-! -xos4-terminus-bold-r-normal--12-120-72-72-c-60-iso10646-1
-! -xos4-terminus-bold-r-normal--14-140-72-72-c-80-iso10646-1
-! -xos4-terminus-bold-r-normal--16-160-72-72-c-80-iso10646-1
-! -xos4-terminus-bold-r-normal--20-200-72-72-c-100-iso10646-1
-! -xos4-terminus-bold-r-normal--24-240-72-72-c-120-iso10646-1
-! -xos4-terminus-bold-r-normal--28-280-72-72-c-140-iso10646-1
-! -xos4-terminus-bold-r-normal--32-320-72-72-c-160-iso10646-1
# Allows for faster key repetition
set -s escape-time 0
+# multi-line shift+enter
+bind -n S-Enter send-keys C-j
+
# bind new keys for splits
bind | split-window -hc "#{pane_current_path}"
bind - split-window -vc "#{pane_current_path}"
# constrain window size to the maximum size of any client connected to that window
setw -g aggressive-resize on
+# Start window and pane numbering at 1.
+set -g base-index 1
+setw -g pane-base-index 1
+set -g renumber-windows on
+
# Set status bar
set -g status-bg colour0
set -g status-fg white
set -g status-left "#[bg=colour0]#[fg=colour4] #h [#[fg=colour15]#I#[fg=colour4]] "
# set -g status-right "#[bg=colour33]#[fg=colour255]#[fg=colour232] #[fg=colour255]#(uptime | awk {'print $8, $9, $10'})#[fg=colour232] #[bg=colour232] #[bg=colour33] #[fg=colour255]#(date '+%H:%M %d-%m-%Y')#[fg=colour232] "
set -g status-right "#[bg=colour0]#[fg=colour255]#[fg=colour232]#[bg=colour0]#[fg=colour149]#(date '+%Y-%m-%d %a %H:%M')"
+
+# Highlight the active tab and show activity/bell notifications.
+set -g status-justify left
+setw -g window-status-separator ''
+setw -g window-status-format '#[fg=colour244,bg=colour0] #I #[fg=colour250]#W #[fg=colour238]|'
+setw -g window-status-current-format '#[fg=colour232,bg=colour39,bold] #I #[fg=colour255]#W #[fg=colour39] '
+setw -g window-status-last-style 'fg=colour0,bg=colour245'
+setw -g window-status-activity-style 'fg=colour232,bg=colour214,bold'
+setw -g window-status-bell-style 'fg=colour255,bg=colour160,bold'
+
+# Switch tabs and panes with Ctrl+Shift+Arrow.
+bind -n C-S-Left previous-window
+bind -n C-S-Right next-window
+bind -n C-S-Up select-pane -U
+bind -n C-S-Down select-pane -D
+
+# Jump directly to tabs 1–9.
+bind -n C-1 select-window -t 1
+bind -n C-2 select-window -t 2
+bind -n C-3 select-window -t 3
+bind -n C-4 select-window -t 4
+bind -n C-5 select-window -t 5
+bind -n C-6 select-window -t 6
+bind -n C-7 select-window -t 7
+bind -n C-8 select-window -t 8
+bind -n C-9 select-window -t 9
## Plugins section: Enable fish style features
# Use syntax highlighting
-source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
+source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
# Use history substring search
-source /usr/share/zsh/plugins/zsh-history-substring-search/zsh-history-substring-search.zsh
+source /opt/homebrew/share/zsh-history-substring-search/zsh-history-substring-search.zsh
# bind UP and DOWN arrow keys to history substring search
zmodload zsh/terminfo
bindkey "$terminfo[kcuu1]" history-substring-search-up
PROMPT='%F{cyan}%n@%m%f %F{240}%1~ %f%# '
RPROMPT='%F{green}${vcs_info_msg_0_} %F{240}%*%f'
+
+
+# ZSH completion
+if type brew &>/dev/null; then
+ FPATH="$(brew --prefix)/share/zsh/site-functions:${FPATH}"
+fi
+autoload -Uz compinit
+# only rebuild the completion dump once a day
+if [[ -n ~/.zcompdump(#qN.mh+24) ]]; then compinit; else compinit -C; fi
+
+export LS_COLORS='di=34:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34;43'
+
+zstyle ':completion:*' menu select
+zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=*' 'l:|=* r:|=*'
+zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}"
+zstyle ':completion:*' group-name ''
+zstyle ':completion:*:descriptions' format '%F{yellow}-- %d --%f'
+zstyle ':completion:*:messages' format '%F{purple}-- %d --%f'
+zstyle ':completion:*:warnings' format '%F{red}-- no matches --%f'
+zstyle ':completion:*' verbose yes
+zstyle ':completion:*' use-cache on
+zstyle ':completion:*' cache-path ~/.zsh/cache
+setopt complete_in_word auto_menu
+
+
# aliases
alias ls='ls -G'
alias cats='highlight -O ansi --force'
alias shf='defaults write com.apple.finder AppleShowAllFiles true; killall Finder' # show hidden
# alias cat='bat'
alias grep='grep --color'
+alias ff='fastfetch'
+#alias ncdu='ncdu --color=dark'
+
+# scripts
+export PATH="/Users/gld/bin:$PATH"
+
# Lima
alias archlinux='limactl shell archlinux'
export PATH="/etc/paths.d/TeX:/Library/TeX/texbin:$PATH"
# ruby
-#export PATH="/Users/gld/.gem/ruby/3.1.1/bin:/opt/homebrew/opt/ruby/bin:/opt/homebrew/lib/ruby/gems/3.1.1/bin:$PATH"
-export PATH="/Users/gld/.gem/ruby/3.1.0/bin:/opt/homebrew/opt/ruby/bin:/opt/homebrew/lib/ruby/gems/3.1.0/bin:$PATH"
-source /opt/homebrew/opt/chruby/share/chruby/chruby.sh
-source /opt/homebrew/opt/chruby/share/chruby/auto.sh
-chruby ruby-3.1.2 # run chruby to see actual version
+export PATH="/Users/gld/.gem/ruby/3.1.1/bin:/opt/homebrew/opt/ruby/bin:/opt/homebrew/lib/ruby/gems/3.1.1/bin:$PATH"
+#export PATH="/Users/gld/.gem/ruby/3.1.0/bin:/opt/homebrew/opt/ruby/bin:/opt/homebrew/lib/ruby/gems/3.1.0/bin:$PATH"
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh
source /opt/homebrew/opt/chruby/share/chruby/auto.sh
+# chruby ruby-3.1.2 # run chruby to see actual version
+#source /opt/homebrew/opt/chruby/share/chruby/chruby.sh
+#source /opt/homebrew/opt/chruby/share/chruby/auto.sh
+
+# mysql
+export PATH="/opt/homebrew/opt/mysql/bin:$PATH"
# dotfiles
alias cfg='/opt/homebrew/bin/git --git-dir=/Users/gld/.cfg/ --work-tree=/Users/gld'
# more path
export PATH="$HOME/.local/bin:$PATH"
+# fzf and fuzzy tab completion
+source <(fzf --zsh)
+source "$(brew --prefix)/opt/fzf-tab/share/fzf-tab/fzf-tab.zsh"
+zstyle ':fzf-tab:*' fzf-flags --height=40%
+# plugins -- syntax highlighting must stay last
+source "$(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh"
+source "$(brew --prefix)/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
# #BASE16_SHELL="/usr/share/zsh/scripts/base16-shell/base16-$theme.$shade.sh"
# #[[ -s $BASE16_SHELL ]] && source $BASE16_SHELL
# # Use autosuggestion
-# source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
+# source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh
# ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=20
# ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'
# ;;
*)
RPROMPT='$(git_prompt_string)'
# Use autosuggestion
- source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
+ source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh
ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=20
ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'
;;
--- /dev/null
+# cd-smart
+
+A `cd` that learns where you go. About 250 lines of zsh, one plain-text
+database, no external processes on the `cd` path, and no dependencies beyond
+zsh itself (fzf is used by `cdi` when present).
+
+## Install
+
+```zsh
+# ~/.zshrc, after compinit
+source ~/.zsh/cd-smart.zsh
+```
+
+Files live next to the script: `cd-smart.tsv` (learned directories) and
+`cd-smart.aliases` (shortcuts defined at the prompt).
+
+## Use
+
+```zsh
+cd ~/Projects/notes # ordinary cd; the directory is learned
+cd agents # jump to the best learned match for "agents"
+cd open ag # words must appear in order: .../Projects/notes
+cd ag<TAB> # normal completion plus learned matches and shortcuts
+cdi ag # pick from matches in fzf; ctrl-/ toggles a preview
+cdi # browse everything learned
+```
+
+Precedence inside the wrapper, first match wins:
+
+1. No argument, or anything with options, slashes, or a leading `.` or `~`,
+ goes straight to the builtin. `cd ..`, `cd -`, `cd ~/x` are untouched.
+2. A single word that exists as a directory here.
+3. A single word that is a shortcut.
+4. Two words that work as the builtin's `cd old new` substitution.
+5. The best-scored learned directory containing the words in order.
+6. The builtin's normal error.
+
+### Shortcuts
+
+```zsh
+cdalias osa ~/Projects/notes # "cd osa" always goes there
+cdalias dl # current directory becomes "dl"
+cdalias # list
+cdunalias dl # remove
+```
+
+Shortcuts can also be declared in `.zshrc` before sourcing, and those win over
+saved ones:
+
+```zsh
+typeset -gA CDS_ALIASES=(osa ~/Projects/notes)
+```
+
+### Maintenance
+
+```zsh
+cdlearn PATH # learn a directory without visiting it
+cdforget PATH # remove one
+cdlist # learned directories with scores
+cdsmart-help # short reminder of all of this
+```
+
+### Configuration
+
+Set before sourcing.
+
+| variable | default | meaning |
+|---------------|------------------------------|-------------------------------------------|
+| `CDS_DB` | `cd-smart.tsv` beside script | learned-directory database |
+| `CDS_ROOTS` | `$HOME` (and `/zpool` if present) | only directories under these are learned |
+| `CDS_MAXAGE` | `1000` | aging threshold, see below |
+| `CDS_ALIASES` | empty | shortcuts declared in `.zshrc` |
+
+## How ranking works
+
+Each learned directory has a score and a last-visit time. A visit adds 1 to
+the score. At lookup time a candidate's rank is its score plus a bonus for an
+exact basename match (+100) or basename prefix (+50) on the last query word,
+plus a recency bonus (20 within an hour, 15 within a day, 10 within a week,
+5 within a month).
+
+Growth is bounded by aging. When the sum of all scores exceeds `CDS_MAXAGE`,
+every score is multiplied by `0.9 × CDS_MAXAGE / total`, entries that fall
+below 1 are dropped, and directories that no longer exist are pruned. Places
+you keep returning to stay; one-off visits fade out.
+
+## Tests
+
+```zsh
+zsh ~/.zsh/cd-smart.test.zsh
+```
+
+Runs in a throwaway sandbox and never touches your real database.
+
+## Compared with zoxide
+
+[zoxide](https://github.com/ajeetdsouza/zoxide) solves the same problem and
+is the obvious alternative. The ranking model here is deliberately close to
+its design. The differences are mostly about scope.
+
+| | cd-smart | zoxide |
+|---|---|---|
+| Implementation | ~250 lines of zsh, one file | ~2,000 lines of Rust plus a shell template per shell |
+| Shells | zsh only | bash, zsh, fish, nushell, PowerShell, and others |
+| Database | plain TSV you can read and edit | binary, with file locking |
+| Command | wraps `cd` itself | separate `z` and `zi`; `--cmd cd` can replace `cd` |
+| Real directories vs. guesses | an existing local directory always wins, then shortcuts, then learned matches | with `--cmd cd`, an existing path also wins; otherwise `z` always guesses |
+| Tab completion | learned matches and shortcuts appear inside normal `cd <TAB>` | completion on the `z` command only |
+| Shortcuts | built in (`cdalias`) | none; use shell aliases |
+| Where learning happens | only under `CDS_ROOTS` | everywhere, minus `_ZO_EXCLUDE_DIRS` |
+| Multi-word queries | words in order anywhere in the path; last word gets basename bonuses | words in order; last word must match the last path component |
+| Recency | additive bonus by age bucket | multiplier by age bucket (4×, 2×, 0.5×, 0.25×) |
+| Aging | same scheme: scale by 0.9 × cap / total, drop below 1, prune dead paths | same, default cap 10,000 |
+| Concurrency | last writer wins if two shells `cd` at the same instant | locked writes |
+| External processes | none on `cd` or lookup | one binary launch per `cd` and per query |
+| Speed at 300 entries (typical after aging) | ~3.5 ms per `cd`, ~1 ms per lookup | a few ms, dominated by the launch |
+| Speed at 2,000 entries | ~17 ms per `cd`, ~7 ms per lookup | a few ms |
+| Tests | 45 smoke tests in one script | full Rust test suite |
+
+In short: zoxide is safer under concurrent shells and portable, and stays
+fast at any database size.
+cd-smart is smaller, fully inspectable, restricted to the directories you
+care about, and integrates with `cd` and its completion rather than adding a
+second command. In practice aging keeps the database in the low hundreds of
+entries, where neither tool is perceptibly slower than a plain `cd`.
--- /dev/null
+1.00 1789845937 /Users/gld
--- /dev/null
+# cd-smart.zsh
+#
+# A small, self-contained Zsh "smart cd" layer.
+#
+# Normal:
+# cd /Users/gabrieldunne/Code
+# cd ~/Downloads
+# cd ../foo
+#
+# Smart:
+# cd ph -> best learned directory matching "ph"
+# cd pro notes -> words must appear in order: .../Projects/notes
+# cd ph<TAB> -> best learned matches
+# cdi ph -> pick from matches in fzf
+#
+# Learning happens automatically after successful directory changes.
+
+[[ -o interactive ]] || return 0
+
+zmodload zsh/datetime
+zmodload zsh/files
+
+: ${CDS_DB:="${${(%):-%x}:A:h}/cd-smart.tsv"}
+: ${CDS_MAXAGE:=1000}
+: ${CDS_ALIAS_FILE:="${CDS_DB:h}/cd-smart.aliases"}
+
+(( ${+CDS_ALIASES} )) || typeset -gA CDS_ALIASES
+typeset -gA _cds_zshrc_aliases=("${(@kv)CDS_ALIASES}")
+
+_cds_load_aliases() {
+ local name target
+ [[ -r "$CDS_ALIAS_FILE" ]] || return 0
+ while IFS=$'\t' read -r name target; do
+ [[ -n "$name" && -n "$target" ]] || continue
+ (( ${+_cds_zshrc_aliases[$name]} )) || CDS_ALIASES[$name]="$target"
+ done < "$CDS_ALIAS_FILE"
+}
+_cds_load_aliases
+
+_cds_valid_alias_name() {
+ [[ -n "$1" && "$1" != -* && "$1" != */* && "$1" != .* && "$1" != '~'* && "$1" != *[[:space:]]* ]]
+}
+
+if (( ! ${+CDS_ROOTS} )); then
+ typeset -ga CDS_ROOTS=("$HOME")
+ [[ -d /zpool ]] && CDS_ROOTS+=("/zpool")
+fi
+CDS_ROOTS=("${(@)CDS_ROOTS%/}")
+
+_cds_init() {
+ [[ -d "${CDS_DB:h}" ]] || zf_mkdir -p -- "${CDS_DB:h}" 2>/dev/null
+ [[ -f "$CDS_DB" ]] || : >| "$CDS_DB" 2>/dev/null
+}
+
+_cds_save() {
+ local tmp="${CDS_DB}.tmp.$$" line dir sc
+ local -F total=0 factor score
+ local -a lines=("${(@)@:#}") scores
+
+ scores=("${(@)lines%%$'\t'*}")
+ for sc in "${scores[@]}"; do (( total += ${sc:-0} )); done
+
+ if (( total > CDS_MAXAGE )); then
+ (( factor = 0.9 * CDS_MAXAGE / total ))
+ {
+ for line in "${lines[@]}"; do
+ dir="${line##*$'\t'}"
+ [[ -n "$dir" && -d "$dir" ]] || continue
+ (( score = ${${line%%$'\t'*}:-0} * factor ))
+ (( score >= 1 )) || continue
+ printf '%.2f\t%s\n' "$score" "${line#*$'\t'}"
+ done
+ } >| "$tmp" 2>/dev/null || { zf_rm -f -- "$tmp"; return 1 }
+ else
+ { (( ${#lines} == 0 )) || print -rl -- "${lines[@]}" } >| "$tmp" 2>/dev/null ||
+ { zf_rm -f -- "$tmp"; return 1 }
+ fi
+
+ zf_mv -f -- "$tmp" "$CDS_DB" 2>/dev/null || { zf_rm -f -- "$tmp"; return 1 }
+}
+
+_cds_allowed() {
+ local p="$1" root
+ for root in "${CDS_ROOTS[@]}"; do
+ [[ "$p" == "$root" || "$p" == "$root"/* ]] && return 0
+ done
+ return 1
+}
+
+_cds_record() {
+ local p="${1:-$PWD}" i pat
+ local -F score
+ local -a lines
+
+ _cds_allowed "$p" || return 0
+ _cds_init || return 0
+ lines=("${(@f)$(<"$CDS_DB")}")
+ lines=("${(@)lines:#}")
+ pat="*"$'\t'"${(b)p}"
+ i=${lines[(i)$pat]}
+ if (( i <= ${#lines} )); then
+ (( score = ${${lines[i]%%$'\t'*}:-0} + 1 ))
+ lines[i]="$(printf '%.2f' "$score")"$'\t'"$EPOCHSECONDS"$'\t'"$p"
+ else
+ lines+=("1.00"$'\t'"$EPOCHSECONDS"$'\t'"$p")
+ fi
+ _cds_save "${lines[@]}"
+}
+
+autoload -Uz add-zsh-hook
+add-zsh-hook chpwd _cds_record
+
+_cds_matches() {
+ setopt localoptions extendedglob
+ local now stored last dir base age recency pat w line key
+ local -F score
+ local -a words=("${(@L)@}") lines ranked
+ local q="${words[-1]}"
+
+ pat='(#i)*'
+ for w in "${words[@]}"; do pat+="${(b)w}*"; done
+
+ _cds_init || return 0
+ now=$EPOCHSECONDS
+ lines=("${(@f)$(<"$CDS_DB")}")
+ lines=("${(@M)lines:#${~pat}}")
+
+ for line in "${lines[@]}"; do
+ stored="${line%%$'\t'*}"
+ dir="${line##*$'\t'}"
+ last="${${line#*$'\t'}%%$'\t'*}"
+ [[ -n "$dir" && -d "$dir" ]] || continue
+ base="${dir:t:l}"
+ score=${stored:-0}
+
+ if [[ -n "$q" && "$base" == "$q" ]]; then
+ (( score += 100 ))
+ elif [[ -n "$q" && "$base" == "$q"* ]]; then
+ (( score += 50 ))
+ fi
+
+ age=$(( now - ${last:-now} ))
+ if (( age < 3600 )); then recency=20
+ elif (( age < 86400 )); then recency=15
+ elif (( age < 604800 )); then recency=10
+ elif (( age < 2592000 )); then recency=5
+ else recency=0
+ fi
+
+ (( score += recency ))
+ printf -v key '%012.2f' "$score"
+ ranked+=("$key"$'\t'"$dir")
+ done
+
+ (( ${#ranked} )) && print -rl -- "${(@)${(@O)ranked}#*$'\t'}"
+}
+
+_cds_simple_words() {
+ local w
+ (( $# )) || return 1
+ for w in "$@"; do
+ [[ "$w" == -* || "$w" == */* || "$w" == .* || "$w" == '~'* ]] && return 1
+ done
+ return 0
+}
+
+cd() {
+ local -a matches
+ (( $# == 0 )) && { builtin cd; return; }
+
+ if (( $# > 1 )) && [[ "${@[-1]}" == /* && -d "${@[-1]}" ]]; then
+ builtin cd -- "${@[-1]}"
+ return
+ fi
+
+ if ! _cds_simple_words "$@"; then
+ builtin cd "$@"
+ return
+ fi
+
+ if (( $# == 1 )) && [[ -d "$1" ]]; then
+ builtin cd -- "$1"
+ return
+ fi
+
+ if (( $# == 1 && ${+CDS_ALIASES[$1]} )); then
+ local target="${CDS_ALIASES[$1]}"
+ [[ -d "$target" ]] || { print -u2 "cd-smart: alias $1 -> $target: not a directory"; return 1; }
+ builtin cd -- "$target"
+ return
+ fi
+
+ if (( $# == 2 )) && builtin cd "$@" 2>/dev/null; then
+ return
+ fi
+
+ matches=(${(f)"$(_cds_matches "$@")"})
+ if (( ${#matches} )); then
+ builtin cd -- "${matches[1]}"
+ return
+ fi
+
+ if (( $# > 1 )); then
+ print -u2 "cd-smart: no learned match for ${(q)*}"
+ return 1
+ fi
+ builtin cd "$@"
+}
+
+_cds_cd_complete() {
+ (( $+functions[_cd] )) && _cd "$@"
+ local -a query learned
+ query=("${(@)words[2,CURRENT]}")
+ _cds_simple_words "${query[@]}" || return 0
+
+ if (( CURRENT == 2 && ${#CDS_ALIASES} )); then
+ local name
+ local -a names descs
+ for name in "${(@ko)CDS_ALIASES}"; do
+ names+=("$name")
+ descs+=("$name -- ${CDS_ALIASES[$name]/#$HOME/~}")
+ done
+ compadd -J shortcuts -X 'shortcuts' -d descs -- "${names[@]}"
+ fi
+
+ learned=(${(f)"$(_cds_matches "${query[@]}")"})
+ (( ${#learned} )) || return 0
+ compadd -U -Q -J learned -X 'learned directories' -- "${learned[@]}"
+}
+
+compdef _cds_cd_complete cd 2>/dev/null
+
+cdi() {
+ local q="$*" choice
+ local -a paths
+ paths=(${(f)"$(_cds_matches "$@")"})
+ if (( ! ${#paths[@]} )); then
+ print -u2 "cd-smart: no learned match for ${(q)q}"
+ return 1
+ fi
+
+ if (( $+commands[fzf] )); then
+ choice="$(print -rl -- "${paths[@]}" | fzf --height=~40% --layout=reverse --no-sort --select-1 --exit-0 --prompt='cd › ' --query="$q" --preview='CLICOLOR_FORCE=1 ls -1G {}' --preview-window=right:50%:hidden --bind=ctrl-/:toggle-preview)" || return 1
+ elif (( ${#paths[@]} == 1 )); then
+ choice="${paths[1]}"
+ else
+ print
+ select choice in "${paths[@]}" "cancel"; do
+ [[ "$choice" == cancel || -z "$choice" ]] && return 1
+ break
+ done
+ fi
+
+ [[ -n "$choice" ]] && builtin cd -- "$choice"
+}
+
+cdlearn() {
+ local p
+ (( $# == 1 )) || { print -u2 "usage: cdlearn PATH"; return 2; }
+ p="${1:A}"
+ [[ -d "$p" ]] || { print -u2 "cd-smart: not a directory: $1"; return 1; }
+ _cds_allowed "$p" || { print -u2 "cd-smart: outside CDS_ROOTS, not learned: $p"; return 1; }
+ _cds_record "$p"
+}
+
+cdforget() {
+ local target="${1:A}" score last dir
+ local -a lines
+ (( $# == 1 )) || { print -u2 "usage: cdforget PATH"; return 2; }
+ _cds_init || return 1
+ while IFS=$'\t' read -r score last dir; do
+ [[ -n "$dir" && "$dir" != "$target" ]] || continue
+ lines+=("$score"$'\t'"$last"$'\t'"$dir")
+ done < "$CDS_DB"
+ _cds_save "${lines[@]}"
+}
+
+cdlist() {
+ local score last dir
+ _cds_init || return 1
+ while IFS=$'\t' read -r score last dir; do
+ [[ -n "$dir" ]] && printf '%9.2f %s\n' "$score" "$dir"
+ done < "$CDS_DB" | sort -rn
+}
+
+_cds_save_aliases() {
+ local name tmp="${CDS_ALIAS_FILE}.tmp.$$"
+ {
+ for name in "${(@ko)CDS_ALIASES}"; do
+ (( ${+_cds_zshrc_aliases[$name]} )) && continue
+ print -r -- "$name"$'\t'"${CDS_ALIASES[$name]}"
+ done
+ } >| "$tmp" 2>/dev/null || { zf_rm -f -- "$tmp"; return 1; }
+ zf_mv -f -- "$tmp" "$CDS_ALIAS_FILE" 2>/dev/null || { zf_rm -f -- "$tmp"; return 1; }
+}
+
+cdalias() {
+ local name="$1" target="${2:-$PWD}"
+ if (( $# == 0 )); then
+ for name in "${(@ko)CDS_ALIASES}"; do
+ printf '%-12s %s\n' "$name" "${CDS_ALIASES[$name]/#$HOME/~}"
+ done
+ return 0
+ fi
+ (( $# <= 2 )) || { print -u2 "usage: cdalias [NAME [PATH]]"; return 2; }
+ _cds_valid_alias_name "$name" || { print -u2 "cd-smart: invalid alias name: ${(q)name}"; return 2; }
+ target="${target:A}"
+ [[ -d "$target" ]] || { print -u2 "cd-smart: not a directory: $2"; return 1; }
+ (( ${+_cds_zshrc_aliases[$name]} )) && { print -u2 "cd-smart: $name is defined in your zshrc; edit it there"; return 1; }
+ CDS_ALIASES[$name]="$target"
+ _cds_save_aliases
+}
+
+cdunalias() {
+ local name="$1"
+ (( $# == 1 )) || { print -u2 "usage: cdunalias NAME"; return 2; }
+ (( ${+CDS_ALIASES[$name]} )) || { print -u2 "cd-smart: no such alias: $name"; return 1; }
+ (( ${+_cds_zshrc_aliases[$name]} )) && { print -u2 "cd-smart: $name is defined in your zshrc; remove it there"; return 1; }
+ unset "CDS_ALIASES[$name]"
+ _cds_save_aliases
+}
+
+cdsmart-help() {
+ print 'cd-smart: cd learns visited directories; cdi searches them with fzf.'
+ print 'Commands: cdlearn PATH, cdforget PATH, cdlist, cdalias [NAME [PATH]], cdunalias NAME'
+}
--- /dev/null
+# Shared Zsh settings
+
+setopt correct extendedglob nocaseglob rcexpandparam nocheckjobs
+setopt numericglobsort nobeep appendhistory histignorealldups autocd prompt_subst
+
+HISTFILE="$HOME/.zsh_history"
+HISTSIZE=100000
+SAVEHIST=100000
+setopt share_history hist_ignore_all_dups hist_ignore_space hist_reduce_blanks extended_history
+
+export LS_COLORS='di=34:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34;43'
+
+# Completion
+if command -v brew >/dev/null 2>&1; then
+ FPATH="$(brew --prefix)/share/zsh/site-functions:${FPATH}"
+fi
+autoload -Uz compinit
+if [[ -n ~/.zcompdump(#qN.mh+24) ]]; then compinit; else compinit -C; fi
+zstyle ':completion:*' menu no
+zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=*' 'l:|=* r:|=*'
+zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}"
+zstyle ':completion:*' group-name ''
+zstyle ':completion:*:descriptions' format '[%d]'
+zstyle ':completion:*:messages' format '[%d]'
+zstyle ':completion:*:warnings' format '[no matches]'
+zstyle ':completion:*' verbose yes use-cache on
+zstyle ':completion:*' cache-path "$HOME/.zsh/cache"
+setopt complete_in_word auto_menu
+
+# Prompt
+autoload -Uz vcs_info
+precmd_vcs_info() { vcs_info }
+precmd_functions+=(precmd_vcs_info)
+zstyle ':vcs_info:git:*' formats '%b'
+PROMPT='%F{cyan}%n@%m%f %F{240}%1~%f %F{cyan}❯%f '
+RPROMPT='%F{green}${vcs_info_msg_0_} %F{240}%*%f'
+
+bindkey -e
+WORDCHARS=${WORDCHARS//\/[&.;]}
+
+# Smart cd; its database remains local to this machine.
+export CDS_DB="$HOME/.local/state/cd-smart.tsv"
+source "$HOME/.zsh/cd-smart.zsh"
+
+alias grep='grep --color'
+alias cfg='git --git-dir="$HOME/.cfg" --work-tree="$HOME"'
+export PATH="$HOME/bin:$HOME/.local/bin:$PATH"
+
+# fzf, when installed
+if command -v fzf >/dev/null 2>&1; then
+ export FZF_CTRL_R_OPTS="--height=~15 --layout=reverse --no-info --no-separator --no-scrollbar --prompt='history › ' --scheme=history --with-nth 2.."
+ source <(fzf --zsh)
+fi
+
+# fzf-tab and optional plugins are loaded by the platform file when available.
--- /dev/null
+# fzf-tab: fuzzy tab completion; preview hidden by default, Ctrl-/ toggles it
+zstyle ':fzf-tab:*' fzf-flags --height=50% --preview-window=right:55%:wrap:hidden --bind=ctrl-/:toggle-preview
+zstyle ':fzf-tab:*' switch-group '<' '>'
+
+# Preview commands run in a separate shell. Quote the fzf-tab variables there,
+# rather than expanding them while this configuration is sourced.
+zstyle ':fzf-tab:complete:cd:*' fzf-preview 'CLICOLOR_FORCE=1 ls -1G -- "$realpath"'
+zstyle ':fzf-tab:complete:(cat|bat|less|vim|subl|code|cp|mv|rm|open):*' fzf-preview \
+ 'if [[ -d "$realpath" ]]; then
+ CLICOLOR_FORCE=1 ls -1G -- "$realpath"
+ elif grep -qI . -- "$realpath" 2>/dev/null; then
+ bat --color=always --style=numbers --line-range=:200 -- "$realpath"
+ else
+ file -b -- "$realpath"
+ fi'
+zstyle ':fzf-tab:complete:git-(add|diff|restore):*' fzf-preview 'git diff --color=always -- $word | head -200'
+zstyle ':fzf-tab:complete:git-(checkout|switch|merge|rebase|log):*' fzf-preview 'git log --oneline --color=always -20 $word 2>/dev/null'
+zstyle ':fzf-tab:complete:(-parameter-|-brace-parameter-|export|unset):*' fzf-preview 'echo ${(P)word}'
+unset _ls
+
+# Complete full Git file paths instead of one directory at a time.
+autoload -Uz +X _multi_parts
+functions -c _multi_parts _multi_parts_orig
+_multi_parts() {
+ [[ $curcontext == *:git-* ]] || { _multi_parts_orig "$@"; return; }
+ local -a opts
+ while [[ $1 != - ]]; do opts+=("$1"); shift; done
+ compadd "${opts[@]}" -- "${(@P)3}"
+}
--- /dev/null
+# Linux-specific Zsh settings
+
+if command -v ls >/dev/null 2>&1; then
+ alias ls='ls --color=auto'
+fi
+
+if command -v fzf-tab >/dev/null 2>&1; then
+ source "$(dirname "$(command -v fzf-tab)")/../share/fzf-tab/fzf-tab.zsh" 2>/dev/null
+fi
+
+[[ -r "$HOME/.zsh/fzf-tab.zsh" ]] && source "$HOME/.zsh/fzf-tab.zsh"
+
+for plugin in \
+ "$HOME/.local/share/zsh-autosuggestions/zsh-autosuggestions.zsh" \
+ /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh \
+ "$HOME/.local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" \
+ /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh; do
+ [[ -r "$plugin" ]] && source "$plugin"
+done
--- /dev/null
+# Copy to ~/.zsh/local.zsh for machine- or work-specific settings.
+# Keep this file out of shared/public dotfiles.
+
+# Examples:
+# AWS configuration is usually best managed in ~/.aws/config.
+# If a machine-wide default is needed, set it here with generic values:
+# export AWS_PROFILE=your-profile
+# export AWS_REGION=your-region
+#
+# Put any other machine-specific paths or environment variables here.
--- /dev/null
+# macOS-specific Zsh settings
+
+alias ls='ls -G'
+alias x86='arch -x86_64 zsh'
+
+if command -v brew >/dev/null 2>&1; then
+ eval "$(brew shellenv)"
+ if [[ -r "$(brew --prefix)/opt/fzf-tab/share/fzf-tab/fzf-tab.zsh" ]]; then
+ source "$(brew --prefix)/opt/fzf-tab/share/fzf-tab/fzf-tab.zsh"
+ [[ -r "$HOME/.zsh/fzf-tab.zsh" ]] && source "$HOME/.zsh/fzf-tab.zsh"
+ fi
+ [[ -r "$(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh" ]] && \
+ source "$(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh"
+ [[ -r "$(brew --prefix)/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" ]] && \
+ source "$(brew --prefix)/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
+fi
+
+[[ -r "$HOME/.docker/init-zsh.sh" ]] && source "$HOME/.docker/init-zsh.sh"
-if [[ "$OSTYPE" == "darwin"* ]]; then
+# Shared entry point
+source "$HOME/.zsh/common.zsh"
- # OSX
- if [[ -e ~/.zsh-darwin ]]; then
- source ~/.zsh-darwin
- fi
+if [[ "$OSTYPE" == darwin* ]]; then
+ [[ -r "$HOME/.zsh/macos.zsh" ]] && source "$HOME/.zsh/macos.zsh"
+elif [[ "$OSTYPE" == linux* ]]; then
+ [[ -r "$HOME/.zsh/linux.zsh" ]] && source "$HOME/.zsh/linux.zsh"
+fi
-else
-
- # Linux or other
- if [[ -e ~/.zsh-config ]]; then
- source ~/.zsh-config
- fi
-
- # Use zsh prompt
- if [[ -e ~/.zsh-prompt ]]; then
- source ~/.zsh-prompt
- fi
-
-fi
\ No newline at end of file
+# Optional machine- or work-specific settings.
+[[ -r "$HOME/.zsh/local.zsh" ]] && source "$HOME/.zsh/local.zsh"