├── .bashrc ├── .config ├── atuin │ └── config.toml ├── flake8 └── jj │ └── config.toml ├── .gdbinit ├── .gemrc ├── .gitconfig ├── .gitignore_global ├── .guile ├── .hgrc ├── .jline.rc ├── .sqliterc ├── .stack └── config.yaml ├── .vimrc ├── .zsh-autosuggestions.zsh ├── .zsh-histdb ├── LICENSE ├── README.org ├── db_migrations │ └── 0to2.sql ├── histdb-interactive.zsh ├── histdb-merge ├── histdb-migrate ├── sqlite-history.zsh └── zsh-histdb.plugin.zsh ├── .zsh-history-substring-search.zsh ├── .zshrc ├── bin ├── git-prune-local └── post-commit ├── make_links.py └── readme.md /.bashrc: -------------------------------------------------------------------------------- 1 | PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' 2 | 3 | # Ensure the terminal doesn't wrap around if we type long lines and 4 | # the window was narrow when we started bash. 5 | # 6 | shopt -s checkwinsize 7 | 8 | # Pressing up or down searches based on the text input so far. 9 | bind '"\e[A": history-search-backward' 10 | bind '"\e[B": history-search-forward' 11 | alias ..='cd ..' 12 | alias gst='git status' 13 | # cd to the root of a git directory. 14 | alias gcd='if [ "`git rev-parse --show-cdup`" != "" ]; then cd `git rev-parse --show-cdup`; fi' 15 | 16 | # Docker aliases 17 | alias docker-stop-all='docker stop $(docker ps -a -q)' 18 | alias docker-rm-all='docker rm $(docker ps -a -q)' 19 | 20 | 21 | -------------------------------------------------------------------------------- /.config/atuin/config.toml: -------------------------------------------------------------------------------- 1 | ## where to store your database, default is your system data directory 2 | ## linux/mac: ~/.local/share/atuin/history.db 3 | ## windows: %USERPROFILE%/.local/share/atuin/history.db 4 | # db_path = "~/.history.db" 5 | 6 | ## where to store your encryption key, default is your system data directory 7 | ## linux/mac: ~/.local/share/atuin/key 8 | ## windows: %USERPROFILE%/.local/share/atuin/key 9 | # key_path = "~/.key" 10 | 11 | ## where to store your auth session token, default is your system data directory 12 | ## linux/mac: ~/.local/share/atuin/session 13 | ## windows: %USERPROFILE%/.local/share/atuin/session 14 | # session_path = "~/.session" 15 | 16 | ## date format used, either "us" or "uk" 17 | dialect = "uk" 18 | 19 | ## default timezone to use when displaying time 20 | ## either "l", "local" to use the system's current local timezone, or an offset 21 | ## from UTC in the format of "<+|->H[H][:M[M][:S[S]]]" 22 | ## for example: "+9", "-05", "+03:30", "-01:23:45", etc. 23 | # timezone = "local" 24 | 25 | ## enable or disable automatic sync 26 | # auto_sync = true 27 | 28 | ## enable or disable automatic update checks 29 | # update_check = true 30 | 31 | ## address of the sync server 32 | # sync_address = "https://api.atuin.sh" 33 | 34 | ## how often to sync history. note that this is only triggered when a command 35 | ## is ran, so sync intervals may well be longer 36 | ## set it to 0 to sync after every command 37 | # sync_frequency = "10m" 38 | 39 | ## which search mode to use 40 | ## possible values: prefix, fulltext, fuzzy, skim 41 | search_mode = "skim" 42 | 43 | ## which filter mode to use by default 44 | ## possible values: "global", "host", "session", "directory", "workspace" 45 | ## consider using search.filters to customize the enablement and order of filter modes 46 | # filter_mode = "global" 47 | 48 | ## With workspace filtering enabled, Atuin will filter for commands executed 49 | ## in any directory within a git repository tree (default: false). 50 | ## 51 | ## To use workspace mode by default when available, set this to true and 52 | ## set filter_mode to "workspace" or leave it unspecified and 53 | ## set search.filters to include "workspace" before other filter modes. 54 | # workspaces = false 55 | 56 | ## which filter mode to use when atuin is invoked from a shell up-key binding 57 | ## the accepted values are identical to those of "filter_mode" 58 | ## leave unspecified to use same mode set in "filter_mode" 59 | # filter_mode_shell_up_key_binding = "global" 60 | 61 | ## which search mode to use when atuin is invoked from a shell up-key binding 62 | ## the accepted values are identical to those of "search_mode" 63 | ## leave unspecified to use same mode set in "search_mode" 64 | # search_mode_shell_up_key_binding = "fuzzy" 65 | 66 | ## which style to use 67 | ## possible values: auto, full, compact 68 | style = "compact" 69 | 70 | ## the maximum number of lines the interface should take up 71 | ## set it to 0 to always go full screen 72 | # inline_height = 0 73 | 74 | ## Invert the UI - put the search bar at the top , Default to `false` 75 | # invert = false 76 | 77 | ## enable or disable showing a preview of the selected command 78 | ## useful when the command is longer than the terminal width and is cut off 79 | # show_preview = true 80 | 81 | ## what to do when the escape key is pressed when searching 82 | ## possible values: return-original, return-query 83 | # exit_mode = "return-original" 84 | 85 | ## possible values: emacs, subl 86 | # word_jump_mode = "emacs" 87 | 88 | ## characters that count as a part of a word 89 | # word_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 90 | 91 | ## number of context lines to show when scrolling by pages 92 | # scroll_context_lines = 1 93 | 94 | ## use ctrl instead of alt as the shortcut modifier key for numerical UI shortcuts 95 | ## alt-0 .. alt-9 96 | # ctrl_n_shortcuts = false 97 | 98 | ## default history list format - can also be specified with the --format arg 99 | # history_format = "{time}\t{command}\t{duration}" 100 | 101 | ## prevent commands matching any of these regexes from being written to history. 102 | ## Note that these regular expressions are unanchored, i.e. if they don't start 103 | ## with ^ or end with $, they'll match anywhere in the command. 104 | ## For details on the supported regular expression syntax, see 105 | ## https://docs.rs/regex/latest/regex/#syntax 106 | # history_filter = [ 107 | # "^secret-cmd", 108 | # "^innocuous-cmd .*--secret=.+", 109 | # ] 110 | 111 | ## prevent commands run with cwd matching any of these regexes from being written 112 | ## to history. Note that these regular expressions are unanchored, i.e. if they don't 113 | ## start with ^ or end with $, they'll match anywhere in CWD. 114 | ## For details on the supported regular expression syntax, see 115 | ## https://docs.rs/regex/latest/regex/#syntax 116 | # cwd_filter = [ 117 | # "^/very/secret/area", 118 | # ] 119 | 120 | ## Configure the maximum height of the preview to show. 121 | ## Useful when you have long scripts in your history that you want to distinguish 122 | ## by more than the first few lines. 123 | # max_preview_height = 4 124 | 125 | ## Configure whether or not to show the help row, which includes the current Atuin 126 | ## version (and whether an update is available), a keymap hint, and the total 127 | ## amount of commands in your history. 128 | # show_help = true 129 | 130 | ## Configure whether or not to show tabs for search and inspect 131 | # show_tabs = true 132 | 133 | ## Configure whether or not the tabs row may be auto-hidden, which includes the current Atuin 134 | ## tab, such as Search or Inspector, and other tabs you may wish to see. This will 135 | ## only be hidden if there are fewer than this count of lines available, and does not affect the use 136 | ## of keyboard shortcuts to switch tab. 0 to never auto-hide, default is 8 (lines). 137 | ## This is ignored except in `compact` mode. 138 | # auto_hide_height = 8 139 | 140 | ## Defaults to true. This matches history against a set of default regex, and will not save it if we get a match. Defaults include 141 | ## 1. AWS key id 142 | ## 2. Github pat (old and new) 143 | ## 3. Slack oauth tokens (bot, user) 144 | ## 4. Slack webhooks 145 | ## 5. Stripe live/test keys 146 | # secrets_filter = true 147 | 148 | ## Defaults to true. If enabled, upon hitting enter Atuin will immediately execute the command. Press tab to return to the shell and edit. 149 | # This applies for new installs. Old installs will keep the old behaviour unless configured otherwise. 150 | enter_accept = false 151 | 152 | ## Defaults to "emacs". This specifies the keymap on the startup of `atuin 153 | ## search`. If this is set to "auto", the startup keymap mode in the Atuin 154 | ## search is automatically selected based on the shell's keymap where the 155 | ## keybinding is defined. If this is set to "emacs", "vim-insert", or 156 | ## "vim-normal", the startup keymap mode in the Atuin search is forced to be 157 | ## the specified one. 158 | # keymap_mode = "auto" 159 | 160 | ## Cursor style in each keymap mode. If specified, the cursor style is changed 161 | ## in entering the cursor shape. Available values are "default" and 162 | ## "{blink,steady}-{block,underline,bar}". 163 | # keymap_cursor = { emacs = "blink-block", vim_insert = "blink-block", vim_normal = "steady-block" } 164 | 165 | # network_connect_timeout = 5 166 | # network_timeout = 5 167 | 168 | ## Timeout (in seconds) for acquiring a local database connection (sqlite) 169 | # local_timeout = 5 170 | 171 | ## Set this to true and Atuin will minimize motion in the UI - timers will not update live, etc. 172 | ## Alternatively, set env NO_MOTION=true 173 | # prefers_reduced_motion = false 174 | 175 | [stats] 176 | ## Set commands where we should consider the subcommand for statistics. Eg, kubectl get vs just kubectl 177 | # common_subcommands = [ 178 | # "apt", 179 | # "cargo", 180 | # "composer", 181 | # "dnf", 182 | # "docker", 183 | # "git", 184 | # "go", 185 | # "ip", 186 | # "kubectl", 187 | # "nix", 188 | # "nmcli", 189 | # "npm", 190 | # "pecl", 191 | # "pnpm", 192 | # "podman", 193 | # "port", 194 | # "systemctl", 195 | # "tmux", 196 | # "yarn", 197 | # ] 198 | 199 | ## Set commands that should be totally stripped and ignored from stats 200 | # common_prefix = ["sudo"] 201 | 202 | ## Set commands that will be completely ignored from stats 203 | # ignored_commands = [ 204 | # "cd", 205 | # "ls", 206 | # "vi" 207 | # ] 208 | 209 | [keys] 210 | # Defaults to true. If disabled, using the up/down key won't exit the TUI when scrolled past the first/last entry. 211 | # scroll_exits = true 212 | 213 | [sync] 214 | # Enable sync v2 by default 215 | # This ensures that sync v2 is enabled for new installs only 216 | # In a later release it will become the default across the board 217 | records = true 218 | 219 | [preview] 220 | ## which preview strategy to use to calculate the preview height (respects max_preview_height). 221 | ## possible values: auto, static 222 | ## auto: length of the selected command. 223 | ## static: length of the longest command stored in the history. 224 | ## fixed: use max_preview_height as fixed height. 225 | # strategy = "auto" 226 | 227 | [daemon] 228 | ## Enables using the daemon to sync. Requires the daemon to be running in the background. Start it with `atuin daemon` 229 | # enabled = false 230 | 231 | ## How often the daemon should sync in seconds 232 | # sync_frequency = 300 233 | 234 | ## The path to the unix socket used by the daemon (on unix systems) 235 | ## linux/mac: ~/.local/share/atuin/atuin.sock 236 | ## windows: Not Supported 237 | # socket_path = "~/.local/share/atuin/atuin.sock" 238 | 239 | ## Use systemd socket activation rather than opening the given path (the path must still be correct for the client) 240 | ## linux: false 241 | ## mac/windows: Not Supported 242 | # systemd_socket = false 243 | 244 | ## The port that should be used for TCP on non unix systems 245 | # tcp_port = 8889 246 | 247 | # [theme] 248 | ## Color theme to use for rendering in the terminal. 249 | ## There are some built-in themes, including the base theme ("default"), 250 | ## "autumn" and "marine". You can add your own themes to the "./themes" subdirectory of your 251 | ## Atuin config (or ATUIN_THEME_DIR, if provided) as TOML files whose keys should be one or 252 | ## more of AlertInfo, AlertWarn, AlertError, Annotation, Base, Guidance, Important, and 253 | ## the string values as lowercase entries from this list: 254 | ## https://ogeon.github.io/docs/palette/master/palette/named/index.html 255 | ## If you provide a custom theme file, it should be called "NAME.toml" and the theme below 256 | ## should be the stem, i.e. `theme = "NAME"` for your chosen NAME. 257 | # name = "autumn" 258 | 259 | ## Whether the theme manager should output normal or extra information to help fix themes. 260 | ## Boolean, true or false. If unset, left up to the theme manager. 261 | # debug = true 262 | 263 | [search] 264 | ## The list of enabled filter modes, in order of priority. 265 | ## The "workspace" mode is skipped when not in a workspace or workspaces = false. 266 | ## Default filter mode can be overridden with the filter_mode setting. 267 | filters = [ "global", "directory", "host", "session", "workspace" ] 268 | -------------------------------------------------------------------------------- /.config/flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # pyflakes: http://flake8.pycqa.org/en/latest/user/error-codes.html 3 | # pep8: http://pep8.readthedocs.io/en/latest/intro.html#error-codes 4 | 5 | # Catch glaring errors, but don't worry about style. I work with too 6 | # many projects with differing styles. 7 | # 8 | # Ignoring: 9 | # whitespace warnings W2 10 | # comment spacing warnings E26 11 | # arithmetic spacing E226 12 | # blank lines warning E3 13 | # import ordering E402 14 | # complexity C901 15 | select = W1,W3,W5,W6,E1,E20,E21,E23,E25,E27,E5,E7,E9,F 16 | max-line-length = 200 17 | max-complexity = 15 18 | -------------------------------------------------------------------------------- /.config/jj/config.toml: -------------------------------------------------------------------------------- 1 | [user] 2 | name = "Wilfred Hughes" 3 | email = "me@wilfred.me.uk" 4 | 5 | [ui] 6 | diff.tool = ["difft", "--color=always", "$left", "$right"] 7 | -------------------------------------------------------------------------------- /.gdbinit: -------------------------------------------------------------------------------- 1 | set history save 2 | 3 | set prompt \033[32m(gdb) \033[0m 4 | 5 | set disassembly-flavor intel 6 | 7 | set debuginfod enabled on 8 | -------------------------------------------------------------------------------- /.gemrc: -------------------------------------------------------------------------------- 1 | gem: --no-rdoc --no-ri 2 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [core] 2 | editor = zile 3 | # Only use less when the output is more than one screen 4 | # https://stackoverflow.com/a/39393497/509706 5 | pager = less -XF 6 | excludesfile = ~/.gitignore_global 7 | [user] 8 | name = Wilfred Hughes 9 | email = me@wilfred.me.uk 10 | [alias] 11 | b = branch 12 | # 'New Branch' 13 | nb = checkout -b 14 | # New branch in a Worktree. 15 | nw = "!f() { git worktree add $1 -b $1; }; f" 16 | # New branch in a Worktree From a specified branch 17 | nwf = "!f() { git worktree add $2 -b $2 $1; }; f" 18 | # Existing branch in a Worktree. 19 | ew = "!f() { git worktree add $1 $1; }; f" 20 | # List Worktrees. 21 | lw = "!f() { git worktree prune && git worktree list; }; f" 22 | 23 | # Difftastic diff of most recent commit. 24 | dp = "!f() { GIT_EXTERNAL_DIFF=difft git log -p --ext-diff -n 1; }; f" 25 | 26 | # Difftastic log of commits. 27 | dlog = "!f() { : git log ; GIT_EXTERNAL_DIFF=difft git log -p --ext-diff $@; }; f" 28 | 29 | # Difftastic show. 30 | dshow = "!f() { : git show ; GIT_EXTERNAL_DIFF=difft git show --ext-diff $1; }; f" 31 | 32 | discard = reset --hard 33 | 34 | # Previous commit 35 | prev = checkout HEAD~ 36 | 37 | lol = log --graph --decorate --pretty=oneline --abbrev-commit 38 | lola = log --graph --decorate --pretty=oneline --abbrev-commit --all 39 | push-current-branch = "!f() { git push origin `git symbolic-ref HEAD`; }; f" 40 | # see http://stackoverflow.com/a/17029936/509706 41 | prune-local = !git-prune-local 42 | # http://stackoverflow.com/a/6127884/509706 43 | # Remove local branches that have merged into master or develop 44 | rm-merged = "!git branch --merged | grep -v '\\*\\|master\\|develop' | xargs -n 1 git branch -d" 45 | ls-merged = "!git branch --merged" 46 | rename-branch = branch -m 47 | delete-branch = "!f() { echo "Deleting remote branch:"; git push origin --delete $1; echo "Deleting local branch:"; git branch -d $1; }; f" 48 | co = !git checkout $(git branch --sort=-committerdate | fzf --height=20% --reverse --info=inline) 49 | up = merge @{u} --ff-only 50 | 51 | dft = difftool 52 | 53 | [color] 54 | branch = auto 55 | diff = auto 56 | status = auto 57 | [color "branch"] 58 | current = yellow reverse 59 | local = yellow 60 | remote = green 61 | [color "diff"] 62 | meta = yellow bold 63 | frag = magenta bold 64 | old = red bold 65 | new = green bold 66 | [color "status"] 67 | added = yellow 68 | changed = green 69 | untracked = cyan 70 | 71 | [help] 72 | autocorrect = 20 73 | [push] 74 | default = current 75 | recurseSubmodules = on-demand 76 | [diff] 77 | algorithm = patience 78 | mnemonicprefix = true 79 | submodule = log 80 | tool = difftastic 81 | 82 | [url "git@github.com:"] 83 | insteadOf = gh: 84 | [rerere] 85 | enabled = true 86 | [advice] 87 | statusHints = false 88 | detachedHead = false 89 | [gc] 90 | pruneExpire = 60 days 91 | [status] 92 | submoduleSummary = true 93 | [merge] 94 | conflictStyle = diff3 95 | [include] 96 | path = ~/.gitconfig.private 97 | [init] 98 | defaultBranch = main 99 | 100 | [difftool] 101 | prompt = false 102 | 103 | [difftool "difftastic"] 104 | cmd = difft "$LOCAL" "$REMOTE" 105 | 106 | [pager] 107 | difftool = true 108 | -------------------------------------------------------------------------------- /.gitignore_global: -------------------------------------------------------------------------------- 1 | TAGS 2 | cscope.* 3 | .tern-port 4 | .tramp_history 5 | .agignore 6 | .hypothesis 7 | .DS_Store 8 | .gdb_history 9 | -------------------------------------------------------------------------------- /.guile: -------------------------------------------------------------------------------- 1 | (use-modules (ice-9 readline)) 2 | (activate-readline) 3 | -------------------------------------------------------------------------------- /.hgrc: -------------------------------------------------------------------------------- 1 | # see 'hg help config' for more info 2 | [ui] 3 | # name and email 4 | username = Wilfred Hughes 5 | 6 | # We recommend enabling tweakdefaults to get slight improvements to 7 | # the UI over time. Make sure to set HGPLAIN in the environment when 8 | # writing scripts! 9 | tweakdefaults = True 10 | 11 | interface = curses 12 | 13 | # command output pagination 14 | # (see 'hg help pager' for details) 15 | # https://www.mercurial-scm.org/wiki/PagerExtension 16 | [pager] 17 | pager = LESS='FRX' less 18 | 19 | [extensions] 20 | # uncomment these lines to enable some popular extensions 21 | # (see 'hg help extensions' for more info) 22 | shelve = 23 | mq = 24 | extdiff = 25 | 26 | [extdiff] 27 | cmd.dft = difft 28 | opts.dft = --missing-as-empty 29 | -------------------------------------------------------------------------------- /.jline.rc: -------------------------------------------------------------------------------- 1 | jline.terminal=unsupported -------------------------------------------------------------------------------- /.sqliterc: -------------------------------------------------------------------------------- 1 | .headers on 2 | .mode column 3 | .separator ROW "\n" 4 | .nullvalue NULL 5 | -------------------------------------------------------------------------------- /.stack/config.yaml: -------------------------------------------------------------------------------- 1 | # This file contains default non-project-specific settings for 'stack', used 2 | # in all projects. For more information about stack's configuration, see 3 | # http://docs.haskellstack.org/en/stable/yaml_configuration/ 4 | 5 | # The following parameters are used by "stack new" to automatically fill fields 6 | # in the cabal config. We recommend uncommenting them and filling them out if 7 | # you intend to use 'stack new'. 8 | # See https://docs.haskellstack.org/en/stable/yaml_configuration/#templates 9 | templates: 10 | params: 11 | author-email: me@wilfred.me.uk 12 | author-name: Wilfred Hughes 13 | copyright: BSD 14 | github-username: wilfred 15 | -------------------------------------------------------------------------------- /.vimrc: -------------------------------------------------------------------------------- 1 | scriptencoding utf-8 2 | " ^^ Please leave the above line at the start of the file. 3 | 4 | " Default configuration file for Vim 5 | " $Header: /var/cvsroot/gentoo-x86/app-editors/vim-core/files/vimrc-r4,v 1.3 2010/04/15 19:30:32 darkside Exp $ 6 | 7 | " Written by Aron Griffis 8 | " Modified by Ryan Phillips 9 | " Modified some more by Ciaran McCreesh 10 | " Added Redhat's vimrc info by Seemant Kulleen 11 | 12 | " You can override any of these settings on a global basis via the 13 | " "/etc/vim/vimrc.local" file, and on a per-user basis via "~/.vimrc". You may 14 | " need to create these. 15 | 16 | " {{{ General settings 17 | " The following are some sensible defaults for Vim for most users. 18 | " We attempt to change as little as possible from Vim's defaults, 19 | " deviating only where it makes sense 20 | set nocompatible " Use Vim defaults (much better!) 21 | set bs=2 " Allow backspacing over everything in insert mode 22 | set ai " Always set auto-indenting on 23 | set history=50 " keep 50 lines of command history 24 | set ruler " Show the cursor position all the time 25 | 26 | set viminfo='20,\"500 " Keep a .viminfo file. 27 | 28 | " Don't use Ex mode, use Q for formatting 29 | map Q gq 30 | 31 | " When doing tab completion, give the following files lower priority. You may 32 | " wish to set 'wildignore' to completely ignore files, and 'wildmenu' to enable 33 | " enhanced tab completion. These can be done in the user vimrc file. 34 | set suffixes+=.info,.aux,.log,.dvi,.bbl,.out,.o,.lo 35 | 36 | " When displaying line numbers, don't use an annoyingly wide number column. This 37 | " doesn't enable line numbers -- :set number will do that. The value given is a 38 | " minimum width to use for the number column, not a fixed size. 39 | if v:version >= 700 40 | set numberwidth=3 41 | endif 42 | " }}} 43 | 44 | " {{{ Modeline settings 45 | " We don't allow modelines by default. See bug #14088 and bug #73715. 46 | " If you're not concerned about these, you can enable them on a per-user 47 | " basis by adding "set modeline" to your ~/.vimrc file. 48 | set nomodeline 49 | " }}} 50 | 51 | " {{{ Locale settings 52 | " Try to come up with some nice sane GUI fonts. Also try to set a sensible 53 | " value for fileencodings based upon locale. These can all be overridden in 54 | " the user vimrc file. 55 | if v:lang =~? "^ko" 56 | set fileencodings=euc-kr 57 | set guifontset=-*-*-medium-r-normal--16-*-*-*-*-*-*-* 58 | elseif v:lang =~? "^ja_JP" 59 | set fileencodings=euc-jp 60 | set guifontset=-misc-fixed-medium-r-normal--14-*-*-*-*-*-*-* 61 | elseif v:lang =~? "^zh_TW" 62 | set fileencodings=big5 63 | set guifontset=-sony-fixed-medium-r-normal--16-150-75-75-c-80-iso8859-1,-taipei-fixed-medium-r-normal--16-150-75-75-c-160-big5-0 64 | elseif v:lang =~? "^zh_CN" 65 | set fileencodings=gb2312 66 | set guifontset=*-r-* 67 | endif 68 | 69 | " If we have a BOM, always honour that rather than trying to guess. 70 | if &fileencodings !~? "ucs-bom" 71 | set fileencodings^=ucs-bom 72 | endif 73 | 74 | " Always check for UTF-8 when trying to determine encodings. 75 | if &fileencodings !~? "utf-8" 76 | " If we have to add this, the default encoding is not Unicode. 77 | " We use this fact later to revert to the default encoding in plaintext/empty 78 | " files. 79 | let g:added_fenc_utf8 = 1 80 | set fileencodings+=utf-8 81 | endif 82 | 83 | " Make sure we have a sane fallback for encoding detection 84 | if &fileencodings !~? "default" 85 | set fileencodings+=default 86 | endif 87 | " }}} 88 | 89 | " {{{ Syntax highlighting settings 90 | " Switch syntax highlighting on, when the terminal has colors 91 | " Also switch on highlighting the last used search pattern. 92 | if &t_Co > 2 || has("gui_running") 93 | syntax on 94 | set hlsearch 95 | endif 96 | " }}} 97 | 98 | " {{{ Terminal fixes 99 | if &term ==? "xterm" 100 | set t_Sb=^[4%dm 101 | set t_Sf=^[3%dm 102 | set ttymouse=xterm2 103 | endif 104 | 105 | if &term ==? "gnome" && has("eval") 106 | " Set useful keys that vim doesn't discover via termcap but are in the 107 | " builtin xterm termcap. See bug #122562. We use exec to avoid having to 108 | " include raw escapes in the file. 109 | exec "set =\eO5D" 110 | exec "set =\eO5C" 111 | endif 112 | " }}} 113 | 114 | " {{{ Filetype plugin settings 115 | " Enable plugin-provided filetype settings, but only if the ftplugin 116 | " directory exists (which it won't on livecds, for example). 117 | if isdirectory(expand("$VIMRUNTIME/ftplugin")) 118 | filetype plugin on 119 | 120 | " Uncomment the next line (or copy to your ~/.vimrc) for plugin-provided 121 | " indent settings. Some people don't like these, so we won't turn them on by 122 | " default. 123 | " filetype indent on 124 | endif 125 | " }}} 126 | 127 | " {{{ Our default /bin/sh is bash, not ksh, so syntax highlighting for .sh 128 | " files should default to bash. See :help sh-syntax and bug #101819. 129 | if has("eval") 130 | let is_bash=1 131 | endif 132 | " }}} 133 | 134 | " {{{ Autocommands 135 | if has("autocmd") 136 | 137 | augroup gentoo 138 | au! 139 | 140 | " Gentoo-specific settings for ebuilds. These are the federally-mandated 141 | " required tab settings. See the following for more information: 142 | " http://www.gentoo.org/proj/en/devrel/handbook/handbook.xml 143 | " Note that the rules below are very minimal and don't cover everything. 144 | " Better to emerge app-vim/gentoo-syntax, which provides full syntax, 145 | " filetype and indent settings for all things Gentoo. 146 | au BufRead,BufNewFile *.e{build,class} let is_bash=1|setfiletype sh 147 | au BufRead,BufNewFile *.e{build,class} set ts=4 sw=4 noexpandtab 148 | 149 | " In text files, limit the width of text to 78 characters, but be careful 150 | " that we don't override the user's setting. 151 | autocmd BufNewFile,BufRead *.txt 152 | \ if &tw == 0 && ! exists("g:leave_my_textwidth_alone") | 153 | \ setlocal textwidth=78 | 154 | \ endif 155 | 156 | " When editing a file, always jump to the last cursor position 157 | autocmd BufReadPost * 158 | \ if ! exists("g:leave_my_cursor_position_alone") | 159 | \ if line("'\"") > 0 && line ("'\"") <= line("$") | 160 | \ exe "normal g'\"" | 161 | \ endif | 162 | \ endif 163 | 164 | " When editing a crontab file, set backupcopy to yes rather than auto. See 165 | " :help crontab and bug #53437. 166 | autocmd FileType crontab set backupcopy=yes 167 | 168 | " If we previously detected that the default encoding is not UTF-8 169 | " (g:added_fenc_utf8), assume that a file with only ASCII characters (or no 170 | " characters at all) isn't a Unicode file, but is in the default encoding. 171 | " Except of course if a byte-order mark is in effect. 172 | autocmd BufReadPost * 173 | \ if exists("g:added_fenc_utf8") && &fileencoding == "utf-8" && 174 | \ ! &bomb && search('[\x80-\xFF]','nw') == 0 && &modifiable | 175 | \ set fileencoding= | 176 | \ endif 177 | 178 | augroup END 179 | 180 | endif " has("autocmd") 181 | " }}} 182 | 183 | " vim: set fenc=utf-8 tw=80 sw=2 sts=2 et foldmethod=marker : 184 | 185 | set showcmd 186 | -------------------------------------------------------------------------------- /.zsh-autosuggestions.zsh: -------------------------------------------------------------------------------- 1 | # Fish-like fast/unobtrusive autosuggestions for zsh. 2 | # https://github.com/zsh-users/zsh-autosuggestions 3 | # v0.7.0 4 | # Copyright (c) 2013 Thiago de Arruda 5 | # Copyright (c) 2016-2021 Eric Freese 6 | # 7 | # Permission is hereby granted, free of charge, to any person 8 | # obtaining a copy of this software and associated documentation 9 | # files (the "Software"), to deal in the Software without 10 | # restriction, including without limitation the rights to use, 11 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the 13 | # Software is furnished to do so, subject to the following 14 | # conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | # OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | #--------------------------------------------------------------------# 29 | # Global Configuration Variables # 30 | #--------------------------------------------------------------------# 31 | 32 | # Color to use when highlighting suggestion 33 | # Uses format of `region_highlight` 34 | # More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets 35 | (( ! ${+ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE} )) && 36 | typeset -g ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' 37 | 38 | # Prefix to use when saving original versions of bound widgets 39 | (( ! ${+ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX} )) && 40 | typeset -g ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- 41 | 42 | # Strategies to use to fetch a suggestion 43 | # Will try each strategy in order until a suggestion is returned 44 | (( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && { 45 | typeset -ga ZSH_AUTOSUGGEST_STRATEGY 46 | ZSH_AUTOSUGGEST_STRATEGY=(history) 47 | } 48 | 49 | # Widgets that clear the suggestion 50 | (( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && { 51 | typeset -ga ZSH_AUTOSUGGEST_CLEAR_WIDGETS 52 | ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( 53 | history-search-forward 54 | history-search-backward 55 | history-beginning-search-forward 56 | history-beginning-search-backward 57 | history-substring-search-up 58 | history-substring-search-down 59 | up-line-or-beginning-search 60 | down-line-or-beginning-search 61 | up-line-or-history 62 | down-line-or-history 63 | accept-line 64 | copy-earlier-word 65 | ) 66 | } 67 | 68 | # Widgets that accept the entire suggestion 69 | (( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && { 70 | typeset -ga ZSH_AUTOSUGGEST_ACCEPT_WIDGETS 71 | ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( 72 | forward-char 73 | end-of-line 74 | vi-forward-char 75 | vi-end-of-line 76 | vi-add-eol 77 | ) 78 | } 79 | 80 | # Widgets that accept the entire suggestion and execute it 81 | (( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && { 82 | typeset -ga ZSH_AUTOSUGGEST_EXECUTE_WIDGETS 83 | ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( 84 | ) 85 | } 86 | 87 | # Widgets that accept the suggestion as far as the cursor moves 88 | (( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && { 89 | typeset -ga ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS 90 | ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( 91 | forward-word 92 | emacs-forward-word 93 | vi-forward-word 94 | vi-forward-word-end 95 | vi-forward-blank-word 96 | vi-forward-blank-word-end 97 | vi-find-next-char 98 | vi-find-next-char-skip 99 | ) 100 | } 101 | 102 | # Widgets that should be ignored (globbing supported but must be escaped) 103 | (( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && { 104 | typeset -ga ZSH_AUTOSUGGEST_IGNORE_WIDGETS 105 | ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( 106 | orig-\* 107 | beep 108 | run-help 109 | set-local-history 110 | which-command 111 | yank 112 | yank-pop 113 | zle-\* 114 | ) 115 | } 116 | 117 | # Pty name for capturing completions for completion suggestion strategy 118 | (( ! ${+ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME} )) && 119 | typeset -g ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty 120 | 121 | #--------------------------------------------------------------------# 122 | # Utility Functions # 123 | #--------------------------------------------------------------------# 124 | 125 | _zsh_autosuggest_escape_command() { 126 | setopt localoptions EXTENDED_GLOB 127 | 128 | # Escape special chars in the string (requires EXTENDED_GLOB) 129 | echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}" 130 | } 131 | 132 | #--------------------------------------------------------------------# 133 | # Widget Helpers # 134 | #--------------------------------------------------------------------# 135 | 136 | _zsh_autosuggest_incr_bind_count() { 137 | typeset -gi bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]+1)) 138 | _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=$bind_count 139 | } 140 | 141 | # Bind a single widget to an autosuggest widget, saving a reference to the original widget 142 | _zsh_autosuggest_bind_widget() { 143 | typeset -gA _ZSH_AUTOSUGGEST_BIND_COUNTS 144 | 145 | local widget=$1 146 | local autosuggest_action=$2 147 | local prefix=$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX 148 | 149 | local -i bind_count 150 | 151 | # Save a reference to the original widget 152 | case $widgets[$widget] in 153 | # Already bound 154 | user:_zsh_autosuggest_(bound|orig)_*) 155 | bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$widget])) 156 | ;; 157 | 158 | # User-defined widget 159 | user:*) 160 | _zsh_autosuggest_incr_bind_count $widget 161 | zle -N $prefix$bind_count-$widget ${widgets[$widget]#*:} 162 | ;; 163 | 164 | # Built-in widget 165 | builtin) 166 | _zsh_autosuggest_incr_bind_count $widget 167 | eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }" 168 | zle -N $prefix$bind_count-$widget _zsh_autosuggest_orig_$widget 169 | ;; 170 | 171 | # Completion widget 172 | completion:*) 173 | _zsh_autosuggest_incr_bind_count $widget 174 | eval "zle -C $prefix$bind_count-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" 175 | ;; 176 | esac 177 | 178 | # Pass the original widget's name explicitly into the autosuggest 179 | # function. Use this passed in widget name to call the original 180 | # widget instead of relying on the $WIDGET variable being set 181 | # correctly. $WIDGET cannot be trusted because other plugins call 182 | # zle without the `-w` flag (e.g. `zle self-insert` instead of 183 | # `zle self-insert -w`). 184 | eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() { 185 | _zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@ 186 | }" 187 | 188 | # Create the bound widget 189 | zle -N -- $widget _zsh_autosuggest_bound_${bind_count}_$widget 190 | } 191 | 192 | # Map all configured widgets to the right autosuggest widgets 193 | _zsh_autosuggest_bind_widgets() { 194 | emulate -L zsh 195 | 196 | local widget 197 | local ignore_widgets 198 | 199 | ignore_widgets=( 200 | .\* 201 | _\* 202 | ${_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS/#/autosuggest-} 203 | $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\* 204 | $ZSH_AUTOSUGGEST_IGNORE_WIDGETS 205 | ) 206 | 207 | # Find every widget we might want to bind and bind it appropriately 208 | for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do 209 | if [[ -n ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]]; then 210 | _zsh_autosuggest_bind_widget $widget clear 211 | elif [[ -n ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]]; then 212 | _zsh_autosuggest_bind_widget $widget accept 213 | elif [[ -n ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]]; then 214 | _zsh_autosuggest_bind_widget $widget execute 215 | elif [[ -n ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]]; then 216 | _zsh_autosuggest_bind_widget $widget partial_accept 217 | else 218 | # Assume any unspecified widget might modify the buffer 219 | _zsh_autosuggest_bind_widget $widget modify 220 | fi 221 | done 222 | } 223 | 224 | # Given the name of an original widget and args, invoke it, if it exists 225 | _zsh_autosuggest_invoke_original_widget() { 226 | # Do nothing unless called with at least one arg 227 | (( $# )) || return 0 228 | 229 | local original_widget_name="$1" 230 | 231 | shift 232 | 233 | if (( ${+widgets[$original_widget_name]} )); then 234 | zle $original_widget_name -- $@ 235 | fi 236 | } 237 | 238 | #--------------------------------------------------------------------# 239 | # Highlighting # 240 | #--------------------------------------------------------------------# 241 | 242 | # If there was a highlight, remove it 243 | _zsh_autosuggest_highlight_reset() { 244 | typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT 245 | 246 | if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then 247 | region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}") 248 | unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT 249 | fi 250 | } 251 | 252 | # If there's a suggestion, highlight it 253 | _zsh_autosuggest_highlight_apply() { 254 | typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT 255 | 256 | if (( $#POSTDISPLAY )); then 257 | typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE" 258 | region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT") 259 | else 260 | unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT 261 | fi 262 | } 263 | 264 | #--------------------------------------------------------------------# 265 | # Autosuggest Widget Implementations # 266 | #--------------------------------------------------------------------# 267 | 268 | # Disable suggestions 269 | _zsh_autosuggest_disable() { 270 | typeset -g _ZSH_AUTOSUGGEST_DISABLED 271 | _zsh_autosuggest_clear 272 | } 273 | 274 | # Enable suggestions 275 | _zsh_autosuggest_enable() { 276 | unset _ZSH_AUTOSUGGEST_DISABLED 277 | 278 | if (( $#BUFFER )); then 279 | _zsh_autosuggest_fetch 280 | fi 281 | } 282 | 283 | # Toggle suggestions (enable/disable) 284 | _zsh_autosuggest_toggle() { 285 | if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then 286 | _zsh_autosuggest_enable 287 | else 288 | _zsh_autosuggest_disable 289 | fi 290 | } 291 | 292 | # Clear the suggestion 293 | _zsh_autosuggest_clear() { 294 | # Remove the suggestion 295 | unset POSTDISPLAY 296 | 297 | _zsh_autosuggest_invoke_original_widget $@ 298 | } 299 | 300 | # Modify the buffer and get a new suggestion 301 | _zsh_autosuggest_modify() { 302 | local -i retval 303 | 304 | # Only available in zsh >= 5.4 305 | local -i KEYS_QUEUED_COUNT 306 | 307 | # Save the contents of the buffer/postdisplay 308 | local orig_buffer="$BUFFER" 309 | local orig_postdisplay="$POSTDISPLAY" 310 | 311 | # Clear suggestion while waiting for next one 312 | unset POSTDISPLAY 313 | 314 | # Original widget may modify the buffer 315 | _zsh_autosuggest_invoke_original_widget $@ 316 | retval=$? 317 | 318 | emulate -L zsh 319 | 320 | # Don't fetch a new suggestion if there's more input to be read immediately 321 | if (( $PENDING > 0 || $KEYS_QUEUED_COUNT > 0 )); then 322 | POSTDISPLAY="$orig_postdisplay" 323 | return $retval 324 | fi 325 | 326 | # Optimize if manually typing in the suggestion or if buffer hasn't changed 327 | if [[ "$BUFFER" = "$orig_buffer"* && "$orig_postdisplay" = "${BUFFER:$#orig_buffer}"* ]]; then 328 | POSTDISPLAY="${orig_postdisplay:$(($#BUFFER - $#orig_buffer))}" 329 | return $retval 330 | fi 331 | 332 | # Bail out if suggestions are disabled 333 | if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then 334 | return $? 335 | fi 336 | 337 | # Get a new suggestion if the buffer is not empty after modification 338 | if (( $#BUFFER > 0 )); then 339 | if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then 340 | _zsh_autosuggest_fetch 341 | fi 342 | fi 343 | 344 | return $retval 345 | } 346 | 347 | # Fetch a new suggestion based on what's currently in the buffer 348 | _zsh_autosuggest_fetch() { 349 | if (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); then 350 | _zsh_autosuggest_async_request "$BUFFER" 351 | else 352 | local suggestion 353 | _zsh_autosuggest_fetch_suggestion "$BUFFER" 354 | _zsh_autosuggest_suggest "$suggestion" 355 | fi 356 | } 357 | 358 | # Offer a suggestion 359 | _zsh_autosuggest_suggest() { 360 | emulate -L zsh 361 | 362 | local suggestion="$1" 363 | 364 | if [[ -n "$suggestion" ]] && (( $#BUFFER )); then 365 | POSTDISPLAY="${suggestion#$BUFFER}" 366 | else 367 | unset POSTDISPLAY 368 | fi 369 | } 370 | 371 | # Accept the entire suggestion 372 | _zsh_autosuggest_accept() { 373 | local -i retval max_cursor_pos=$#BUFFER 374 | 375 | # When vicmd keymap is active, the cursor can't move all the way 376 | # to the end of the buffer 377 | if [[ "$KEYMAP" = "vicmd" ]]; then 378 | max_cursor_pos=$((max_cursor_pos - 1)) 379 | fi 380 | 381 | # If we're not in a valid state to accept a suggestion, just run the 382 | # original widget and bail out 383 | if (( $CURSOR != $max_cursor_pos || !$#POSTDISPLAY )); then 384 | _zsh_autosuggest_invoke_original_widget $@ 385 | return 386 | fi 387 | 388 | # Only accept if the cursor is at the end of the buffer 389 | # Add the suggestion to the buffer 390 | BUFFER="$BUFFER$POSTDISPLAY" 391 | 392 | # Remove the suggestion 393 | unset POSTDISPLAY 394 | 395 | # Run the original widget before manually moving the cursor so that the 396 | # cursor movement doesn't make the widget do something unexpected 397 | _zsh_autosuggest_invoke_original_widget $@ 398 | retval=$? 399 | 400 | # Move the cursor to the end of the buffer 401 | if [[ "$KEYMAP" = "vicmd" ]]; then 402 | CURSOR=$(($#BUFFER - 1)) 403 | else 404 | CURSOR=$#BUFFER 405 | fi 406 | 407 | return $retval 408 | } 409 | 410 | # Accept the entire suggestion and execute it 411 | _zsh_autosuggest_execute() { 412 | # Add the suggestion to the buffer 413 | BUFFER="$BUFFER$POSTDISPLAY" 414 | 415 | # Remove the suggestion 416 | unset POSTDISPLAY 417 | 418 | # Call the original `accept-line` to handle syntax highlighting or 419 | # other potential custom behavior 420 | _zsh_autosuggest_invoke_original_widget "accept-line" 421 | } 422 | 423 | # Partially accept the suggestion 424 | _zsh_autosuggest_partial_accept() { 425 | local -i retval cursor_loc 426 | 427 | # Save the contents of the buffer so we can restore later if needed 428 | local original_buffer="$BUFFER" 429 | 430 | # Temporarily accept the suggestion. 431 | BUFFER="$BUFFER$POSTDISPLAY" 432 | 433 | # Original widget moves the cursor 434 | _zsh_autosuggest_invoke_original_widget $@ 435 | retval=$? 436 | 437 | # Normalize cursor location across vi/emacs modes 438 | cursor_loc=$CURSOR 439 | if [[ "$KEYMAP" = "vicmd" ]]; then 440 | cursor_loc=$((cursor_loc + 1)) 441 | fi 442 | 443 | # If we've moved past the end of the original buffer 444 | if (( $cursor_loc > $#original_buffer )); then 445 | # Set POSTDISPLAY to text right of the cursor 446 | POSTDISPLAY="${BUFFER[$(($cursor_loc + 1)),$#BUFFER]}" 447 | 448 | # Clip the buffer at the cursor 449 | BUFFER="${BUFFER[1,$cursor_loc]}" 450 | else 451 | # Restore the original buffer 452 | BUFFER="$original_buffer" 453 | fi 454 | 455 | return $retval 456 | } 457 | 458 | () { 459 | typeset -ga _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS 460 | 461 | _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS=( 462 | clear 463 | fetch 464 | suggest 465 | accept 466 | execute 467 | enable 468 | disable 469 | toggle 470 | ) 471 | 472 | local action 473 | for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS modify partial_accept; do 474 | eval "_zsh_autosuggest_widget_$action() { 475 | local -i retval 476 | 477 | _zsh_autosuggest_highlight_reset 478 | 479 | _zsh_autosuggest_$action \$@ 480 | retval=\$? 481 | 482 | _zsh_autosuggest_highlight_apply 483 | 484 | zle -R 485 | 486 | return \$retval 487 | }" 488 | done 489 | 490 | for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS; do 491 | zle -N autosuggest-$action _zsh_autosuggest_widget_$action 492 | done 493 | } 494 | 495 | #--------------------------------------------------------------------# 496 | # Completion Suggestion Strategy # 497 | #--------------------------------------------------------------------# 498 | # Fetches a suggestion from the completion engine 499 | # 500 | 501 | _zsh_autosuggest_capture_postcompletion() { 502 | # Always insert the first completion into the buffer 503 | compstate[insert]=1 504 | 505 | # Don't list completions 506 | unset 'compstate[list]' 507 | } 508 | 509 | _zsh_autosuggest_capture_completion_widget() { 510 | # Add a post-completion hook to be called after all completions have been 511 | # gathered. The hook can modify compstate to affect what is done with the 512 | # gathered completions. 513 | local -a +h comppostfuncs 514 | comppostfuncs=(_zsh_autosuggest_capture_postcompletion) 515 | 516 | # Only capture completions at the end of the buffer 517 | CURSOR=$#BUFFER 518 | 519 | # Run the original widget wrapping `.complete-word` so we don't 520 | # recursively try to fetch suggestions, since our pty is forked 521 | # after autosuggestions is initialized. 522 | zle -- ${(k)widgets[(r)completion:.complete-word:_main_complete]} 523 | 524 | if is-at-least 5.0.3; then 525 | # Don't do any cr/lf transformations. We need to do this immediately before 526 | # output because if we do it in setup, onlcr will be re-enabled when we enter 527 | # vared in the async code path. There is a bug in zpty module in older versions 528 | # where the tty is not properly attached to the pty slave, resulting in stty 529 | # getting stopped with a SIGTTOU. See zsh-workers thread 31660 and upstream 530 | # commit f75904a38 531 | stty -onlcr -ocrnl -F /dev/tty 532 | fi 533 | 534 | # The completion has been added, print the buffer as the suggestion 535 | echo -nE - $'\0'$BUFFER$'\0' 536 | } 537 | 538 | zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget 539 | 540 | _zsh_autosuggest_capture_setup() { 541 | # There is a bug in zpty module in older zsh versions by which a 542 | # zpty that exits will kill all zpty processes that were forked 543 | # before it. Here we set up a zsh exit hook to SIGKILL the zpty 544 | # process immediately, before it has a chance to kill any other 545 | # zpty processes. 546 | if ! is-at-least 5.4; then 547 | zshexit() { 548 | # The zsh builtin `kill` fails sometimes in older versions 549 | # https://unix.stackexchange.com/a/477647/156673 550 | kill -KILL $$ 2>&- || command kill -KILL $$ 551 | 552 | # Block for long enough for the signal to come through 553 | sleep 1 554 | } 555 | fi 556 | 557 | # Try to avoid any suggestions that wouldn't match the prefix 558 | zstyle ':completion:*' matcher-list '' 559 | zstyle ':completion:*' path-completion false 560 | zstyle ':completion:*' max-errors 0 not-numeric 561 | 562 | bindkey '^I' autosuggest-capture-completion 563 | } 564 | 565 | _zsh_autosuggest_capture_completion_sync() { 566 | _zsh_autosuggest_capture_setup 567 | 568 | zle autosuggest-capture-completion 569 | } 570 | 571 | _zsh_autosuggest_capture_completion_async() { 572 | _zsh_autosuggest_capture_setup 573 | 574 | zmodload zsh/parameter 2>/dev/null || return # For `$functions` 575 | 576 | # Make vared completion work as if for a normal command line 577 | # https://stackoverflow.com/a/7057118/154703 578 | autoload +X _complete 579 | functions[_original_complete]=$functions[_complete] 580 | function _complete() { 581 | unset 'compstate[vared]' 582 | _original_complete "$@" 583 | } 584 | 585 | # Open zle with buffer set so we can capture completions for it 586 | vared 1 587 | } 588 | 589 | _zsh_autosuggest_strategy_completion() { 590 | # Reset options to defaults and enable LOCAL_OPTIONS 591 | emulate -L zsh 592 | 593 | # Enable extended glob for completion ignore pattern 594 | setopt EXTENDED_GLOB 595 | 596 | typeset -g suggestion 597 | local line REPLY 598 | 599 | # Exit if we don't have completions 600 | whence compdef >/dev/null || return 601 | 602 | # Exit if we don't have zpty 603 | zmodload zsh/zpty 2>/dev/null || return 604 | 605 | # Exit if our search string matches the ignore pattern 606 | [[ -n "$ZSH_AUTOSUGGEST_COMPLETION_IGNORE" ]] && [[ "$1" == $~ZSH_AUTOSUGGEST_COMPLETION_IGNORE ]] && return 607 | 608 | # Zle will be inactive if we are in async mode 609 | if zle; then 610 | zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_sync 611 | else 612 | zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_async "\$1" 613 | zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME $'\t' 614 | fi 615 | 616 | { 617 | # The completion result is surrounded by null bytes, so read the 618 | # content between the first two null bytes. 619 | zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0''*'$'\0' 620 | 621 | # Extract the suggestion from between the null bytes. On older 622 | # versions of zsh (older than 5.3), we sometimes get extra bytes after 623 | # the second null byte, so trim those off the end. 624 | # See http://www.zsh.org/mla/workers/2015/msg03290.html 625 | suggestion="${${(@0)line}[2]}" 626 | } always { 627 | # Destroy the pty 628 | zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME 629 | } 630 | } 631 | 632 | #--------------------------------------------------------------------# 633 | # History Suggestion Strategy # 634 | #--------------------------------------------------------------------# 635 | # Suggests the most recent history item that matches the given 636 | # prefix. 637 | # 638 | 639 | _zsh_autosuggest_strategy_history() { 640 | # Reset options to defaults and enable LOCAL_OPTIONS 641 | emulate -L zsh 642 | 643 | # Enable globbing flags so that we can use (#m) and (x~y) glob operator 644 | setopt EXTENDED_GLOB 645 | 646 | # Escape backslashes and all of the glob operators so we can use 647 | # this string as a pattern to search the $history associative array. 648 | # - (#m) globbing flag enables setting references for match data 649 | # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 650 | local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" 651 | 652 | # Get the history items that match the prefix, excluding those that match 653 | # the ignore pattern 654 | local pattern="$prefix*" 655 | if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then 656 | pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" 657 | fi 658 | 659 | # Give the first history item matching the pattern as the suggestion 660 | # - (r) subscript flag makes the pattern match on values 661 | typeset -g suggestion="${history[(r)$pattern]}" 662 | } 663 | 664 | #--------------------------------------------------------------------# 665 | # Match Previous Command Suggestion Strategy # 666 | #--------------------------------------------------------------------# 667 | # Suggests the most recent history item that matches the given 668 | # prefix and whose preceding history item also matches the most 669 | # recently executed command. 670 | # 671 | # For example, suppose your history has the following entries: 672 | # - pwd 673 | # - ls foo 674 | # - ls bar 675 | # - pwd 676 | # 677 | # Given the history list above, when you type 'ls', the suggestion 678 | # will be 'ls foo' rather than 'ls bar' because your most recently 679 | # executed command (pwd) was previously followed by 'ls foo'. 680 | # 681 | # Note that this strategy won't work as expected with ZSH options that don't 682 | # preserve the history order such as `HIST_IGNORE_ALL_DUPS` or 683 | # `HIST_EXPIRE_DUPS_FIRST`. 684 | 685 | _zsh_autosuggest_strategy_match_prev_cmd() { 686 | # Reset options to defaults and enable LOCAL_OPTIONS 687 | emulate -L zsh 688 | 689 | # Enable globbing flags so that we can use (#m) and (x~y) glob operator 690 | setopt EXTENDED_GLOB 691 | 692 | # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 693 | local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" 694 | 695 | # Get the history items that match the prefix, excluding those that match 696 | # the ignore pattern 697 | local pattern="$prefix*" 698 | if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then 699 | pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" 700 | fi 701 | 702 | # Get all history event numbers that correspond to history 703 | # entries that match the pattern 704 | local history_match_keys 705 | history_match_keys=(${(k)history[(R)$~pattern]}) 706 | 707 | # By default we use the first history number (most recent history entry) 708 | local histkey="${history_match_keys[1]}" 709 | 710 | # Get the previously executed command 711 | local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")" 712 | 713 | # Iterate up to the first 200 history event numbers that match $prefix 714 | for key in "${(@)history_match_keys[1,200]}"; do 715 | # Stop if we ran out of history 716 | [[ $key -gt 1 ]] || break 717 | 718 | # See if the history entry preceding the suggestion matches the 719 | # previous command, and use it if it does 720 | if [[ "${history[$((key - 1))]}" == "$prev_cmd" ]]; then 721 | histkey="$key" 722 | break 723 | fi 724 | done 725 | 726 | # Give back the matched history entry 727 | typeset -g suggestion="$history[$histkey]" 728 | } 729 | 730 | #--------------------------------------------------------------------# 731 | # Fetch Suggestion # 732 | #--------------------------------------------------------------------# 733 | # Loops through all specified strategies and returns a suggestion 734 | # from the first strategy to provide one. 735 | # 736 | 737 | _zsh_autosuggest_fetch_suggestion() { 738 | typeset -g suggestion 739 | local -a strategies 740 | local strategy 741 | 742 | # Ensure we are working with an array 743 | strategies=(${=ZSH_AUTOSUGGEST_STRATEGY}) 744 | 745 | for strategy in $strategies; do 746 | # Try to get a suggestion from this strategy 747 | _zsh_autosuggest_strategy_$strategy "$1" 748 | 749 | # Ensure the suggestion matches the prefix 750 | [[ "$suggestion" != "$1"* ]] && unset suggestion 751 | 752 | # Break once we've found a valid suggestion 753 | [[ -n "$suggestion" ]] && break 754 | done 755 | } 756 | 757 | #--------------------------------------------------------------------# 758 | # Async # 759 | #--------------------------------------------------------------------# 760 | 761 | _zsh_autosuggest_async_request() { 762 | zmodload zsh/system 2>/dev/null # For `$sysparams` 763 | 764 | typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID 765 | 766 | # If we've got a pending request, cancel it 767 | if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then 768 | # Close the file descriptor and remove the handler 769 | exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- 770 | zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD 771 | 772 | # We won't know the pid unless the user has zsh/system module installed 773 | if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then 774 | # Zsh will make a new process group for the child process only if job 775 | # control is enabled (MONITOR option) 776 | if [[ -o MONITOR ]]; then 777 | # Send the signal to the process group to kill any processes that may 778 | # have been forked by the suggestion strategy 779 | kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null 780 | else 781 | # Kill just the child process since it wasn't placed in a new process 782 | # group. If the suggestion strategy forked any child processes they may 783 | # be orphaned and left behind. 784 | kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null 785 | fi 786 | fi 787 | fi 788 | 789 | # Fork a process to fetch a suggestion and open a pipe to read from it 790 | exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( 791 | # Tell parent process our pid 792 | echo $sysparams[pid] 793 | 794 | # Fetch and print the suggestion 795 | local suggestion 796 | _zsh_autosuggest_fetch_suggestion "$1" 797 | echo -nE "$suggestion" 798 | ) 799 | 800 | # There's a weird bug here where ^C stops working unless we force a fork 801 | # See https://github.com/zsh-users/zsh-autosuggestions/issues/364 802 | autoload -Uz is-at-least 803 | is-at-least 5.8 || command true 804 | 805 | # Read the pid from the child process 806 | read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD 807 | 808 | # When the fd is readable, call the response handler 809 | zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response 810 | } 811 | 812 | # Called when new data is ready to be read from the pipe 813 | # First arg will be fd ready for reading 814 | # Second arg will be passed in case of error 815 | _zsh_autosuggest_async_response() { 816 | emulate -L zsh 817 | 818 | local suggestion 819 | 820 | if [[ -z "$2" || "$2" == "hup" ]]; then 821 | # Read everything from the fd and give it as a suggestion 822 | IFS='' read -rd '' -u $1 suggestion 823 | zle autosuggest-suggest -- "$suggestion" 824 | 825 | # Close the fd 826 | exec {1}<&- 827 | fi 828 | 829 | # Always remove the handler 830 | zle -F "$1" 831 | } 832 | 833 | #--------------------------------------------------------------------# 834 | # Start # 835 | #--------------------------------------------------------------------# 836 | 837 | # Start the autosuggestion widgets 838 | _zsh_autosuggest_start() { 839 | # By default we re-bind widgets on every precmd to ensure we wrap other 840 | # wrappers. Specifically, highlighting breaks if our widgets are wrapped by 841 | # zsh-syntax-highlighting widgets. This also allows modifications to the 842 | # widget list variables to take effect on the next precmd. However this has 843 | # a decent performance hit, so users can set ZSH_AUTOSUGGEST_MANUAL_REBIND 844 | # to disable the automatic re-binding. 845 | if (( ${+ZSH_AUTOSUGGEST_MANUAL_REBIND} )); then 846 | add-zsh-hook -d precmd _zsh_autosuggest_start 847 | fi 848 | 849 | _zsh_autosuggest_bind_widgets 850 | } 851 | 852 | # Mark for auto-loading the functions that we use 853 | autoload -Uz add-zsh-hook is-at-least 854 | 855 | # Automatically enable asynchronous mode in newer versions of zsh. Disable for 856 | # older versions because there is a bug when using async mode where ^C does not 857 | # work immediately after fetching a suggestion. 858 | # See https://github.com/zsh-users/zsh-autosuggestions/issues/364 859 | if is-at-least 5.0.8; then 860 | typeset -g ZSH_AUTOSUGGEST_USE_ASYNC= 861 | fi 862 | 863 | # Start the autosuggestion widgets on the next precmd 864 | add-zsh-hook precmd _zsh_autosuggest_start 865 | -------------------------------------------------------------------------------- /.zsh-histdb/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Tom Hinton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.zsh-histdb/README.org: -------------------------------------------------------------------------------- 1 | #+TITLE:ZSH History Database 2 | 3 | * News 4 | - 13/10/21 :: Thanks to Aloxaf some subshell invocations have been removed which should make things quicker. Thanks to m42e (again) ~histdb-sync~ uses the remote database IDs as the canonical ones which should make syncing a bit less thrashy. Thanks to Chad Transtrum we use ~builtin which~ rather than ~which~, for systems which have an unusual which (?!), and an improvement to examples below in the README. Thanks to Klaus Ethgen the invocation of ~sqlite3~ is now unaffected by some potential confusions in your sqlite rc files. 5 | - 30/06/20 :: Thanks to rolandwalker, add-zsh-hook is used so histdb is a better citizen. 6 | Thanks to GreenArchon and phiresky the sqlite helper process is terminated on exit better, and the WAL is truncated before doing histdb sync. This should make things behave a bit better. Thanks to gabreal (and others, I think), some things have been changed to ~declare -ga~ which helps when using antigen or somesuch? Thanks to sheperdjerred and fuero there is now a file which might make antigen and oh-my-zsh work. 7 | 8 | There is a *breaking change*, which is that you no longer need to ~add-zsh-hook precmd histdb-update-outcome~ in your rc file. This now happens when you source ~sqlite-history.zsh~. 9 | - 11/03/20 :: Thanks to phiresky (https://github.com/phiresky) history appends within a shell session are performed through a single long-running sqlite process rather than by starting a new process per history append. This reduces contention between shells that are trying to write, as sqlite always fsyncs on exit. 10 | - 29/05/19 :: Thanks to Matthias Bilger (https://github.com/m42e/) a bug has been removed which would have broken the database if the vacuum command were used. Turns out, you can't use rowid as a foreign key unless you've given it a name. As a side-effect your database will need updating, in a non-backwards compatible way, so you'll need to update on all your installations at once if you share a history file. 11 | Also, it's not impossible that this change will make a problem for someone somewhere, so be careful with this update. 12 | 13 | Also thanks to Matthias, the exit status of long-running commands is handled better. 14 | - 05/04/18 :: I've done a bit of work to make a replacement reverse-isearch function, which is in a usable state now. 15 | 16 | If you want to use it, see the [[Reverse isearch]] section below which now covers it. 17 | 18 | - 09/09/17 :: If you have already installed and you want to get the right timings in the database, see the installation section again. Fix to issue #18. 19 | 20 | * What is this 21 | 22 | This is a small bit of zsh code that stores your history into a sqlite3 database. 23 | It improves on the normal history by storing, for each history command: 24 | 25 | - The start and stop times of the command 26 | - The working directory where the command was run 27 | - The hostname of the machine 28 | - A unique per-host session ID, so history from several sessions is not confused 29 | - The exit status of the command 30 | 31 | It is also possible to merge multiple history databases together without conflict, so long as all your machines have different hostnames. 32 | 33 | * Installation 34 | 35 | You will need ~sqlite3~ and the usual coreutils commands installed on your ~PATH~. 36 | To load and activate history recording you need to source ~sqlite-history.zsh~ from your shell in your zsh startup files. 37 | 38 | Example for installing in ~$HOME/.oh-my-zsh/custom/plugins/zsh-histdb~ (note that ~oh-my-zsh~ is not required): 39 | 40 | #+BEGIN_SRC zsh 41 | mkdir -p $HOME/.oh-my-zsh/custom/plugins/ 42 | git clone https://github.com/larkery/zsh-histdb $HOME/.oh-my-zsh/custom/plugins/zsh-histdb 43 | #+END_SRC 44 | 45 | Add this to your ~$HOME/.zshrc~: 46 | 47 | #+BEGIN_SRC zsh 48 | source $HOME/.oh-my-zsh/custom/plugins/zsh-histdb/sqlite-history.zsh 49 | autoload -Uz add-zsh-hook 50 | #+END_SRC 51 | 52 | in your zsh startup files. 53 | 54 | ** Note for OS X users 55 | 56 | Add the following line before you source `sqlite-history.zsh`. See https://github.com/larkery/zsh-histdb/pull/31 for details. 57 | 58 | #+BEGIN_SRC zsh 59 | HISTDB_TABULATE_CMD=(sed -e $'s/\x1f/\t/g') 60 | #+END_SRC 61 | 62 | ** Importing your old history 63 | 64 | [[https://github.com/drewis/go-histdbimport][go-histdbimport]] and [[https://github.com/phiresky/ts-histdbimport][ts-histdbimport]] are useful tools for doing this! Note that the imported history will not include metadata such as the working directory or the exit status, since that is not stored in the normal history file format, so queries using ~--in DIR~, etc. will not work as expected. 65 | 66 | * Querying history 67 | You can query the history with the ~histdb~ command. 68 | With no arguments it will print one screenful of history on the current host. 69 | 70 | With arguments, it will print history lines matching their concatenation. 71 | 72 | For wildcards within a history line, you can use the ~%~ character, which is like the shell glob ~*~, so ~histdb this%that~ will match any history line containing ~this~ followed by ~that~ with zero or more characters in-between. 73 | 74 | To search on particular hosts, directories, sessions, or time periods, see the help with ~histdb --help~. 75 | 76 | You can also run ~histdb-top~ to see your most frequent commands, and ~histdb-top dir~ to show your favourite directory for running commands in, but these commands are really a bit useless. 77 | ** Example: 78 | 79 | #+BEGIN_SRC text 80 | $ histdb strace 81 | time ses dir cmd 82 | 17/03 438 ~ strace conkeror 83 | 22/03 522 ~ strace apropos cake 84 | 22/03 522 ~ strace -e trace=file s 85 | 22/03 522 ~ strace -e trace=file ls 86 | 22/03 522 ~ strace -e trace=file cat temp/people.vcf 87 | 22/03 522 ~ strace -e trace=file cat temp/gammu.log 88 | 22/03 522 ~ run-help strace 89 | 24/03 547 ~ man strace 90 | #+END_SRC 91 | 92 | These are all the history entries involving ~strace~ in my history. 93 | If there was more than one screenful, I would need to say ~--limit 1000~ or some other large number. 94 | The command does not warn you if you haven't seen all the results. 95 | The ~ses~ column contains a unique session number, so all the ~522~ rows are from the same shell session. 96 | 97 | To see all hosts, add ~--host~ /after/ the query terms. 98 | To see a specific host, add ~--host hostname~. 99 | To see all of a specific session say e.g. ~-s 522 --limit 10000~. 100 | ** Integration with ~zsh-autosuggestions~ 101 | 102 | If you use [[https://github.com/zsh-users/zsh-autosuggestions][zsh-autosuggestions]] you can configure it to search the history database instead of the ZSH history file thus: 103 | 104 | #+BEGIN_SRC sh 105 | _zsh_autosuggest_strategy_histdb_top_here() { 106 | local query="select commands.argv from 107 | history left join commands on history.command_id = commands.rowid 108 | left join places on history.place_id = places.rowid 109 | where places.dir LIKE '$(sql_escape $PWD)%' 110 | and commands.argv LIKE '$(sql_escape $1)%' 111 | group by commands.argv order by count(*) desc limit 1" 112 | suggestion=$(_histdb_query "$query") 113 | } 114 | 115 | ZSH_AUTOSUGGEST_STRATEGY=histdb_top_here 116 | #+END_SRC 117 | 118 | This query will find the most frequently issued command that is issued in the current directory or any subdirectory. You can get other behaviours by changing the query, for example 119 | 120 | #+BEGIN_SRC sh 121 | _zsh_autosuggest_strategy_histdb_top() { 122 | local query=" 123 | select commands.argv from history 124 | left join commands on history.command_id = commands.rowid 125 | left join places on history.place_id = places.rowid 126 | where commands.argv LIKE '$(sql_escape $1)%' 127 | group by commands.argv, places.dir 128 | order by places.dir != '$(sql_escape $PWD)', count(*) desc 129 | limit 1 130 | " 131 | suggestion=$(_histdb_query "$query") 132 | } 133 | 134 | ZSH_AUTOSUGGEST_STRATEGY=histdb_top 135 | #+END_SRC 136 | 137 | This will find the most frequently issued command issued exactly in this directory, or if there are no matches it will find the most frequently issued command in any directory. You could use other fields like the hostname to restrict to suggestions on this host, etc. 138 | ** Reverse isearch 139 | If you want a history-reverse-isearch type feature there is one defined in ~histdb-interactive.zsh~. If you source that file you will get a new widget called _histdb-isearch which you can bind to a key, e.g. 140 | 141 | #+BEGIN_SRC sh 142 | source histdb-interactive.zsh 143 | bindkey '^r' _histdb-isearch 144 | #+END_SRC 145 | 146 | This is like normal ~history-reverse-isearch~ except: 147 | - The search will start with the buffer contents automatically 148 | - The editing keys are all standard (because it does not really use the minibuffer). 149 | 150 | This means pressing ~C-a~ or ~C-e~ or similar will not exit the search like normal ~history-reverse-isearch~ 151 | - The accept key (~RET~) does not cause the command to run immediately but instead lets you edit it 152 | 153 | There are also a few extra keybindings: 154 | 155 | - ~M-j~ will ~cd~ to the directory for the history entry you're looking at. 156 | This means you can search for ./run-this-command and then ~M-j~ to go to the right directory before running. 157 | - ~M-h~ will toggle limiting the search to the current host's history. 158 | - ~M-d~ will toggle limiting the search to the current directory and subdirectories' histories 159 | * Database schema 160 | The database lives by default in ~$HOME/.histdb/zsh-history.db~. 161 | You can look in it easily by running ~_histdb_query~, as this actually just fires up sqlite with the database. 162 | 163 | For inspiration you can also use ~histdb~ with the ~-d~ argument and it will print the SQL it's running. 164 | * Synchronising history 165 | You should be able to synchronise the history using ~git~; a 3-way merge driver is supplied in ~histdb-merge~. 166 | 167 | The 3-way merge will only work properly if all the computers on which you use the repository have different hostnames. 168 | 169 | The ~histdb-sync~ function will initialize git in the histdb directory and configure the merge driver for you first time you run it. 170 | Subsequent times it will commit all changes, pull all changes, force a merge, and push all changes back again. 171 | The commit message is useless, so if you find that kind of thing upsetting you will need to fix it. 172 | 173 | The reason for using ~histdb-sync~ instead of doing it by hand is that if you are running the git steps in your shell the history database will be changed each command, and so you will never be able to do a pull / merge. 174 | * Completion 175 | None, and I've used the names with underscores to mean something else. 176 | * Pull requests / missing features 177 | Happy to look at changes. 178 | I did at one point have a reverse-isearch thing in here for searching the database interactively, but it didn't really make my life any better so I deleted it. 179 | -------------------------------------------------------------------------------- /.zsh-histdb/db_migrations/0to2.sql: -------------------------------------------------------------------------------- 1 | PRAGMA foreign_keys=OFF; 2 | begin transaction; 3 | create table commands_new (id integer primary key autoincrement, argv text, unique(argv) on conflict ignore); 4 | create table places_new (id integer primary key autoincrement, host text, dir text, unique(host, dir) on conflict ignore); 5 | create table history_new (id integer primary key autoincrement, 6 | session int, 7 | command_id int references commands (id), 8 | place_id int references places (id), 9 | exit_status int, 10 | start_time int, 11 | duration int); 12 | 13 | INSERT INTO commands_new (id, argv) SELECT rowid, argv FROM commands; 14 | INSERT INTO places_new (id, host, dir) SELECT rowid, host, dir FROM places; 15 | 16 | INSERT INTO history_new (session, command_id, place_id, exit_status, start_time, duration) 17 | SELECT H.session, C.rowid, P.rowid, H.exit_status, H.start_time, H.duration 18 | FROM history H 19 | LEFT JOIN places P ON H.place_id = P.rowid 20 | LEFT JOIN commands C ON H.command_id = C.rowid; 21 | drop table history; 22 | drop table places; 23 | drop table commands; 24 | ALTER TABLE commands_new RENAME TO commands; 25 | ALTER TABLE places_new RENAME TO places; 26 | ALTER TABLE history_new RENAME TO history; 27 | PRAGMA foreign_key_check ; 28 | PRAGMA user_version=2; 29 | commit; 30 | PRAGMA foreign_keys=ON; 31 | -------------------------------------------------------------------------------- /.zsh-histdb/histdb-interactive.zsh: -------------------------------------------------------------------------------- 1 | typeset -g HISTDB_ISEARCH_N 2 | typeset -g HISTDB_ISEARCH_MATCH 3 | typeset -g HISTDB_ISEARCH_DIR 4 | typeset -g HISTDB_ISEARCH_HOST 5 | typeset -g HISTDB_ISEARCH_DATE 6 | typeset -g HISTDB_ISEARCH_MATCH_END 7 | 8 | typeset -g HISTDB_ISEARCH_THIS_HOST=1 9 | typeset -g HISTDB_ISEARCH_THIS_DIR=0 10 | typeset -g HISTDB_ISEARCH_LAST_QUERY="" 11 | typeset -g HISTDB_ISEARCH_LAST_N="" 12 | 13 | # TODO Show more info about match (n, date, pwd, host) 14 | # TODO Keys to limit match? 15 | 16 | # make a keymap for histdb isearch 17 | bindkey -N histdb-isearch main 18 | 19 | _histdb_isearch_query () { 20 | if [[ -z $BUFFER ]]; then 21 | HISTDB_ISEARCH_MATCH="" 22 | return 23 | fi 24 | 25 | local new_query="$BUFFER $HISTDB_ISEARCH_THIS_HOST $HISTDB_ISEARCH_THIS_DIR" 26 | if [[ $new_query == $HISTDB_ISEARCH_LAST_QUERY ]] && [[ $HISTDB_ISEARCH_N == $HISTDB_ISEARCH_LAST_N ]]; then 27 | return 28 | elif [[ $new_query != $HISTDB_ISEARCH_LAST_QUERY ]]; then 29 | HISTDB_ISEARCH_N=0 30 | fi 31 | 32 | HISTDB_ISEARCH_LAST_QUERY=$new_query 33 | HISTDB_ISEARCH_LAST_N=HISTDB_ISEARCH_N 34 | 35 | if (( $HISTDB_ISEARCH_N < 0 )); then 36 | local maxmin="min" 37 | local ascdesc="asc" 38 | local offset=$(( - $HISTDB_ISEARCH_N )) 39 | else 40 | local maxmin="max" 41 | local ascdesc="desc" 42 | local offset=$(( $HISTDB_ISEARCH_N )) 43 | fi 44 | 45 | if [[ $HISTDB_ISEARCH_THIS_DIR == 1 ]]; then 46 | local where_dir="and places.dir like '$(sql_escape $PWD)%'" 47 | else 48 | local where_dir="" 49 | fi 50 | 51 | 52 | if [[ $HISTDB_ISEARCH_THIS_HOST == 1 ]]; then 53 | local where_host="and places.host = '$(sql_escape $HOST)'" 54 | else 55 | local where_host="" 56 | fi 57 | 58 | local query="select 59 | commands.argv, 60 | places.dir, 61 | places.host, 62 | datetime(max(history.start_time), 'unixepoch', 'localtime') 63 | from history left join commands 64 | on history.command_id = commands.rowid 65 | left join places 66 | on history.place_id = places.rowid 67 | where commands.argv glob '*$(sql_escape ${BUFFER})*' 68 | ${where_host} 69 | ${where_dir} 70 | group by commands.argv, places.dir, places.host 71 | order by ${maxmin}(history.start_time) ${ascdesc} 72 | limit 1 73 | offset ${offset}" 74 | local result=$(_histdb_query -separator $'\n' "$query") 75 | local lines=("${(f)result}") 76 | HISTDB_ISEARCH_DATE=${lines[-1]} 77 | HISTDB_ISEARCH_HOST=${lines[-2]} 78 | HISTDB_ISEARCH_DIR=${lines[-3]} 79 | lines[-1]=() 80 | lines[-1]=() 81 | lines[-1]=() 82 | HISTDB_ISEARCH_MATCH=${(F)lines} 83 | } 84 | 85 | _histdb_isearch_display () { 86 | if [[ $HISTDB_ISEARCH_THIS_HOST == 1 ]]; then 87 | local host_bit=" h" 88 | else 89 | local host_bit="" 90 | fi 91 | if [[ $HISTDB_ISEARCH_THIS_DIR == 1 ]]; then 92 | local dir_bit=" d" 93 | else 94 | local dir_bit="" 95 | fi 96 | local top_bit="histdb ${HISTDB_ISEARCH_N}${host_bit}${dir_bit}: " 97 | if [[ -z ${HISTDB_ISEARCH_MATCH} ]]; then 98 | PREDISPLAY="(no match) 99 | $top_bit" 100 | else 101 | local qbuffer="${(b)BUFFER}" 102 | qbuffer="${${qbuffer//\\\*/*}//\\\?/?}" 103 | local match_len="${#HISTDB_ISEARCH_MATCH}" 104 | local prefix="${HISTDB_ISEARCH_MATCH%%${~qbuffer}*}" 105 | local prefix_len="${#prefix}" 106 | local suffix_len="${#${HISTDB_ISEARCH_MATCH:${prefix_len}}##${~qbuffer}}" 107 | local match_end=$(( $match_len - $suffix_len )) 108 | HISTDB_ISEARCH_MATCH_END=${match_end} 109 | 110 | if [[ $HISTDB_ISEARCH_HOST == $HOST ]]; then 111 | local host="" 112 | else 113 | local host=" 114 | host: $HISTDB_ISEARCH_HOST" 115 | fi 116 | region_highlight=("P${prefix_len} ${match_end} underline") 117 | PREDISPLAY="${HISTDB_ISEARCH_MATCH} 118 | → in ${HISTDB_ISEARCH_DIR}$host 119 | → on ${HISTDB_ISEARCH_DATE} 120 | $top_bit" 121 | fi 122 | } 123 | 124 | _histdb-isearch-up () { 125 | HISTDB_ISEARCH_N=$(( $HISTDB_ISEARCH_N + 1 )) 126 | } 127 | 128 | _histdb-isearch-down () { 129 | HISTDB_ISEARCH_N=$(( $HISTDB_ISEARCH_N - 1 )) 130 | } 131 | 132 | zle -N self-insert-histdb-isearch 133 | 134 | _histdb_line_redraw () { 135 | _histdb_isearch_query 136 | _histdb_isearch_display 137 | } 138 | 139 | _histdb-isearch () { 140 | local old_buffer=${BUFFER} 141 | local old_cursor=${CURSOR} 142 | HISTDB_ISEARCH_N=0 143 | echo -ne "\e[4 q" # switch to underline cursor 144 | 145 | zle -K histdb-isearch 146 | zle -N zle-line-pre-redraw _histdb_line_redraw 147 | _histdb_isearch_query 148 | _histdb_isearch_display 149 | zle recursive-edit; local stat=$? 150 | zle -D zle-line-pre-redraw # TODO push/pop zle-line-pre-redraw and 151 | # self-insert, rather than nuking 152 | 153 | zle -K main 154 | PREDISPLAY="" 155 | region_highlight=() 156 | 157 | echo -ne "\e[1 q" #box cursor 158 | 159 | if ! (( stat )); then 160 | BUFFER="${HISTDB_ISEARCH_MATCH}" 161 | CURSOR="${HISTDB_ISEARCH_MATCH_END}" 162 | else 163 | BUFFER=${old_buffer} 164 | CURSOR=${old_cursor} 165 | fi 166 | 167 | return 0 168 | } 169 | 170 | # this will work outside histdb-isearch if you want 171 | # so you can recover from history and then cd afterwards 172 | _histdb-isearch-cd () { 173 | if [[ -d ${HISTDB_ISEARCH_DIR} ]]; then 174 | cd "${HISTDB_ISEARCH_DIR}" 175 | zle reset-prompt 176 | fi 177 | } 178 | 179 | _histdb-isearch-toggle-host () { 180 | if [[ $HISTDB_ISEARCH_THIS_HOST == 1 ]]; then 181 | HISTDB_ISEARCH_THIS_HOST=0 182 | else 183 | HISTDB_ISEARCH_THIS_HOST=1 184 | fi 185 | } 186 | 187 | _histdb-isearch-toggle-dir () { 188 | if [[ $HISTDB_ISEARCH_THIS_DIR == 1 ]]; then 189 | HISTDB_ISEARCH_THIS_DIR=0 190 | else 191 | HISTDB_ISEARCH_THIS_DIR=1 192 | fi 193 | } 194 | 195 | zle -N _histdb-isearch-up 196 | zle -N _histdb-isearch-down 197 | zle -N _histdb-isearch 198 | zle -N _histdb-isearch-cd 199 | zle -N _histdb-isearch-toggle-dir 200 | zle -N _histdb-isearch-toggle-host 201 | 202 | bindkey -M histdb-isearch '' _histdb-isearch-up 203 | bindkey -M histdb-isearch '^[[A' _histdb-isearch-up 204 | 205 | bindkey -M histdb-isearch '' _histdb-isearch-down 206 | bindkey -M histdb-isearch '^[[B' _histdb-isearch-down 207 | 208 | bindkey -M histdb-isearch '^[j' _histdb-isearch-cd 209 | 210 | bindkey -M histdb-isearch '^[h' _histdb-isearch-toggle-host 211 | bindkey -M histdb-isearch '^[d' _histdb-isearch-toggle-dir 212 | 213 | # because we are using BUFFER for output, we have to reimplement 214 | # pretty much the whole set of buffer editing operations 215 | -------------------------------------------------------------------------------- /.zsh-histdb/histdb-merge: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | typeset -g HERE=$(dirname "${(%):-%N}") 4 | 5 | local ancestor=${1:?three databases required}; shift 6 | local ours=${1:?three databases required}; shift 7 | local theirs=${1:?three databases required} 8 | 9 | $HERE/histdb-migrate $ancestor # this is always reasonable to do 10 | $HERE/histdb-migrate $ours # this also seems fine 11 | 12 | V_OURS=$(sqlite3 -batch -noheader $ours 'PRAGMA user_version') 13 | V_THEIRS=$(sqlite3 -batch -noheader $theirs 'PRAGMA user_version') 14 | 15 | if [[ ${V_OURS} -lt ${V_THEIRS} ]] ; then 16 | echo "Attempting to merge with a database from a future version (${V_THEIRS})." 17 | echo "You need to update your histdb version to continue." 18 | exit 1 19 | elif [[ ${V_THEIRS} -lt ${V_OURS} ]]; then 20 | echo "Merging different database versions: ours ${V_OURS}, theirs ${V_THEIRS}." 21 | echo "To continue merging, their database needs migrating to newer version." 22 | echo "If you do this, and push the changes, then the remote version of histdb will need upgrading too." 23 | 24 | read -q "REPLY?Try migrating their database? [y/n] " 25 | if [[ $REPLY != "y" ]]; then 26 | echo "Cancelled." 27 | exit 1 28 | fi 29 | fi 30 | 31 | # for reasons I cannot use the encryption filter here. 32 | # most annoying. 33 | # 34 | echo "Ancestor has $(sqlite3 ${ancestor} 'select count(*) from history') entries" 35 | echo "We have $(sqlite3 ${ours} 'select count(*) from history') entries" 36 | echo "Theirs have $(sqlite3 ${theirs} 'select count(*) from history') entries" 37 | 38 | sqlite3 -batch -noheader "${theirs}" < (SELECT max(id) FROM a.commands); 44 | INSERT INTO places (host, dir) SELECT host, dir FROM n.places as NP where NP.id > (SELECT max(id) FROM a.places); 45 | 46 | -- insert missing history, rewriting IDs 47 | -- could uniquify sessions by host in this way too 48 | 49 | INSERT INTO history (session, command_id, place_id, exit_status, start_time, duration) 50 | SELECT NO.session, C.id, P.id, NO.exit_status, NO.start_time, NO.duration 51 | FROM n.history NO 52 | LEFT JOIN n.places PO ON NO.place_id = PO.id 53 | LEFT JOIN n.commands CO ON NO.command_id = CO.id 54 | LEFT JOIN commands C ON C.argv = CO.argv 55 | LEFT JOIN places P ON (P.host = PO.host 56 | AND P.dir = PO.dir) 57 | WHERE NO.id > (SELECT MAX(id) FROM a.history) 58 | ; 59 | VACUUM; 60 | EOF 61 | cp ${theirs} ${ours} 62 | echo "Now we have $(sqlite3 ${ours} 'select count(*) from history') entries" 63 | -------------------------------------------------------------------------------- /.zsh-histdb/histdb-migrate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | typeset -g HISTDB_SCHEMA_VERSION=2 4 | typeset -g HISTDB_INSTALLED_IN="${(%):-%N}" 5 | 6 | local TARGET_FILE="$1" 7 | local CURRENT_VERSION=$(sqlite3 -batch -noheader "${TARGET_FILE}" 'PRAGMA user_version') 8 | if [[ ${CURRENT_VERSION} -lt ${HISTDB_SCHEMA_VERSION} ]]; then 9 | echo "History database ${TARGET_FILE} is using an older schema (${CURRENT_VERSION}) and will be updated to version ${HISTDB_SCHEMA_VERSION}." 10 | local MIGRATION_FILENAME="$(dirname ${HISTDB_INSTALLED_IN})/db_migrations/${CURRENT_VERSION}to${HISTDB_SCHEMA_VERSION}.sql" 11 | if [[ -f $MIGRATION_FILENAME ]]; then 12 | local BACKUP_FILE="${TARGET_FILE}-$(date +%s).bak" 13 | echo "Backing up database to ${BACKUP_FILE} before migration" 14 | cp ${BACKUP_FILE} ${HISTDB_FILE} 15 | sqlite3 -batch -noheader "${TARGET_FILE}" < "${MIGRATION_FILENAME}" 16 | local R="$?" 17 | [[ "$R" -ne 0 ]] && (echo "Error during database conversion"; cp ${BACKUP_FILE} ${HISTDB_FILE}) || echo "Update successful (you may want to remove the backup)" 18 | return "$R" 19 | else 20 | echo "There is no migration script from version ${CURRENT_VERSION} to ${HISTDB_SCHEMA_VERSION}." 21 | return 1 22 | fi 23 | elif [[ ${CURRENT_VERSION} -gt ${HISTDB_SCHEMA_VERSION} ]]; then 24 | echo "History database ${TARGET_FILE} is using a newer schema (${CURRENT_VERSION}) than this version of histdb understands (${HISTDB_SCHEMA_VERSION})." 25 | echo "Most likely, you have updated histdb on another machine which has updated your history database, and you need to update this copy of histdb." 26 | return 1 27 | fi 28 | -------------------------------------------------------------------------------- /.zsh-histdb/sqlite-history.zsh: -------------------------------------------------------------------------------- 1 | which sqlite3 >/dev/null 2>&1 || return; 2 | 3 | zmodload zsh/datetime # for EPOCHSECONDS 4 | zmodload zsh/system # for sysopen 5 | builtin which sysopen &>/dev/null || return; # guard against zsh older than 5.0.8. 6 | 7 | zmodload -F zsh/stat b:zstat # just zstat 8 | autoload -U add-zsh-hook 9 | 10 | typeset -g HISTDB_QUERY="" 11 | if [[ -z ${HISTDB_FILE} ]]; then 12 | typeset -g HISTDB_FILE="${HOME}/.histdb/zsh-history.db" 13 | else 14 | typeset -g HISTDB_FILE 15 | fi 16 | 17 | typeset -g HISTDB_INODE=() 18 | typeset -g HISTDB_SESSION="" 19 | typeset -g HISTDB_HOST="" 20 | typeset -g HISTDB_INSTALLED_IN="${(%):-%N}" 21 | 22 | 23 | 24 | sql_escape () { 25 | print -r -- ${${@//\'/\'\'}//$'\x00'} 26 | } 27 | 28 | _histdb_query () { 29 | sqlite3 -batch -noheader -cmd ".timeout 1000" "${HISTDB_FILE}" "$@" 30 | [[ "$?" -ne 0 ]] && echo "error in $@" 31 | } 32 | 33 | _histdb_stop_sqlite_pipe () { 34 | if [[ -n $HISTDB_FD ]]; then 35 | if print -nu$HISTDB_FD; then 36 | exec {HISTDB_FD}>&-; # https://stackoverflow.com/a/22794374/2639190 37 | fi 38 | fi 39 | # Sometimes, it seems like closing the fd does not terminate the 40 | # sqlite batch process, so here is a horrible fallback. 41 | # if [[ -n $HISTDB_SQLITE_PID ]]; then 42 | # ps -o args= -p $HISTDB_SQLITE_PID | read -r args 43 | # if [[ $args == "sqlite3 -batch ${HISTDB_FILE}" ]]; then 44 | # kill -TERM $HISTDB_SQLITE_PID 45 | # fi 46 | # fi 47 | } 48 | 49 | add-zsh-hook zshexit _histdb_stop_sqlite_pipe 50 | 51 | _histdb_start_sqlite_pipe () { 52 | local PIPE==(<<<'') 53 | setopt local_options no_notify no_monitor 54 | mkfifo $PIPE 55 | sqlite3 -batch -noheader "${HISTDB_FILE}" < $PIPE >/dev/null &| 56 | sysopen -w -o cloexec -u HISTDB_FD -- $PIPE 57 | command rm $PIPE 58 | zstat -A HISTDB_INODE +inode ${HISTDB_FILE} 59 | } 60 | 61 | _histdb_query_batch () { 62 | local CUR_INODE 63 | zstat -A CUR_INODE +inode ${HISTDB_FILE} 64 | if [[ $CUR_INODE != $HISTDB_INODE ]]; then 65 | _histdb_stop_sqlite_pipe 66 | _histdb_start_sqlite_pipe 67 | fi 68 | cat >&$HISTDB_FD 69 | echo ';' >&$HISTDB_FD # make sure last command is executed 70 | } 71 | 72 | _histdb_init () { 73 | if [[ -n "${HISTDB_SESSION}" ]]; then 74 | return 75 | fi 76 | 77 | if ! [[ -e "${HISTDB_FILE}" ]]; then 78 | local hist_dir="${HISTDB_FILE:h}" 79 | if ! [[ -d "$hist_dir" ]]; then 80 | mkdir -p -- "$hist_dir" 81 | fi 82 | _histdb_query <<-EOF 83 | create table commands (id integer primary key autoincrement, argv text, unique(argv) on conflict ignore); 84 | create table places (id integer primary key autoincrement, host text, dir text, unique(host, dir) on conflict ignore); 85 | create table history (id integer primary key autoincrement, 86 | session int, 87 | command_id int references commands (id), 88 | place_id int references places (id), 89 | exit_status int, 90 | start_time int, 91 | duration int); 92 | PRAGMA user_version = 2; 93 | EOF 94 | fi 95 | if [[ -z "${HISTDB_SESSION}" ]]; then 96 | ${HISTDB_INSTALLED_IN:h}/histdb-migrate "${HISTDB_FILE}" 97 | HISTDB_HOST=${HISTDB_HOST:-"'$(sql_escape ${HOST})'"} 98 | HISTDB_SESSION=$(_histdb_query "select 1+max(session) from history inner join places on places.id=history.place_id where places.host = ${HISTDB_HOST}") 99 | HISTDB_SESSION="${HISTDB_SESSION:-0}" 100 | readonly HISTDB_SESSION 101 | fi 102 | 103 | _histdb_start_sqlite_pipe 104 | _histdb_query_batch >/dev/null <>! .gitattributes 222 | git add .gitattributes 223 | git add "${HISTDB_FILE:t}" 224 | fi 225 | _histdb_stop_sqlite_pipe # Stop in case of a merge, starting again afterwards 226 | git commit -am "history" && git pull --no-edit && git push 227 | _histdb_start_sqlite_pipe 228 | popd -q 229 | } 230 | fi 231 | 232 | echo 'pragma wal_checkpoint(passive);' | _histdb_query_batch 233 | } 234 | 235 | histdb () { 236 | _histdb_init 237 | local -a opts 238 | local -a hosts 239 | local -a indirs 240 | local -a atdirs 241 | local -a sessions 242 | 243 | zparseopts -E -D -a opts \ 244 | -host+::=hosts \ 245 | -in+::=indirs \ 246 | -at+::=atdirs \ 247 | -forget \ 248 | -yes \ 249 | -detail \ 250 | -sep:- \ 251 | -exact \ 252 | d h -help \ 253 | s+::=sessions \ 254 | -from:- -until:- -limit:- \ 255 | -status:- -desc 256 | 257 | local usage="usage:$0 terms [--desc] [--host[ x]] [--in[ x]] [--at] [-s n]+* [-d] [--detail] [--forget] [--yes] [--exact] [--sep x] [--from x] [--until x] [--limit n] [--status x] 258 | --desc reverse sort order of results 259 | --host print the host column and show all hosts (otherwise current host) 260 | --host x find entries from host x 261 | --in find only entries run in the current dir or below 262 | --in x find only entries in directory x or below 263 | --at like --in, but excluding subdirectories 264 | -s n only show session n 265 | -d debug output query that will be run 266 | --detail show details 267 | --forget forget everything which matches in the history 268 | --yes don't ask for confirmation when forgetting 269 | --exact don't match substrings 270 | --sep x print with separator x, and don't tabulate 271 | --from x only show commands after date x (sqlite date parser) 272 | --until x only show commands before date x (sqlite date parser) 273 | --limit n only show n rows. defaults to $LINES or 25 274 | --status x only show rows with exit status x. Can be 'error' to find all nonzero." 275 | 276 | local selcols="session as ses, dir" 277 | local cols="session, replace(places.dir, '$HOME', '~') as dir" 278 | local where="1" 279 | if [[ -p /dev/stdout ]]; then 280 | local limit="" 281 | else 282 | local limit="${$((LINES - 4)):-25}" 283 | fi 284 | 285 | local forget="0" 286 | local forget_accept=0 287 | local exact=0 288 | 289 | if (( ${#hosts} )); then 290 | local hostwhere="" 291 | local host="" 292 | for host ($hosts); do 293 | host="${${host#--host}#=}" 294 | hostwhere="${hostwhere}${host:+${hostwhere:+ or }places.host='$(sql_escape ${host})'}" 295 | done 296 | where="${where}${hostwhere:+ and (${hostwhere})}" 297 | cols="${cols}, places.host as host" 298 | selcols="${selcols}, host" 299 | else 300 | where="${where} and places.host=${HISTDB_HOST}" 301 | fi 302 | 303 | if (( ${#indirs} + ${#atdirs} )); then 304 | local dirwhere="" 305 | local dir="" 306 | for dir ($indirs); do 307 | dir="${${${dir#--in}#=}:-$PWD}" 308 | dirwhere="${dirwhere}${dirwhere:+ or }places.dir like '$(sql_escape $dir)%'" 309 | done 310 | for dir ($atdirs); do 311 | dir="${${${dir#--at}#=}:-$PWD}" 312 | dirwhere="${dirwhere}${dirwhere:+ or }places.dir = '$(sql_escape $dir)'" 313 | done 314 | where="${where}${dirwhere:+ and (${dirwhere})}" 315 | fi 316 | 317 | if (( ${#sessions} )); then 318 | local sin="" 319 | local ses="" 320 | for ses ($sessions); do 321 | ses="${${${ses#-s}#=}:-${HISTDB_SESSION}}" 322 | sin="${sin}${sin:+, }$ses" 323 | done 324 | where="${where}${sin:+ and session in ($sin)}" 325 | fi 326 | 327 | local sep=$'\x1f' 328 | local orderdir='asc' 329 | local debug=0 330 | local opt="" 331 | for opt ($opts); do 332 | case $opt in 333 | --desc) 334 | orderdir='desc' 335 | ;; 336 | --sep*) 337 | sep=${opt#--sep} 338 | ;; 339 | --from*) 340 | local from=${opt#--from} 341 | case $from in 342 | -*) 343 | from="datetime('now', '$from')" 344 | ;; 345 | today) 346 | from="datetime('now', 'start of day')" 347 | ;; 348 | yesterday) 349 | from="datetime('now', 'start of day', '-1 day')" 350 | ;; 351 | esac 352 | where="${where} and datetime(start_time, 'unixepoch') >= $from" 353 | ;; 354 | --status*) 355 | local xstatus=${opt#--status} 356 | case $xstatus in 357 | <->) 358 | where="${where} and exit_status = $xstatus" 359 | ;; 360 | error) 361 | where="${where} and exit_status <> 0" 362 | ;; 363 | esac 364 | ;; 365 | --until*) 366 | local until=${opt#--until} 367 | case $until in 368 | -*) 369 | until="datetime('now', '$until')" 370 | ;; 371 | today) 372 | until="datetime('now', 'start of day')" 373 | ;; 374 | yesterday) 375 | until="datetime('now', 'start of day', '-1 day')" 376 | ;; 377 | esac 378 | where="${where} and datetime(start_time, 'unixepoch') <= $until" 379 | ;; 380 | -d) 381 | debug=1 382 | ;; 383 | --detail) 384 | cols="${cols}, exit_status, duration " 385 | selcols="${selcols}, exit_status as [?],duration as secs " 386 | ;; 387 | -h|--help) 388 | echo "$usage" 389 | return 0 390 | ;; 391 | --forget) 392 | forget=1 393 | ;; 394 | --yes) 395 | forget_accept=1 396 | ;; 397 | --exact) 398 | exact=1 399 | ;; 400 | --limit*) 401 | limit=${opt#--limit} 402 | ;; 403 | esac 404 | done 405 | 406 | if [[ -n "$*" ]]; then 407 | if [[ $exact -eq 0 ]]; then 408 | where="${where} and commands.argv glob '*$(sql_escape $@)*'" 409 | else 410 | where="${where} and commands.argv = '$(sql_escape $@)'" 411 | fi 412 | fi 413 | 414 | if [[ $forget -gt 0 ]]; then 415 | limit="" 416 | fi 417 | local seps=$(echo "$cols" | tr -c -d ',' | tr ',' $sep) 418 | cols="${cols}, replace(commands.argv, ' 419 | ', ' 420 | $seps') as argv, max(start_time) as max_start" 421 | 422 | local mst="datetime(max_start, 'unixepoch')" 423 | local dst="datetime('now', 'start of day')" 424 | local timecol="strftime(case when $mst > $dst then '%H:%M' else '%d/%m' end, max_start, 'unixepoch', 'localtime') as time" 425 | 426 | selcols="${timecol}, ${selcols}, argv as cmd" 427 | 428 | local r_order="asc" 429 | if [[ $orderdir == "asc" ]]; then 430 | r_order="desc" 431 | fi 432 | 433 | local query="select ${selcols} from (select ${cols} 434 | from 435 | commands 436 | join history on history.command_id = commands.id 437 | join places on history.place_id = places.id 438 | where ${where} 439 | group by history.command_id, history.place_id 440 | order by max_start ${r_order} 441 | ${limit:+limit $limit}) order by max_start ${orderdir}" 442 | 443 | ## min max date? 444 | local count_query="select count(*) from (select ${cols} 445 | from 446 | commands 447 | join history on history.command_id = commands.id 448 | join places on history.place_id = places.id 449 | where ${where} 450 | group by history.command_id, history.place_id 451 | order by max_start desc) order by max_start ${orderdir}" 452 | 453 | if [[ $debug = 1 ]]; then 454 | echo "$query" 455 | else 456 | local count=$(_histdb_query "$count_query") 457 | if [[ -p /dev/stdout ]]; then 458 | buffer() { 459 | ## this runs out of memory for big files I think perl -e 'local $/; my $stdin = ; print $stdin;' 460 | temp=$(mktemp) 461 | cat >! "$temp" 462 | cat -- "$temp" 463 | rm -f -- "$temp" 464 | } 465 | else 466 | buffer() { 467 | cat 468 | } 469 | fi 470 | if [[ $sep == $'\x1f' ]]; then 471 | _histdb_query -header -separator $sep "$query" | iconv -f utf-8 -t utf-8 -c | buffer | "${HISTDB_TABULATE_CMD[@]}" 472 | else 473 | _histdb_query -header -separator $sep "$query" | buffer 474 | fi 475 | [[ -n $limit ]] && [[ $limit -lt $count ]] && echo "(showing $limit of $count results)" 476 | fi 477 | 478 | if [[ $forget -gt 0 ]]; then 479 | if [[ $forget_accept -gt 0 ]]; then 480 | REPLY=y 481 | else 482 | read -q "REPLY?Forget all these results? [y/n] " 483 | fi 484 | if [[ $REPLY =~ "[yY]" ]]; then 485 | _histdb_query "delete from history where 486 | history.id in ( 487 | select history.id from 488 | history 489 | left join commands on history.command_id = commands.id 490 | left join places on history.place_id = places.id 491 | where ${where})" 492 | _histdb_query "delete from commands where commands.id not in (select distinct history.command_id from history)" 493 | fi 494 | fi 495 | } 496 | -------------------------------------------------------------------------------- /.zsh-histdb/zsh-histdb.plugin.zsh: -------------------------------------------------------------------------------- 1 | source ${0:A:h}/sqlite-history.zsh 2 | -------------------------------------------------------------------------------- /.zsh-history-substring-search.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | ############################################################################## 3 | # 4 | # Copyright (c) 2009 Peter Stephenson 5 | # Copyright (c) 2011 Guido van Steen 6 | # Copyright (c) 2011 Suraj N. Kurapati 7 | # Copyright (c) 2011 Sorin Ionescu 8 | # Copyright (c) 2011 Vincent Guerci 9 | # All rights reserved. 10 | # 11 | # Redistribution and use in source and binary forms, with or without 12 | # modification, are permitted provided that the following conditions are met: 13 | # 14 | # * Redistributions of source code must retain the above copyright 15 | # notice, this list of conditions and the following disclaimer. 16 | # 17 | # * Redistributions in binary form must reproduce the above 18 | # copyright notice, this list of conditions and the following 19 | # disclaimer in the documentation and/or other materials provided 20 | # with the distribution. 21 | # 22 | # * Neither the name of the FIZSH nor the names of its contributors 23 | # may be used to endorse or promote products derived from this 24 | # software without specific prior written permission. 25 | # 26 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 30 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 | # POSSIBILITY OF SUCH DAMAGE. 37 | # 38 | ############################################################################## 39 | 40 | #----------------------------------------------------------------------------- 41 | # configuration variables 42 | #----------------------------------------------------------------------------- 43 | 44 | HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND='bg=magenta,fg=white,bold' 45 | HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND='bg=red,fg=white,bold' 46 | HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS='i' 47 | 48 | #----------------------------------------------------------------------------- 49 | # the main ZLE widgets 50 | #----------------------------------------------------------------------------- 51 | 52 | history-substring-search-up() { 53 | _history-substring-search-begin 54 | 55 | _history-substring-search-up-history || 56 | _history-substring-search-up-buffer || 57 | _history-substring-search-up-search 58 | 59 | _history-substring-search-end 60 | } 61 | 62 | history-substring-search-down() { 63 | _history-substring-search-begin 64 | 65 | _history-substring-search-down-history || 66 | _history-substring-search-down-buffer || 67 | _history-substring-search-down-search 68 | 69 | _history-substring-search-end 70 | } 71 | 72 | zle -N history-substring-search-up 73 | zle -N history-substring-search-down 74 | 75 | #----------------------------------------------------------------------------- 76 | # implementation details 77 | #----------------------------------------------------------------------------- 78 | 79 | zmodload -F zsh/parameter 80 | 81 | # 82 | # We have to "override" some keys and widgets if the 83 | # zsh-syntax-highlighting plugin has not been loaded: 84 | # 85 | # https://github.com/nicoulaj/zsh-syntax-highlighting 86 | # 87 | if [[ $+functions[_zsh_highlight] -eq 0 ]]; then 88 | # 89 | # Dummy implementation of _zsh_highlight() that 90 | # simply removes any existing highlights when the 91 | # user inserts printable characters into $BUFFER. 92 | # 93 | _zsh_highlight() { 94 | if [[ $KEYS == [[:print:]] ]]; then 95 | region_highlight=() 96 | fi 97 | } 98 | 99 | # 100 | # The following snippet was taken from the zsh-syntax-highlighting project: 101 | # 102 | # https://github.com/zsh-users/zsh-syntax-highlighting/blob/56b134f5d62ae3d4e66c7f52bd0cc2595f9b305b/zsh-syntax-highlighting.zsh#L126-161 103 | # 104 | # Copyright (c) 2010-2011 zsh-syntax-highlighting contributors 105 | # All rights reserved. 106 | # 107 | # Redistribution and use in source and binary forms, with or without 108 | # modification, are permitted provided that the following conditions are 109 | # met: 110 | # 111 | # * Redistributions of source code must retain the above copyright 112 | # notice, this list of conditions and the following disclaimer. 113 | # 114 | # * Redistributions in binary form must reproduce the above copyright 115 | # notice, this list of conditions and the following disclaimer in the 116 | # documentation and/or other materials provided with the distribution. 117 | # 118 | # * Neither the name of the zsh-syntax-highlighting contributors nor the 119 | # names of its contributors may be used to endorse or promote products 120 | # derived from this software without specific prior written permission. 121 | # 122 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 123 | # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 124 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 125 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 126 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 127 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 128 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 129 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 130 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 131 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 132 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 133 | # 134 | #--------------8<-------------------8<-------------------8<----------------- 135 | # Rebind all ZLE widgets to make them invoke _zsh_highlights. 136 | _zsh_highlight_bind_widgets() 137 | { 138 | # Load ZSH module zsh/zleparameter, needed to override user defined widgets. 139 | zmodload zsh/zleparameter 2>/dev/null || { 140 | echo 'zsh-syntax-highlighting: failed loading zsh/zleparameter.' >&2 141 | return 1 142 | } 143 | 144 | # Override ZLE widgets to make them invoke _zsh_highlight. 145 | local cur_widget 146 | for cur_widget in ${${(f)"$(builtin zle -la)"}:#(.*|_*|orig-*|run-help|which-command|beep|yank*)}; do 147 | case $widgets[$cur_widget] in 148 | 149 | # Already rebound event: do nothing. 150 | user:$cur_widget|user:_zsh_highlight_widget_*);; 151 | 152 | # User defined widget: override and rebind old one with prefix "orig-". 153 | user:*) eval "zle -N orig-$cur_widget ${widgets[$cur_widget]#*:}; \ 154 | _zsh_highlight_widget_$cur_widget() { builtin zle orig-$cur_widget -- \"\$@\" && _zsh_highlight }; \ 155 | zle -N $cur_widget _zsh_highlight_widget_$cur_widget";; 156 | 157 | # Completion widget: override and rebind old one with prefix "orig-". 158 | completion:*) eval "zle -C orig-$cur_widget ${${widgets[$cur_widget]#*:}/:/ }; \ 159 | _zsh_highlight_widget_$cur_widget() { builtin zle orig-$cur_widget -- \"\$@\" && _zsh_highlight }; \ 160 | zle -N $cur_widget _zsh_highlight_widget_$cur_widget";; 161 | 162 | # Builtin widget: override and make it call the builtin ".widget". 163 | builtin) eval "_zsh_highlight_widget_$cur_widget() { builtin zle .$cur_widget -- \"\$@\" && _zsh_highlight }; \ 164 | zle -N $cur_widget _zsh_highlight_widget_$cur_widget";; 165 | 166 | # Default: unhandled case. 167 | *) echo "zsh-syntax-highlighting: unhandled ZLE widget '$cur_widget'" >&2 ;; 168 | esac 169 | done 170 | } 171 | #-------------->8------------------->8------------------->8----------------- 172 | 173 | _zsh_highlight_bind_widgets 174 | fi 175 | 176 | _history-substring-search-begin() { 177 | setopt localoptions extendedglob 178 | 179 | _history_substring_search_refresh_display= 180 | _history_substring_search_query_highlight= 181 | 182 | # 183 | # Continue using the previous $_history_substring_search_result by default, 184 | # unless the current query was cleared or a new/different query was entered. 185 | # 186 | if [[ -z $BUFFER || $BUFFER != $_history_substring_search_result ]]; then 187 | # 188 | # For the purpose of highlighting we will also keep 189 | # a version without doubly-escaped meta characters. 190 | # 191 | _history_substring_search_query=$BUFFER 192 | 193 | # 194 | # $BUFFER contains the text that is in the command-line currently. 195 | # we put an extra "\\" before meta characters such as "\(" and "\)", 196 | # so that they become "\\\(" and "\\\)". 197 | # 198 | _history_substring_search_query_escaped=${BUFFER//(#m)[\][()|\\*?#<>~^]/\\$MATCH} 199 | 200 | # 201 | # Find all occurrences of the search query in the history file. 202 | # 203 | # (k) returns the "keys" (history index numbers) instead of the values 204 | # (Oa) reverses the order, because (R) returns results reversed. 205 | # 206 | _history_substring_search_matches=(${(kOa)history[(R)(#$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)*${_history_substring_search_query_escaped}*]}) 207 | 208 | # 209 | # Define the range of values that $_history_substring_search_match_index 210 | # can take: [0, $_history_substring_search_matches_count_plus]. 211 | # 212 | _history_substring_search_matches_count=$#_history_substring_search_matches 213 | _history_substring_search_matches_count_plus=$(( _history_substring_search_matches_count + 1 )) 214 | _history_substring_search_matches_count_sans=$(( _history_substring_search_matches_count - 1 )) 215 | 216 | # 217 | # If $_history_substring_search_match_index is equal to 218 | # $_history_substring_search_matches_count_plus, this indicates that we 219 | # are beyond the beginning of $_history_substring_search_matches. 220 | # 221 | # If $_history_substring_search_match_index is equal to 0, this indicates 222 | # that we are beyond the end of $_history_substring_search_matches. 223 | # 224 | # If we have initially pressed "up" we have to initialize 225 | # $_history_substring_search_match_index to 226 | # $_history_substring_search_matches_count_plus so that it will be 227 | # decreased to $_history_substring_search_matches_count. 228 | # 229 | # If we have initially pressed "down" we have to initialize 230 | # $_history_substring_search_match_index to 231 | # $_history_substring_search_matches_count so that it will be increased to 232 | # $_history_substring_search_matches_count_plus. 233 | # 234 | if [[ $WIDGET == history-substring-search-down ]]; then 235 | _history_substring_search_match_index=$_history_substring_search_matches_count 236 | else 237 | _history_substring_search_match_index=$_history_substring_search_matches_count_plus 238 | fi 239 | fi 240 | } 241 | 242 | _history-substring-search-end() { 243 | setopt localoptions extendedglob 244 | 245 | _history_substring_search_result=$BUFFER 246 | 247 | # the search was succesful so display the result properly by clearing away 248 | # existing highlights and moving the cursor to the end of the result buffer 249 | if [[ $_history_substring_search_refresh_display -eq 1 ]]; then 250 | region_highlight=() 251 | CURSOR=${#BUFFER} 252 | fi 253 | 254 | # highlight command line using zsh-syntax-highlighting 255 | _zsh_highlight 256 | 257 | # highlight the search query inside the command line 258 | if [[ -n $_history_substring_search_query_highlight && -n $_history_substring_search_query ]]; then 259 | # 260 | # The following expression yields a variable $MBEGIN, which 261 | # indicates the begin position + 1 of the first occurrence 262 | # of _history_substring_search_query_escaped in $BUFFER. 263 | # 264 | : ${(S)BUFFER##(#m$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)($_history_substring_search_query##)} 265 | local begin=$(( MBEGIN - 1 )) 266 | local end=$(( begin + $#_history_substring_search_query )) 267 | region_highlight+=("$begin $end $_history_substring_search_query_highlight") 268 | fi 269 | 270 | # For debugging purposes: 271 | # zle -R "mn: "$_history_substring_search_match_index" m#: "${#_history_substring_search_matches} 272 | # read -k -t 200 && zle -U $REPLY 273 | 274 | # Exit successfully from the history-substring-search-* widgets. 275 | return 0 276 | } 277 | 278 | _history-substring-search-up-buffer() { 279 | # 280 | # Check if the UP arrow was pressed to move the cursor within a multi-line 281 | # buffer. This amounts to three tests: 282 | # 283 | # 1. $#buflines -gt 1. 284 | # 285 | # 2. $CURSOR -ne $#BUFFER. 286 | # 287 | # 3. Check if we are on the first line of the current multi-line buffer. 288 | # If so, pressing UP would amount to leaving the multi-line buffer. 289 | # 290 | # We check this by adding an extra "x" to $LBUFFER, which makes 291 | # sure that xlbuflines is always equal to the number of lines 292 | # until $CURSOR (including the line with the cursor on it). 293 | # 294 | local buflines XLBUFFER xlbuflines 295 | buflines=(${(f)BUFFER}) 296 | XLBUFFER=$LBUFFER"x" 297 | xlbuflines=(${(f)XLBUFFER}) 298 | 299 | if [[ $#buflines -gt 1 && $CURSOR -ne $#BUFFER && $#xlbuflines -ne 1 ]]; then 300 | zle up-line-or-history 301 | return 0 302 | fi 303 | 304 | return 1 305 | } 306 | 307 | _history-substring-search-down-buffer() { 308 | # 309 | # Check if the DOWN arrow was pressed to move the cursor within a multi-line 310 | # buffer. This amounts to three tests: 311 | # 312 | # 1. $#buflines -gt 1. 313 | # 314 | # 2. $CURSOR -ne $#BUFFER. 315 | # 316 | # 3. Check if we are on the last line of the current multi-line buffer. 317 | # If so, pressing DOWN would amount to leaving the multi-line buffer. 318 | # 319 | # We check this by adding an extra "x" to $RBUFFER, which makes 320 | # sure that xrbuflines is always equal to the number of lines 321 | # from $CURSOR (including the line with the cursor on it). 322 | # 323 | local buflines XRBUFFER xrbuflines 324 | buflines=(${(f)BUFFER}) 325 | XRBUFFER="x"$RBUFFER 326 | xrbuflines=(${(f)XRBUFFER}) 327 | 328 | if [[ $#buflines -gt 1 && $CURSOR -ne $#BUFFER && $#xrbuflines -ne 1 ]]; then 329 | zle down-line-or-history 330 | return 0 331 | fi 332 | 333 | return 1 334 | } 335 | 336 | _history-substring-search-up-history() { 337 | # 338 | # Behave like up in ZSH, except clear the $BUFFER 339 | # when beginning of history is reached like in Fish. 340 | # 341 | if [[ -z $_history_substring_search_query ]]; then 342 | 343 | # we have reached the absolute top of history 344 | if [[ $HISTNO -eq 1 ]]; then 345 | BUFFER= 346 | 347 | # going up from somewhere below the top of history 348 | else 349 | zle up-line-or-history 350 | fi 351 | 352 | return 0 353 | fi 354 | 355 | return 1 356 | } 357 | 358 | _history-substring-search-down-history() { 359 | # 360 | # Behave like down-history in ZSH, except clear the 361 | # $BUFFER when end of history is reached like in Fish. 362 | # 363 | if [[ -z $_history_substring_search_query ]]; then 364 | 365 | # going down from the absolute top of history 366 | if [[ $HISTNO -eq 1 && -z $BUFFER ]]; then 367 | BUFFER=${history[1]} 368 | _history_substring_search_refresh_display=1 369 | 370 | # going down from somewhere above the bottom of history 371 | else 372 | zle down-line-or-history 373 | fi 374 | 375 | return 0 376 | fi 377 | 378 | return 1 379 | } 380 | 381 | _history-substring-search-not-found() { 382 | # 383 | # Nothing matched the search query, so put it back into the $BUFFER while 384 | # highlighting it accordingly so the user can revise it and search again. 385 | # 386 | _history_substring_search_old_buffer=$BUFFER 387 | BUFFER=$_history_substring_search_query 388 | _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND 389 | } 390 | 391 | _history-substring-search-up-search() { 392 | _history_substring_search_refresh_display=1 393 | 394 | # 395 | # Highlight matches during history-substring-up-search: 396 | # 397 | # The following constants have been initialized in 398 | # _history-substring-search-up/down-search(): 399 | # 400 | # $_history_substring_search_matches is the current list of matches 401 | # $_history_substring_search_matches_count is the current number of matches 402 | # $_history_substring_search_matches_count_plus is the current number of matches + 1 403 | # $_history_substring_search_matches_count_sans is the current number of matches - 1 404 | # $_history_substring_search_match_index is the index of the current match 405 | # 406 | # The range of values that $_history_substring_search_match_index can take 407 | # is: [0, $_history_substring_search_matches_count_plus]. A value of 0 408 | # indicates that we are beyond the end of 409 | # $_history_substring_search_matches. A value of 410 | # $_history_substring_search_matches_count_plus indicates that we are beyond 411 | # the beginning of $_history_substring_search_matches. 412 | # 413 | # In _history-substring-search-up-search() the initial value of 414 | # $_history_substring_search_match_index is 415 | # $_history_substring_search_matches_count_plus. This value is set in 416 | # _history-substring-search-begin(). _history-substring-search-up-search() 417 | # will initially decrease it to $_history_substring_search_matches_count. 418 | # 419 | if [[ $_history_substring_search_match_index -ge 2 ]]; then 420 | # 421 | # Highlight the next match: 422 | # 423 | # 1. Decrease the value of $_history_substring_search_match_index. 424 | # 425 | # 2. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 426 | # to highlight the current buffer. 427 | # 428 | (( _history_substring_search_match_index-- )) 429 | BUFFER=$history[$_history_substring_search_matches[$_history_substring_search_match_index]] 430 | _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 431 | 432 | elif [[ $_history_substring_search_match_index -eq 1 ]]; then 433 | # 434 | # We will move beyond the end of $_history_substring_search_matches: 435 | # 436 | # 1. Decrease the value of $_history_substring_search_match_index. 437 | # 438 | # 2. Save the current buffer in $_history_substring_search_old_buffer, 439 | # so that it can be retrieved by 440 | # _history-substring-search-down-search() later. 441 | # 442 | # 3. Make $BUFFER equal to $_history_substring_search_query. 443 | # 444 | # 4. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND 445 | # to highlight the current buffer. 446 | # 447 | (( _history_substring_search_match_index-- )) 448 | _history-substring-search-not-found 449 | 450 | elif [[ $_history_substring_search_match_index -eq $_history_substring_search_matches_count_plus ]]; then 451 | # 452 | # We were beyond the beginning of $_history_substring_search_matches but 453 | # UP makes us move back to $_history_substring_search_matches: 454 | # 455 | # 1. Decrease the value of $_history_substring_search_match_index. 456 | # 457 | # 2. Restore $BUFFER from $_history_substring_search_old_buffer. 458 | # 459 | # 3. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 460 | # to highlight the current buffer. 461 | # 462 | (( _history_substring_search_match_index-- )) 463 | BUFFER=$_history_substring_search_old_buffer 464 | _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 465 | 466 | else 467 | # 468 | # We are at the beginning of history and there are no further matches. 469 | # 470 | _history-substring-search-not-found 471 | return 472 | fi 473 | 474 | # 475 | # When HIST_FIND_NO_DUPS is set, meaning that only unique command lines from 476 | # history should be matched, make sure the new and old results are different. 477 | # But when HIST_IGNORE_ALL_DUPS is set, ZSH already ensures a unique history. 478 | # 479 | if [[ ! -o HIST_IGNORE_ALL_DUPS && -o HIST_FIND_NO_DUPS && $BUFFER == $_history_substring_search_result ]]; then 480 | # 481 | # Repeat the current search so that a different (unique) match is found. 482 | # 483 | _history-substring-search-up-search 484 | fi 485 | } 486 | 487 | _history-substring-search-down-search() { 488 | _history_substring_search_refresh_display=1 489 | 490 | # 491 | # Highlight matches during history-substring-up-search: 492 | # 493 | # The following constants have been initialized in 494 | # _history-substring-search-up/down-search(): 495 | # 496 | # $_history_substring_search_matches is the current list of matches 497 | # $_history_substring_search_matches_count is the current number of matches 498 | # $_history_substring_search_matches_count_plus is the current number of matches + 1 499 | # $_history_substring_search_matches_count_sans is the current number of matches - 1 500 | # $_history_substring_search_match_index is the index of the current match 501 | # 502 | # The range of values that $_history_substring_search_match_index can take 503 | # is: [0, $_history_substring_search_matches_count_plus]. A value of 0 504 | # indicates that we are beyond the end of 505 | # $_history_substring_search_matches. A value of 506 | # $_history_substring_search_matches_count_plus indicates that we are beyond 507 | # the beginning of $_history_substring_search_matches. 508 | # 509 | # In _history-substring-search-down-search() the initial value of 510 | # $_history_substring_search_match_index is 511 | # $_history_substring_search_matches_count. This value is set in 512 | # _history-substring-search-begin(). 513 | # _history-substring-search-down-search() will initially increase it to 514 | # $_history_substring_search_matches_count_plus. 515 | # 516 | if [[ $_history_substring_search_match_index -le $_history_substring_search_matches_count_sans ]]; then 517 | # 518 | # Highlight the next match: 519 | # 520 | # 1. Increase $_history_substring_search_match_index by 1. 521 | # 522 | # 2. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 523 | # to highlight the current buffer. 524 | # 525 | (( _history_substring_search_match_index++ )) 526 | BUFFER=$history[$_history_substring_search_matches[$_history_substring_search_match_index]] 527 | _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 528 | 529 | elif [[ $_history_substring_search_match_index -eq $_history_substring_search_matches_count ]]; then 530 | # 531 | # We will move beyond the beginning of $_history_substring_search_matches: 532 | # 533 | # 1. Increase $_history_substring_search_match_index by 1. 534 | # 535 | # 2. Save the current buffer in $_history_substring_search_old_buffer, so 536 | # that it can be retrieved by _history-substring-search-up-search() 537 | # later. 538 | # 539 | # 3. Make $BUFFER equal to $_history_substring_search_query. 540 | # 541 | # 4. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND 542 | # to highlight the current buffer. 543 | # 544 | (( _history_substring_search_match_index++ )) 545 | _history-substring-search-not-found 546 | 547 | elif [[ $_history_substring_search_match_index -eq 0 ]]; then 548 | # 549 | # We were beyond the end of $_history_substring_search_matches but DOWN 550 | # makes us move back to the $_history_substring_search_matches: 551 | # 552 | # 1. Increase $_history_substring_search_match_index by 1. 553 | # 554 | # 2. Restore $BUFFER from $_history_substring_search_old_buffer. 555 | # 556 | # 3. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 557 | # to highlight the current buffer. 558 | # 559 | (( _history_substring_search_match_index++ )) 560 | BUFFER=$_history_substring_search_old_buffer 561 | _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND 562 | 563 | else 564 | # 565 | # We are at the end of history and there are no further matches. 566 | # 567 | _history-substring-search-not-found 568 | return 569 | fi 570 | 571 | # 572 | # When HIST_FIND_NO_DUPS is set, meaning that only unique command lines from 573 | # history should be matched, make sure the new and old results are different. 574 | # But when HIST_IGNORE_ALL_DUPS is set, ZSH already ensures a unique history. 575 | # 576 | if [[ ! -o HIST_IGNORE_ALL_DUPS && -o HIST_FIND_NO_DUPS && $BUFFER == $_history_substring_search_result ]]; then 577 | # 578 | # Repeat the current search so that a different (unique) match is found. 579 | # 580 | _history-substring-search-down-search 581 | fi 582 | } 583 | 584 | # -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- 585 | # vim: ft=zsh sw=2 ts=2 et 586 | -------------------------------------------------------------------------------- /.zshrc: -------------------------------------------------------------------------------- 1 | alias ls='ls -GF --color=auto' 2 | 3 | # http://stackoverflow.com/a/2534676 4 | autoload -U colors && colors 5 | 6 | # # Open new terminals in the same directory as the current terminal. 7 | if [ -f /etc/profile.d/vte.sh ]; then 8 | source /etc/profile.d/vte.sh 9 | fi 10 | 11 | my_colors=("$fg_bold[red]" "$fg_bold[blue]" "$fg_bold[green]" "$fg_bold[cyan]" "$fg_bold[yellow]" "$fg_bold[magenta]") 12 | hostname=$(uname -n) 13 | 14 | # Choose a color based on the length of the hostname, so the color is 15 | # usually unique to the machine. 16 | my_colors_index=$((${#hostname} % ${#my_colors[@]})) 17 | # zsh arrays are 1-indexed. 18 | my_colors_index_zsh=$(($my_colors_index+1)) 19 | 20 | prompt_color="${my_colors[$my_colors_index_zsh]}" 21 | 22 | # E.g. 23 | # mybox /tmp/foo 24 | # $ 25 | PS1=$'\n'"%{$prompt_color%}"$(uname -n)" %~ %(?..exit:%? ) "$'\n'"$ %{$reset_color%}" 26 | 27 | # tab completion 28 | autoload -Uz compinit && compinit 29 | 30 | # Store execution time taken in history, visible with `history -D`. 31 | setopt inc_append_history_time 32 | 33 | # Report the time taken for commands that take more than 10 seconds to terminate. 34 | REPORTTIME=10 35 | 36 | # Ensure that Alt-Backspace only deletes up to the last slash, 37 | # not the whole path. 38 | # http://stackoverflow.com/a/1438523/509706 39 | # https://github.com/zsh-users/zsh-syntax-highlighting/issues/67 40 | autoload -U select-word-style 41 | select-word-style bash 42 | 43 | if [ -f /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]; then 44 | source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh 45 | fi 46 | if [ -f /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]; then 47 | source /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh 48 | fi 49 | 50 | if [ -f /usr/share/fzf/key-bindings.zsh ]; then 51 | # Set up fzf key bindings and fuzzy completion. 52 | source <(fzf --zsh) 53 | fi 54 | 55 | # Don't show the command number in history, just the command itself. 56 | export FZF_CTRL_R_OPTS="--with-nth 2.." 57 | 58 | # Show a file preview when doing ctrl-t, using bat. 59 | # https://github.com/junegunn/fzf#key-bindings-for-command-line 60 | export FZF_CTRL_T_OPTS="--preview 'bat -n --color=always {}'" 61 | 62 | # Exact matches by default, and show the result at the top rather than 63 | # the bottom. Show the result count on the right, on the same line as 64 | # the prompt. 65 | export FZF_DEFAULT_OPTS='--layout=reverse --exact --info=inline-right' 66 | 67 | export FZF_DEFAULT_COMMAND='rg --files' 68 | 69 | source ~/.zsh-autosuggestions.zsh 70 | 71 | source ~/.zsh-history-substring-search.zsh 72 | zmodload zsh/terminfo 73 | bindkey "$terminfo[kcuu1]" history-substring-search-up 74 | bindkey "$terminfo[kcud1]" history-substring-search-down 75 | 76 | bindkey '^[[A' history-substring-search-up 77 | bindkey '^[[B' history-substring-search-down 78 | 79 | source ~/.zsh-histdb/sqlite-history.zsh 80 | autoload -Uz add-zsh-hook 81 | 82 | alias ..='cd ..' 83 | 84 | ## History 85 | HISTFILE=$HOME/.zhistory # enable history saving on shell exit 86 | setopt APPEND_HISTORY # append rather than overwrite history file. 87 | 88 | # History. I'm seeing roughly 500 commands per month. 89 | HISTSIZE=10000 # lines of history to maintain memory 90 | SAVEHIST=10000 # lines of history to maintain in history file. 91 | setopt HIST_EXPIRE_DUPS_FIRST # allow dups, but expire old ones when I hit HISTSIZE 92 | setopt EXTENDED_HISTORY # save timestamp and runtime information 93 | 94 | # allow bash-style # comments 95 | setopt interactivecomments 96 | 97 | # If we run a command that's already in the history, remove the old version. 98 | # This means we can't necessarily replay history, but avoids duplicates. 99 | setopt histignorealldups 100 | 101 | export PATH=~/bin:$PATH 102 | export PATH=$PATH:~/.cabal/bin 103 | 104 | # Cask installs to ~/.local/bin. 105 | export PATH=$PATH:~/.local/bin 106 | 107 | export PATH="$PATH":~/.gem/ruby/3.0.0/bin 108 | export PATH="$HOME/.cargo/bin:$PATH" 109 | export PATH="/home/wilfred/.evm/bin:$PATH" 110 | 111 | # Ensure 'npm install -g' works without sudo, based on 112 | # https://github.com/sindresorhus/guides/blob/master/npm-global-without-sudo.md 113 | export NPM_PACKAGES="${HOME}/.npm-packages" 114 | export PATH="$NPM_PACKAGES/bin:$PATH" 115 | 116 | export EDITOR=zile 117 | 118 | export MYSQL_PS1="\u@\h:mysql> " 119 | alias mysql-color="rlwrap --always-readline --prompt-colour=GREEN mysql" 120 | 121 | alias docker='sudo docker' 122 | 123 | alias sst='svn status' 124 | alias gst='git status' 125 | 126 | # quieten gdb's verbose startup 127 | alias gdb="gdb -q" 128 | 129 | # quick and dirty compile alias 130 | alias compile='gcc -Wall -Wextra -g' 131 | 132 | alias open="xdg-open" 133 | 134 | alias serve='python2 -m SimpleHTTPServer' 135 | 136 | alias apt-install="sudo apt-get install" 137 | apt-search () { apt-cache search $* | less } 138 | 139 | 140 | # less colours -- since man uses less as a pager, this gives us 141 | # coloured man pages 142 | export LESS_TERMCAP_mb=$'\E[01;31m' # begin blinking 143 | export LESS_TERMCAP_md=$'\E[01;38;5;74m' # begin bold 144 | export LESS_TERMCAP_me=$'\E[0m' # end mode 145 | export LESS_TERMCAP_se=$'\E[0m' # end standout-mode 146 | export LESS_TERMCAP_so=$'\E[38;5;246m' # begin standout-mode - info box 147 | export LESS_TERMCAP_ue=$'\E[0m' # end underline 148 | export LESS_TERMCAP_us=$'\E[04;38;5;146m' # begin underline 149 | 150 | # Support ANSI color sequences, truncate lines (don't wrap), and use 151 | # smart case search (rather than case sensitive). 152 | # https://stackoverflow.com/a/26069/509706 153 | export LESS=-RSi 154 | 155 | # share history between terminals, from http://askubuntu.com/q/23630 156 | setopt inc_append_history 157 | setopt share_history 158 | 159 | if hash zoxide 2>/dev/null; then 160 | eval "$(zoxide init zsh)" 161 | alias zz=zi 162 | fi 163 | 164 | if [ -f /usr/bin/virtualenvwrapper.sh ]; then 165 | export WORKON_HOME=$HOME/.envs 166 | export VIRTUALENVWRAPPER_SCRIPT=/usr/bin/virtualenvwrapper.sh 167 | source /usr/bin/virtualenvwrapper_lazy.sh 168 | fi 169 | 170 | # Don't use cowsay with ansible 171 | export ANSIBLE_NOCOWS=1 172 | 173 | # all the interesting windows apps I use are 32-bit 174 | export WINEARCH=win32 175 | 176 | # Work around invisible null on dark themes. 177 | # https://github.com/stedolan/jq/issues/1972 178 | export JQ_COLORS='0;37:0;39:0;39:0;39:0;32:1;39:1;39' 179 | 180 | export NINJA_STATUS="[done:%f doing:%r left:%u elapsed:%e] " 181 | 182 | # Expand 'cd -' to previously visited directories. 183 | # http://unix.stackexchange.com/a/157773/61642 184 | setopt AUTO_PUSHD 185 | zstyle ':completion:*:directory-stack' list-colors '=(#b) #([0-9]#)*( *)==95=38;5;12' 186 | 187 | # Make a directory and CD into it. 188 | mcd() { 189 | mkdir $1 && cd $1 190 | } 191 | 192 | # CD to Git root. 193 | cdg() { 194 | # https://stackoverflow.com/questions/957928/is-there-a-way-to-get-the-git-root-directory-in-one-command/38852055#38852055 195 | local r 196 | r=$(git rev-parse --git-dir) && r=$(cd "$r" && pwd)/ && cd "${r%%/.git/*}" 197 | } 198 | 199 | gco() { 200 | local tags branches target 201 | branches=$( 202 | git --no-pager branch --all --sort=-committerdate \ 203 | --format="%(if)%(HEAD)%(then)%(else)%(if:equals=HEAD)%(refname:strip=3)%(then)%(else)%1B[0;34;1mbranch%09%1B[m%(refname:short)%(end)%(end)" \ 204 | | sed '/^$/d') || return 205 | tags=$( 206 | git --no-pager tag | awk '{print "\x1b[35;1mtag\x1b[m\t" $1}') || return 207 | target=$( 208 | (echo "$branches"; echo "$tags") | 209 | fzf --height=20% --no-hscroll --no-multi -n 2 \ 210 | --ansi --preview="git --no-pager log -150 --pretty=format:%s '..{2}'") || return 211 | git checkout $(awk '{print $2}' <<<"$target" ) 212 | } 213 | -------------------------------------------------------------------------------- /bin/git-prune-local: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # see http://stackoverflow.com/a/17029936/509706 4 | git branch -r | awk '{print $1}' | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk '{print $1}' | xargs git branch -d; 5 | -------------------------------------------------------------------------------- /bin/post-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script is intended to give a nice log of every git repository 4 | # that has been worked on from this box. Add them to every repository: 5 | # http://stackoverflow.com/questions/2293498/git-commit-hooks-global-settings 6 | 7 | repo_path=`git rev-parse --show-toplevel` 8 | commit_message=`git log --oneline -1` 9 | now=`date "+%Y-%m-%d %H:%M"` 10 | 11 | echo "$now $repo_path $commit_message" >> ~/commits.log 12 | -------------------------------------------------------------------------------- /make_links.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | 7 | class FileExists(OSError): pass 8 | 9 | def create_symlink(source_path, target, overwrite=False): 10 | if os.path.exists(target): 11 | if overwrite: 12 | os.unlink(target) 13 | else: 14 | raise FileExists() 15 | 16 | elif os.path.islink(target): 17 | # It's a broken symlink. 18 | if overwrite: 19 | os.unlink(target) 20 | else: 21 | raise FileExists("Broken symlink exists") 22 | 23 | target_parent = os.path.dirname(target) 24 | if not os.path.exists(target_parent): 25 | os.makedirs(target_parent) 26 | 27 | os.symlink(source_path, target) 28 | 29 | 30 | def is_configuration_file(path): 31 | file_name = os.path.basename(path) 32 | 33 | # skip .git files specific to the repo 34 | if file_name in ['.git', '.gitignore']: 35 | return False 36 | 37 | # emacs backup files 38 | if file_name.startswith('.#'): 39 | return False 40 | 41 | if file_name.startswith('.'): 42 | return True 43 | 44 | return False 45 | 46 | 47 | def get_configuration_file_names(path): 48 | for file_name in os.listdir(path): 49 | absolute_path = os.path.join(path, file_name) 50 | 51 | if is_configuration_file(absolute_path): 52 | 53 | if os.path.isdir(absolute_path): 54 | for nested_file_name in os.listdir(absolute_path): 55 | yield os.path.join(file_name, nested_file_name) 56 | else: 57 | yield file_name 58 | 59 | 60 | if __name__ == '__main__': 61 | home_path = os.path.expanduser('~') 62 | 63 | script_path = os.path.realpath(__file__) 64 | dotfiles_path = os.path.dirname(script_path) 65 | 66 | overwrite = False 67 | if '--force' in sys.argv: 68 | overwrite = True 69 | 70 | for file_name in get_configuration_file_names(dotfiles_path): 71 | source_path = os.path.join(dotfiles_path, file_name) 72 | target_path = os.path.join(home_path, file_name) 73 | 74 | try: 75 | create_symlink(source_path, target_path, overwrite) 76 | print("Linking %s to %s" % (source_path, target_path)) 77 | except FileExists: 78 | print("There is already a file at %s, skipping." % target_path) 79 | 80 | 81 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Dot files 2 | 3 | A collection of configuration files (shells, VCS and so on) that I use 4 | on all my *nix machines. 5 | 6 | ## Usage 7 | 8 | You'll want to install zsh and oh-my-zsh first. 9 | 10 | Grab the repo, then run make_links.py. 11 | 12 | $ git clone git://github.com/Wilfred/dotfiles.git 13 | $ cd dotfiles 14 | $ ./make_links.py 15 | Linking /home/wilfred/dotfiles/.gitconfig to /home/wilfred/.gitconfig 16 | Linking /home/wilfred/dotfiles/.zshrc to /home/wilfred/.zshrc 17 | 18 | If you have old configuration you want to wipe, pass `--force` to make_links: 19 | 20 | $ ./make_links.py --force 21 | 22 | make_links will copy any file whose name is `.FOO` or `.FOO/BAR`, 23 | except git metadata. 24 | 25 | ## Other executables 26 | 27 | I use ag ('the silver searcher'), ack (a slower ag), git-fuzzy (on my 28 | GitHub) and icdiff. 29 | --------------------------------------------------------------------------------