├── .gitattributes ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .hgignore ├── .hushlogin ├── .inputrc ├── .ssh └── config ├── Brewfile ├── README.md ├── bin ├── battery ├── battery_indicator.sh ├── colortest ├── confirm ├── extract ├── fh ├── fromhex ├── git-clc ├── git-delete-merged-branches ├── git-kill ├── git-modified ├── isdir ├── isfile ├── login-shell ├── prc ├── reason-language-server ├── reload-browser ├── renameFile.zsh ├── replace ├── subl └── tm ├── commands.md ├── config └── kitty │ ├── Symbols-2048-em Nerd Font Complete.ttf │ ├── kitty-dark.icns │ ├── kitty-light.icns │ ├── kitty-theme-custom.conf │ ├── kitty-theme-tokyo-night.conf │ ├── kitty.conf │ ├── symbolmap.conf │ └── test-fonts.sh ├── gitattributes.symlink ├── gitconfig.symlink ├── hammerspoon ├── Spoons │ ├── AppLauncher.spoon │ │ ├── docs.json │ │ └── init.lua │ ├── Cherry.spoon │ │ ├── docs.json │ │ └── init.lua │ ├── ShiftIt.spoon │ │ └── init.lua │ └── SpoonInstall.spoon │ │ ├── docs.json │ │ └── init.lua ├── crypto.lua ├── grid.lua ├── init.lua ├── menubar.lua ├── pomodoro.lua ├── session.lua ├── weather.lua └── wifi.lua ├── hosts-allowlist ├── hosts-denylist ├── install.sh ├── npmbin ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .nvmrc ├── .yarnrc.yml ├── README.md ├── package.json └── yarn.lock ├── nvim-custom ├── chadrc.lua ├── configs │ ├── conform.lua │ ├── copilot.lua │ ├── lspconfig.lua │ └── overrides.lua ├── highlights.lua ├── init.lua ├── mappings.lua └── plugins.lua ├── resources ├── tmux-256color-italic.terminfo ├── tmux.terminfo └── xterm-256color-italic.terminfo ├── rgrc.symlink ├── screenshots ├── forgit.png ├── git-diff.png ├── neovim.png └── tmux.png ├── scripts ├── dock.sh ├── osx.sh └── prezto_setup.zsh ├── settings └── Preferences.sublime-settings ├── tmux ├── base16.sh ├── theme.sh └── tmux.conf.symlink └── zsh └── functions ├── fzf-functions ├── gpo └── misc-functions /.gitattributes: -------------------------------------------------------------------------------- 1 | *.lua filter=gitignore 2 | * filter=gitignore 3 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '30 14 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'ruby' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # working files 2 | .vim/viminfo 3 | .vim/swaps/* 4 | .vim/undo 5 | 6 | # git credential file 7 | .gitconfig.local 8 | 9 | # Folder view configuration files 10 | .DS_Store 11 | Desktop.ini 12 | 13 | # Thumbnail cache files 14 | ._* 15 | Thumbs.db 16 | 17 | # Files that might appear on external disks 18 | .Spotlight-V100 19 | .Trashes 20 | 21 | # Compiled Python files 22 | *.pyc 23 | 24 | # Compiled C++ files 25 | *.out 26 | 27 | # Application specific files 28 | venv 29 | node_modules 30 | .sass-cache 31 | 32 | # Ignore neovim nethistory 33 | .netrwhist 34 | 35 | config/heroku 36 | config/configstore 37 | alfred 38 | 39 | npm-debug.log 40 | 41 | # nvim related 42 | config/nvim/spell 43 | config/nvim/shada 44 | config/nvim/plugged 45 | config/nvim/autoload/plug.vim 46 | vim/vim.symlink/spell/ 47 | vim/vim.symlink/plugged/ 48 | plug.vim.old 49 | 50 | # brewfile 51 | Brewfile.lock.json 52 | 53 | # stevenblack/hosts 54 | hosts 55 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | # Use shell-style glob syntax 2 | syntax: glob 3 | 4 | # Compiled Python files 5 | *.pyc 6 | 7 | # Folder view configuration files 8 | .DS_Store 9 | Desktop.ini 10 | 11 | # Thumbnail cache files 12 | ._* 13 | Thumbs.db 14 | 15 | # Files that might appear on external disks 16 | .Spotlight-V100 17 | .Trashes 18 | -------------------------------------------------------------------------------- /.hushlogin: -------------------------------------------------------------------------------- 1 | # The mere presence of this file in the home directory disables the system 2 | # copyright notice, the date and time of the last login, the message of the 3 | # day as well as other information that may otherwise appear on login. 4 | # See `man login`. 5 | -------------------------------------------------------------------------------- /.inputrc: -------------------------------------------------------------------------------- 1 | # Make Tab autocomplete regardless of filename case 2 | set completion-ignore-case on 3 | 4 | # List all matches in case multiple possible completions are possible 5 | set show-all-if-ambiguous on 6 | 7 | # Immediately add a trailing slash when autocompleting symlinks to directories 8 | set mark-symlinked-directories on 9 | 10 | # Use the text that has already been typed as the prefix for searching through 11 | # commands (i.e. more intelligent Up/Down behavior) 12 | "\e[B": history-search-forward 13 | "\e[A": history-search-backward 14 | 15 | # Do not autocomplete hidden files unless the pattern explicitly begins with a dot 16 | set match-hidden-files off 17 | 18 | # Show all autocomplete results at once 19 | set page-completions off 20 | 21 | # If there are more than 200 possible completions for a word, ask to show them all 22 | set completion-query-items 200 23 | 24 | # Show extra file information when completing, like `ls -F` does 25 | set visible-stats on 26 | 27 | # Be more intelligent when autocompleting by also looking at the text after 28 | # the cursor. For example, when the current line is "cd ~/src/mozil", and 29 | # the cursor is on the "z", pressing Tab will not autocomplete it to "cd 30 | # ~/src/mozillail", but to "cd ~/src/mozilla". (This is supported by the 31 | # Readline used by Bash 4.) 32 | set skip-completed-text on 33 | 34 | # Allow UTF-8 input and output, instead of showing stuff like $'\0123\0456' 35 | set input-meta on 36 | set output-meta on 37 | set convert-meta off 38 | 39 | # Use Alt/Meta + Delete to delete the preceding word 40 | "\e[3;3~": kill-word 41 | -------------------------------------------------------------------------------- /.ssh/config: -------------------------------------------------------------------------------- 1 | # symlink to ~/.ssh/config 2 | # make sure to add new ssh key 3 | 4 | Host * 5 | AddKeysToAgent yes 6 | UseKeychain yes 7 | # IdentityFile ~/.ssh/... 8 | 9 | # Use a shared channel for all sessions to the same host, 10 | # instead of always opening a new one. This leads to much 11 | # quicker connection times. 12 | 13 | ControlMaster auto 14 | ControlPath ~/.ssh/control/%r@%h:%p 15 | ControlPersist 1800 16 | 17 | # also this stuff 18 | Compression yes 19 | TCPKeepAlive yes 20 | ServerAliveInterval 20 21 | ServerAliveCountMax 10 22 | 23 | Host github.com 24 | ControlMaster auto 25 | ControlPersist 120 26 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | if OS.mac? 2 | # Taps 3 | tap "homebrew/cask" 4 | tap "homebrew/cask-fonts" 5 | 6 | brew "trash" # rm, but faster since it goes in the trash 7 | # https://github.com/kcrawford/dockutil/issues/127#issuecomment-1118733013 8 | # Broken until the owner wants to support this 9 | # brew "dockutil" # https://github.com/kcrawford/dockutil 10 | cask "hpedrorodrigues/tools/dockutil" 11 | 12 | # Apps 13 | cask "kitty" # better terminal 14 | cask "imageoptim" # image optimization tool 15 | cask "hammerspoon" # automation https://www.hammerspoon.org/ 16 | cask "1password/tap/1password-cli" 17 | 18 | # Fonts 19 | cask "font-iosevka" 20 | # cask "font-3270-nerd-font" 21 | 22 | elsif OS.linux? 23 | brew "xclip" # access to clipboard (similar to pbcopy/pbpaste) 24 | end 25 | 26 | tap "homebrew/bundle" 27 | tap "homebrew/core" 28 | 29 | brew "bat" # modern cat https://github.com/sharkdp/bat 30 | brew "bit-git" # modern git cli https://github.com/chriswalz/bit#how-to-install 31 | brew "curl" # https://github.com/curl/curl 32 | brew "exa" # ls replacement https://github.com/ogham/exa 33 | brew "fzf" # fuzzy-finder https://github.com/junegunn/fzf 34 | brew "fd" # modern find https://github.com/sharkdp/fd 35 | brew "gh" # github CLI https://github.com/cli/cli 36 | brew "git" # latest 37 | 38 | brew "git-delta" # better git diff https://github.com/dandavison/delta 39 | brew "jq" #jq shell scripts 40 | 41 | brew "lazydocker" # cli gui https://github.com/jesseduffield/lazydocker 42 | brew "lazygit" # cli gui https://github.com/jesseduffield/lazygit 43 | brew "libpq" # psql postgres cli 44 | brew "mas" # Mac automation https://github.com/mas-cli/mas 45 | brew "neovim" # better vim 46 | 47 | brew "pigz" # better tar https://github.com/madler/pigz 48 | brew "python" # latest 49 | 50 | brew "ruby" 51 | brew "redis" 52 | brew "ripgrep" # Modern grep https://github.com/BurntSushi/ripgrep 53 | brew "fnm" # fast node manager 54 | brew "shellcheck" # https://github.com/koalaman/shellcheck 55 | brew "flyctl" # https://github.com/superfly/fly 56 | brew "sd" # Modern sed https://github.com/chmln/sd 57 | 58 | # Simplified and community-driven man pages 59 | # https://github.com/tldr-pages/tldr 60 | # brew "tldr" use global npm package 61 | brew "tmux" 62 | brew "trash" # https://hasseg.org/trash/ 63 | brew "tree" 64 | 65 | brew "wget" 66 | brew "wifi-password" 67 | brew "watchman" # file watcher, used by coc 68 | brew "youtube-dl" 69 | brew "zoxide" # Modern z https://github.com/ajeetdsouza/zoxide 70 | brew "zsh" # zsh (latest) 71 | 72 | ## Casks - actual visual software 73 | 74 | cask "appcleaner" 75 | cask "alfred" 76 | cask "backblaze" 77 | cask "battle-net" 78 | cask "brave-browser" 79 | cask "calibre" 80 | cask "charles" 81 | cask "chromium" 82 | cask "docker" 83 | cask "discord" 84 | cask "focus" 85 | cask "iterm2" 86 | cask "licecap" 87 | cask "notion" 88 | cask "postico" 89 | cask "postman" 90 | cask "sublime-text" 91 | cask "signal" 92 | cask "spotify" 93 | cask "steam" 94 | cask "visual-studio-code" 95 | cask "zoom" 96 | 97 | # Mac App Store Installations 98 | mas "Numbers", id: 409203825 99 | # mas "1Password 7", id: 1333542190 download from their site 100 | mas "Xcode", id: 497799835 101 | mas "NextDNS", id: 1464122853 102 | mas "DaisyDisk", id: 411643860 103 | mas "Fantastical", id: 975937182 104 | mas "Slack", id: 803453959 105 | mas "Telegram", id: 747648890 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Peter's dotfiles 2 | 3 | Dotfiles and automations that make my life easier (or harder lol) 4 | 5 | - [Prezto](https://github.com/sorin-ionescu/prezto) 6 | A lightweight zsh configuration framework with sensible defaults. It's fast, too! 7 | 8 | - [Homebrew](https://brew.sh) 9 | The Missing Package Manager for MacOS (or Linux) 10 | 11 | - [Mac App Store CLI](https://github.com/mas-cli/mas) 12 | A CLI for the Mac App Store. Installs all your favorite apps in just 1 line! 13 | 14 | - [Hammerspoon](https://github.com/Hammerspoon/hammerspoon) 15 | Staggeringly powerful MacOS desktop automation with Lua 16 | 17 | - [Neovim](https://neovim.io/) 18 | A modern, ground up rewrite of Vim 19 | 20 | - [Kitty](https://sw.kovidgoyal.net/kitty/) 21 | A fast, GPU based terminal alternative to iTerm 22 | 23 | - [Tmux](https://github.com/tmux/tmux/wiki) 24 | Create, split, save, move terminal tabs easily all within one window. 25 | 26 | - [Fzf](https://github.com/junegunn/fzf) 27 | The fastest way to search for ANYTHING on your computer 28 | 29 | - [Forgit](https://github.com/wfxr/forgit) 30 | Use git interactively. Powered by fzf 31 | 32 | - [PowerLevel10k](https://github.com/romkatv/powerlevel10k) 33 | A zsh theme that emphasizes speed, flexibility and an out-of-the-box experience 34 | 35 | ## Screenshots 36 | 37 | ## Getting Started 38 | 39 | Check out `./install.sh`. You'll have to run `./install all` or pick the section you'd like to run. 40 | 41 | - Install Homebrew & dependencies 42 | - Install Xcode and Xcode CLI tools 43 | - Setup symlinks and config 44 | 45 | ## Post-automation updates 46 | - Install [NVChad](https://nvchad.github.io/quickstart/install#pre-requisites) if you haven't yet. This replaces the local nvim config 47 | 48 | Files that store personal info or api keys are gitignored. Make sure you either comment these references out, or set them up: 49 | 50 | - `~/.npmrc` 51 | - `~/.gitconfig.local` 52 | - `~/.ssh/config` 53 | - [Nicer kitty icon](https://github.com/DinkDonk/kitty-icon) 54 | - Download & Install into `~/.local/share/fonts/` [Symbols-2048-em Nerd Font Complete.ttf](https://github.com/ryanoasis/nerd-fonts/blob/master/src/glyphs/Symbols-2048-em%20Nerd%20Font%20Complete.ttf) 55 | 56 | ## Vim and Neovim Setup 57 | 58 | [Neovim](https://neovim.io/) is a fork and drop-in replacement for vim. in most cases, you would not notice a difference between the two, other than Neovim allows plugins to run asynchronously so that they do not freeze the editor, which is the main reason I have switched over to it. Vim and Neovim both use Vimscript and most plugins will work in both (all of the plugins I use do work in both Vim and Neovim). For this reason, they share the same configuration files in this setup. Neovim uses the [XDG base directory specification](http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) which means it won't look for a `.vimrc` in your home directory. Instead, its configuration looks like the following: 59 | 60 | | | Vim | Neovim | 61 | | ----------------------- | ---------- | ------------------------- | 62 | | Main Configuration File | `~/.vimrc` | `~/.config/nvim/init.vim` | 63 | | Configuration directory | `~/.vim` | `~/.config/nvim` | 64 | 65 | ## Thanks 66 | 67 | I've been working on my dotfiles for over 8 years. A lot of it is thanks to the community and some of my favorite people / projects: 68 | 69 | - [Maximum Awesome](https://github.com/square/maximum-awesome) 70 | - [Paul Irish Dotfiles](https://github.com/paulirish/dotfiles) 71 | - [Nick Nisi Dotfiles](https://github.com/nicknisi/dotfiles) 72 | - [Mathias Bynens Dotfiles](https://github.com/mathiasbynens/dotfiles) 73 | 74 | -------------------------------------------------------------------------------- /bin/battery: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pmset -g batt | egrep "([0-9]+\%).*" -o --colour=auto | cut -f1 -d';' 4 | -------------------------------------------------------------------------------- /bin/battery_indicator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # modified from http://ficate.com/blog/2012/10/15/battery-life-in-the-land-of-tmux/ 3 | 4 | HEART='♥ ' 5 | 6 | if [[ `uname` == 'Linux' ]]; then 7 | current_charge=$(cat /proc/acpi/battery/BAT1/state | grep 'remaining capacity' | awk '{print $3}') 8 | total_charge=$(cat /proc/acpi/battery/BAT1/info | grep 'last full capacity' | awk '{print $4}') 9 | else 10 | battery_info=`ioreg -rc AppleSmartBattery` 11 | current_charge=$(echo $battery_info | grep -o '"CurrentCapacity" = [0-9]\+' | awk '{print $3}') 12 | total_charge=$(echo $battery_info | grep -o '"MaxCapacity" = [0-9]\+' | awk '{print $3}') 13 | fi 14 | 15 | charged_slots=$(echo "((($current_charge/$total_charge)*10)/3)+1" | bc -l | cut -d '.' -f 1) 16 | if [[ $charged_slots -gt 3 ]]; then 17 | charged_slots=3 18 | fi 19 | 20 | echo -n '#[fg=colour196]' 21 | for i in `seq 1 $charged_slots`; do echo -n "$HEART"; done 22 | 23 | if [[ $charged_slots -lt 3 ]]; then 24 | echo -n '#[fg=colour254]' 25 | for i in `seq 1 $(echo "3-$charged_slots" | bc)`; do echo -n "$HEART"; done 26 | fi 27 | -------------------------------------------------------------------------------- /bin/colortest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | fg="" 4 | bg="" 5 | for i in {0..255}; do 6 | a=$(printf "\\x1b[38;5;%sm%3d\\e[0m " "$i" "$i") 7 | b=$(printf "\\x1b[48;5;%sm%3d\\e[0m " "$i" "$i") 8 | fg+="$a" 9 | bg+="$b" 10 | if (( "$i" % 5 ==0 )); then 11 | echo -e "$fg\\t\\t$bg" 12 | fg="" 13 | bg="" 14 | else 15 | fg+=" " 16 | bg+=" " 17 | fi 18 | done 19 | -------------------------------------------------------------------------------- /bin/confirm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | read -r -n 1 -p "$1 (y/n) " answer 4 | echo "" 5 | test "$answer" = "y" 6 | -------------------------------------------------------------------------------- /bin/extract: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Extract archives - use: extract 4 | # Credits to http://dotfiles.org/~pseup/.bashrc 5 | 6 | if [ -f "$1" ] ; then 7 | case "$1" in 8 | *.tar.bz2) tar xjf "$1" ;; 9 | *.tar.gz) tar xzf "$1" ;; 10 | *.bz2) bunzip2 "$1" ;; 11 | *.rar) rar x "$1" ;; 12 | *.gz) gunzip "$1" ;; 13 | *.tar) tar xf "$1" ;; 14 | *.tbz2) tar xjf "$1" ;; 15 | *.tgz) tar xzf "$1" ;; 16 | *.zip) unzip "$1" ;; 17 | *.Z) uncompress "$1" ;; 18 | *.7z) 7z x "$1" ;; 19 | *) echo "'$1' cannot be extracted via extract()" ;; 20 | esac 21 | else 22 | echo "'$1' is not a valid file" 23 | fi 24 | -------------------------------------------------------------------------------- /bin/fh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | print -z $( ([ -n "$ZSH_NAME" ] && fc -l 1 || history) | fzf +s --tac | sed 's/ *[0-9]* *//') 4 | -------------------------------------------------------------------------------- /bin/fromhex: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | hex=${1#"#"} 4 | r=$(printf '0x%0.2s' "$hex") 5 | g=$(printf '0x%0.2s' "${hex#??}") 6 | b=$(printf '0x%0.2s' "${hex#????}") 7 | printf '%03d' "$(( 8 | (r<75?0:(r-35)/40)*6*6 + (g<75?0:(g-35)/40)*6 + (b<75?0:(b-35)/40) + 16 9 | ))" 10 | -------------------------------------------------------------------------------- /bin/git-clc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | [[ -z $1 ]] && BRANCH=$( git rev-parse --apprev-ref HEAD ) || BRANCH=$1 4 | LAST_COMMIT_SHA=$( git rev-parse $BRANCH | tail -n 1 ) 5 | echo "$LAST_COMMIT_SHA" | tr -d '\n' | pbcopy 6 | echo "Copied ${LAST_COMMIT_SHA}." 7 | -------------------------------------------------------------------------------- /bin/git-delete-merged-branches: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | branches_to_die=$(git branch --no-color --merged origin/master | grep -v '\smaster$') 4 | echo "Local branches to be deleted:" 5 | echo "$branches_to_die" 6 | 7 | kill_branches(){ 8 | echo $branches_to_die | xargs -n 1 git branch -D 9 | } 10 | 11 | remote_branches_to_die=$(git branch --no-color --remote --merged origin/master | grep -v '\smaster$' | grep -v '\/master$' | grep -v "origin\/HEAD" | grep -v "origin\/master") 12 | echo "Remote branches to be deleted:" 13 | echo "$remote_branches_to_die" 14 | 15 | kill_remote_branches(){ 16 | # Remove remote branches 17 | for remote in $remote_branches_to_die 18 | do 19 | # branches=`echo "$remote_branches" | grep "$remote/" | sed 's/\(.*\)\/\(.*\)/:\2 /g' | tr -d '\n'` 20 | git branch -rD "$remote" 21 | done 22 | } 23 | 24 | echo "" 25 | echo "Enter Y to confirm" 26 | read -p "> " confirm 27 | 28 | [[ $confirm == "Y" ]] && kill_branches && kill_remote_branches 29 | 30 | echo "" 31 | echo "Pruning all remotes" 32 | git remote | xargs -n 1 git remote prune 33 | -------------------------------------------------------------------------------- /bin/git-kill: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ $# == 0 ]]; then 4 | echo "Must provide at least one branch" 5 | exit 1 6 | fi 7 | 8 | for branch in "$@"; do 9 | git branch -D "$branch" 10 | for r in $(git remote); do 11 | git push "$r" :"$branch" 12 | done 13 | done 14 | -------------------------------------------------------------------------------- /bin/git-modified: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | git ls-files | xargs -I{} git log -1 --format="%ai {}" {} 4 | -------------------------------------------------------------------------------- /bin/isdir: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | test -d "$1" 4 | -------------------------------------------------------------------------------- /bin/isfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | test -f "$1" 4 | -------------------------------------------------------------------------------- /bin/login-shell: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | shell=$(basename "$SHELL") 4 | 5 | # check if reattach-to-user-namespace is available 6 | if [ -n "$(command -v reattach-to-user-namespace)" ]; then 7 | reattach-to-user-namespace -l "$shell" 8 | else 9 | exec "$shell -l" 10 | fi 11 | -------------------------------------------------------------------------------- /bin/prc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the current directory 4 | current_dir=$(pwd) 5 | 6 | # Check if the current directory contains "backpack" 7 | if [[ $current_dir != *"backpack"* ]]; then 8 | echo "can only be run inside backpack" 9 | exit 1 10 | fi 11 | 12 | # Define prefix based on current directory 13 | prefix="" 14 | 15 | # Handle backpack-api directory structure 16 | if [[ $current_dir == *"/backpack-api/workers-expo-eas-services"* ]]; then 17 | prefix="workers/mobile-worker:" 18 | elif [[ $current_dir == *"/backpack-api/"* ]]; then 19 | # Extract the relative path after backpack-api and prefix it 20 | relative_path=${current_dir#*"/backpack-api/"} 21 | # Ensure the relative path doesn't end with a slash 22 | relative_path=${relative_path%/} 23 | if [[ -n "$relative_path" ]]; then # Check if the relative path is not empty 24 | prefix="${relative_path}:" 25 | fi 26 | elif [[ $current_dir == *"backpack/packages/"* ]]; then 27 | prefix=$(basename "$current_dir")":" 28 | elif [[ $current_dir == *"backpack"* ]]; then 29 | prefix="main:" 30 | fi 31 | 32 | gh pr create -t "${prefix} $1" -b "" 33 | -------------------------------------------------------------------------------- /bin/reason-language-server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterpme/dotfiles/bbd557015dcff32bd3d4eb44477c38f2538da4f7/bin/reason-language-server -------------------------------------------------------------------------------- /bin/reload-browser: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # reload-browser - A cross-platform wrapper for reloading the current 3 | # browser tab 4 | # Based on original script by Eric Radman, 2014 5 | # http://entrproject.org/ 6 | 7 | usage() { 8 | case $( uname ) in 9 | Darwin) 10 | # applescript needs the exact title 11 | echo "Usage: $( basename "$0" ) Firefox [Safari \"Google Chrome\" ...]" 12 | ;; 13 | *) 14 | # xdotool uses regular expressions 15 | echo "Usage: $( basename "$0" ) Firefox [Chrome ...]" 16 | ;; 17 | esac 18 | exit 1 19 | } 20 | 21 | [ $# -lt 1 ] && usage 22 | 23 | for app in "$@"; do 24 | echo "$app" 25 | case $( uname ) in 26 | Darwin) 27 | /usr/bin/osascript <<- APPLESCRIPT 28 | set prev to (path to frontmost application as text) 29 | tell application "$app" 30 | activate 31 | end tell 32 | delay 0.5 33 | tell application "System Events" to keystroke "r" using {command down} 34 | delay 0.5 35 | activate application prev 36 | APPLESCRIPT 37 | ;; 38 | *) 39 | xdotool search --onlyvisible --class "$app" windowfocus key \ 40 | --window %@ 'ctrl+r' || { 41 | 1>&2 echo "unable to signal an application named \"$app\"" 42 | } 43 | ;; 44 | esac 45 | done 46 | -------------------------------------------------------------------------------- /bin/renameFile.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Function to rename a file in place 4 | function renameFile() { 5 | if [ -z "$1" ] || [ -z "$2" ]; then 6 | echo "Please provide the current filename and the new filename." 7 | return 1 8 | fi 9 | 10 | current_filename="$1" 11 | new_filename="$2" 12 | 13 | # Find the file using fd and store the path 14 | file_path=$(fd "$current_filename" . | head -n 1) 15 | 16 | if [ -z "$file_path" ]; then 17 | echo "File not found: $current_filename" 18 | return 1 19 | fi 20 | 21 | # Extract the directory path from the file path 22 | directory_path=$(dirname "$file_path") 23 | 24 | # Rename the file in place 25 | mv "$file_path" "$directory_path/$new_filename" 26 | 27 | echo "File renamed successfully:" 28 | echo "Old path: $file_path" 29 | echo "New path: $directory_path/$new_filename" 30 | } 31 | -------------------------------------------------------------------------------- /bin/replace: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # cli-helper.sh 4 | 5 | # Check if enough arguments are supplied 6 | if [ "$#" -ne 2 ]; then 7 | echo "Usage: cli-helper.sh " 8 | exit 1 9 | fi 10 | 11 | # Extract arguments 12 | find_pattern="$1" 13 | replace_with="$2" 14 | 15 | # Run the command 16 | rg --files-with-matches "$find_pattern" | xargs sd "$find_pattern" "$replace_with" 17 | -------------------------------------------------------------------------------- /bin/subl: -------------------------------------------------------------------------------- 1 | /Applications/Sublime Text.app/Contents/SharedSupport/bin/subl 2 | -------------------------------------------------------------------------------- /bin/tm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # abort if we're already inside a TMUX session 4 | [ "$TMUX" == "" ] || exit 0 5 | # startup a "default" session if non currently exists 6 | # tmux has-session -t _default || tmux new-session -s _default -d 7 | 8 | # present menu for user to choose which workspace to open 9 | PS3="Please choose your session: " 10 | # shellcheck disable=SC2207 11 | options=($(tmux list-sessions -F "#S" 2>/dev/null) "New Session" "zsh") 12 | echo "Available sessions" 13 | echo "------------------" 14 | echo " " 15 | select opt in "${options[@]}" 16 | do 17 | case $opt in 18 | "New Session") 19 | read -rp "Enter new session name: " SESSION_NAME 20 | tmux new -s "$SESSION_NAME" 21 | break 22 | ;; 23 | "zsh") 24 | zsh --login 25 | break;; 26 | *) 27 | tmux attach-session -t "$opt" 28 | break 29 | ;; 30 | esac 31 | done 32 | -------------------------------------------------------------------------------- /commands.md: -------------------------------------------------------------------------------- 1 | # Commands / Tips 2 | 3 | - find all the specific files then display it 4 | ``` 5 | bat $(fd tsconfig.json) 6 | ``` 7 | 8 | ``` 9 | # https://unix.stackexchange.com/questions/292253/how-to-use-cat-command-on-find-commands-output 10 | fd tsconfig.json -x bat {} \; 11 | ``` 12 | 13 | - global find & replace 14 | ``` 15 | rg --files-with-matches | xargs sd 16 | ``` 17 | 18 | ``` 19 | rg --files-with-matches "https://auth.xnfts.dev" | xargs sd "https://auth.xnfts.dev" "http://127.0.0.1:8776" 20 | ``` 21 | 22 | - vscode find & replace inside parens 23 | 24 | ``` 25 | find: 26 | "typescript": "(.*)" 27 | 28 | replace: 29 | "typescript": "~4.9.3" 30 | ``` 31 | -------------------------------------------------------------------------------- /config/kitty/Symbols-2048-em Nerd Font Complete.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterpme/dotfiles/bbd557015dcff32bd3d4eb44477c38f2538da4f7/config/kitty/Symbols-2048-em Nerd Font Complete.ttf -------------------------------------------------------------------------------- /config/kitty/kitty-dark.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterpme/dotfiles/bbd557015dcff32bd3d4eb44477c38f2538da4f7/config/kitty/kitty-dark.icns -------------------------------------------------------------------------------- /config/kitty/kitty-light.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterpme/dotfiles/bbd557015dcff32bd3d4eb44477c38f2538da4f7/config/kitty/kitty-light.icns -------------------------------------------------------------------------------- /config/kitty/kitty-theme-custom.conf: -------------------------------------------------------------------------------- 1 | # Tokyo Night color scheme for kitty terminal emulator 2 | # https://github.com/davidmathers/tokyo-night-kitty-theme 3 | # 4 | # Based on Tokyo Night color theme for Visual Studio Code 5 | # https://github.com/enkia/tokyo-night-vscode-theme 6 | 7 | foreground #a9b1d6 8 | background #000000 9 | 10 | # Black 11 | color0 #414868 12 | color8 #414868 13 | 14 | # Red 15 | color1 #f7768e 16 | color9 #f7768e 17 | 18 | # Green 19 | color2 #73daca 20 | color10 #73daca 21 | 22 | # Yellow 23 | color3 #e0af68 24 | color11 #e0af68 25 | 26 | # Blue 27 | color4 #7aa2f7 28 | color12 #7aa2f7 29 | 30 | # Magenta 31 | color5 #bb9af7 32 | color13 #bb9af7 33 | 34 | # Cyan 35 | color6 #7dcfff 36 | color14 #7dcfff 37 | 38 | # White 39 | color7 #c0caf5 40 | color15 #c0caf5 41 | 42 | # Cursor 43 | cursor #c0caf5 44 | cursor_text_color #1a1b26 45 | 46 | # Selection highlight 47 | selection_foreground none 48 | selection_background #28344a 49 | 50 | # The color for highlighting URLs on mouse-over 51 | url_color #9ece6a 52 | 53 | # Window borders 54 | active_border_color #3d59a1 55 | inactive_border_color #101014 56 | bell_border_color #e0af68 57 | 58 | # Tab bar 59 | tab_bar_style fade 60 | tab_fade 1 61 | active_tab_foreground #3d59a1 62 | active_tab_background #16161e 63 | active_tab_font_style bold 64 | inactive_tab_foreground #787c99 65 | inactive_tab_background #16161e 66 | inactive_tab_font_style bold 67 | tab_bar_background #101014 68 | 69 | # Title bar 70 | macos_titlebar_color #16161e 71 | -------------------------------------------------------------------------------- /config/kitty/kitty-theme-tokyo-night.conf: -------------------------------------------------------------------------------- 1 | # Tokyo Night color scheme for kitty terminal emulator 2 | # https://github.com/davidmathers/tokyo-night-kitty-theme 3 | # 4 | # Based on Tokyo Night color theme for Visual Studio Code 5 | # https://github.com/enkia/tokyo-night-vscode-theme 6 | 7 | foreground #a9b1d6 8 | background #011627 9 | # background #1a1b26 10 | 11 | # Black 12 | color0 #414868 13 | color8 #414868 14 | 15 | # Red 16 | color1 #f7768e 17 | color9 #f7768e 18 | 19 | # Green 20 | color2 #73daca 21 | color10 #73daca 22 | 23 | # Yellow 24 | color3 #e0af68 25 | color11 #e0af68 26 | 27 | # Blue 28 | color4 #7aa2f7 29 | color12 #7aa2f7 30 | 31 | # Magenta 32 | color5 #bb9af7 33 | color13 #bb9af7 34 | 35 | # Cyan 36 | color6 #7dcfff 37 | color14 #7dcfff 38 | 39 | # White 40 | color7 #c0caf5 41 | color15 #c0caf5 42 | 43 | # Cursor 44 | cursor #c0caf5 45 | cursor_text_color #1a1b26 46 | 47 | # Selection highlight 48 | selection_foreground none 49 | selection_background #28344a 50 | 51 | # The color for highlighting URLs on mouse-over 52 | url_color #9ece6a 53 | 54 | # Window borders 55 | active_border_color #3d59a1 56 | inactive_border_color #101014 57 | bell_border_color #e0af68 58 | 59 | # Tab bar 60 | tab_bar_style fade 61 | tab_fade 1 62 | active_tab_foreground #3d59a1 63 | active_tab_background #16161e 64 | active_tab_font_style bold 65 | inactive_tab_foreground #787c99 66 | inactive_tab_background #16161e 67 | inactive_tab_font_style bold 68 | tab_bar_background #101014 69 | 70 | # Title bar 71 | macos_titlebar_color #16161e 72 | 73 | # Storm 74 | # background #24283b 75 | # cursor_text_color #24283b 76 | # active_tab_background #1f2335 77 | # inactive_tab_background #1f2335 78 | # macos_titlebar_color #1f2335 79 | -------------------------------------------------------------------------------- /config/kitty/kitty.conf: -------------------------------------------------------------------------------- 1 | # vim:ft=conf 2 | # Kitty Configuration File 3 | # For configuration options, see https://sw.kovidgoyal.net/kitty/conf 4 | 5 | ## Fonts 6 | # font_family Fira Code Regular 7 | # Stylistic sets https://github.com/tonsky/FiraCode/wiki/How-to-enable-stylistic-sets 8 | # font_features FiraCode-Regular +ss01 +ss02 +ss03 +ss04 +ss05 +ss07 +ss08 +zero +onum 9 | font_size 17.0 10 | # adjust_baseline 3 11 | font_family Iosevka Term 12 | font_features Iosevka-Term +ss05 +zero +onum 13 | 14 | disable_ligatures cursor 15 | background_opacity 0.8 16 | 17 | # https://erwin.co/kitty-and-nerd-fonts/#symbols 18 | # TLDR download to ~/.local/share/fonts 19 | # https://github.com/ryanoasis/nerd-fonts/blob/master/src/glyphs/Symbols-2048-em%20Nerd%20Font%20Complete.ttf 20 | # Run sudo fc-cache -fr 21 | include ./symbolmap.conf 22 | 23 | ## Window 24 | window_padding_width 2.0 25 | # macos_titlebar_color background 26 | hide_window_decorations yes 27 | macos_show_window_title_in window 28 | 29 | ## Mouse 30 | # hide the mouse cursor after a period of time (in seconds) 31 | mouse_hide_wait 1.0 32 | # copy text when selecting with the mouse 33 | copy_on_select yes 34 | cursor_shape block 35 | shell_integration no-cursor 36 | 37 | enable_audio_bell no 38 | 39 | # unmaps cmd+enter 40 | map cmd+enter no_op 41 | 42 | # https://github.com/catppuccin/kitty 43 | tab_bar_min_tabs 1 44 | tab_bar_edge bottom 45 | tab_bar_style powerline 46 | tab_powerline_style slanted 47 | tab_title_template {title}{' :{}:'.format(num_windows) if num_windows > 1 else ''} 48 | 49 | include ./kitty-theme-custom.conf 50 | # include catppuccin-kitty/mocha.conf 51 | 52 | # kitty icon 53 | # https://github.com/DinkDonk/kitty-icon 54 | 55 | # https://manpages.ubuntu.com/manpages/lunar/man5/kitty.conf.5.html#:~:text=You%20can%20reload%20the%20config,⌘%2B%2C%20on%20macOS). 56 | # syncing to monitor limits the rendering speed. 57 | sync_to_monitor no 58 | -------------------------------------------------------------------------------- /config/kitty/symbolmap.conf: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # Symbols Nerd Font complete symbol_map 3 | # easily troubleshoot missing/incorrect characters with: 4 | # kitty --debug-font-fallback 5 | ########################################################### 6 | 7 | # # "Nerd Fonts - Powerline" 8 | # symbol_map U+e0a0-U+e0a2,U+e0b0-U+e0b3 Symbols Nerd Font 9 | # 10 | # # "Nerd Fonts - Powerline Extra" 11 | # symbol_map U+e0a3-U+e0a3,U+e0b4-U+e0c8,U+e0cc-U+e0d2,U+e0d4-U+e0d4 Symbols Nerd Font 12 | # 13 | # # "Nerd Fonts - Symbols original" 14 | # symbol_map U+e5fa-U+e62b Symbols Nerd Font 15 | # 16 | # # "Nerd Fonts - Devicons" 17 | # symbol_map U+e700-U+e7c5 Symbols Nerd Font 18 | # 19 | # # "Nerd Fonts - Octicons" 20 | # symbol_map U+f400-U+f4a8,U+2665-U+2665,U+26A1-U+26A1,U+f27c-U+f27c Symbols Nerd Font 21 | # 22 | # # "Nerd Fonts - Font Linux" 23 | # symbol_map U+F300-U+F313 Symbols Nerd Font 24 | # 25 | # # Nerd Fonts - Font Power Symbols" 26 | # symbol_map U+23fb-U+23fe,U+2b58-U+2b58 Symbols Nerd Font 27 | # 28 | # # "Nerd Fonts - Material Design Icons" 29 | # symbol_map U+f500-U+fd46 Symbols Nerd Font 30 | # # 31 | # # "Nerd Fonts - Weather Icons" 32 | # symbol_map U+e300-U+e3eb Symbols Nerd Font 33 | # 34 | # # Misc Code Point Fixes 35 | # symbol_map U+21B5,U+25B8,U+2605,U+2630,U+2632,U+2714,U+E0A3,U+E615,U+E62B Symbols Nerd Font 36 | 37 | # /////////////////////////////////////////////////////////////////////////////// 38 | 39 | # Seti-UI + Custom 40 | symbol_map U+E5FA-U+E6AC Symbols Nerd Font 41 | 42 | # Devicons 43 | symbol_map U+E700-U+E7C5 Symbols Nerd Font 44 | 45 | # Font Awesome 46 | symbol_map U+F000-U+F2E0 Symbols Nerd Font 47 | 48 | # Font Awesome Extension 49 | symbol_map U+E200-U+E2A9 Symbols Nerd Font 50 | 51 | # Material Design Icons 52 | # symbol_map U+F0001-U+F1AF0 Symbols Nerd Font 53 | 54 | # Weather 55 | # symbol_map U+E300-U+E3E3 Symbols Nerd Font 56 | 57 | # Octicons 58 | symbol_map U+F400-U+F532,U+2665,U+26A1 Symbols Nerd Font 59 | 60 | # Powerline Symbols 61 | symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 Symbols Nerd Font 62 | 63 | # Powerline Extra Symbols 64 | symbol_map U+E0A3,U+E0B4-U+E0C8,U+E0CA,U+E0CC-U+E0D4 Symbols Nerd Font 65 | symbol_map U+e0A3-U+e0a3,U+e0b4-U+e0c8,U+e0cc-U+e0d2,U+e0d4-U+e0d4 Symbols Nerd Font 66 | 67 | # IEC Power Symbols 68 | symbol_map U+23FB-U+23FE,U+2B58 Symbols Nerd Font 69 | 70 | # Font Logos 71 | symbol_map U+F300-U+F32F Symbols Nerd Font 72 | 73 | # Pomicons 74 | symbol_map U+E000-U+E00D Symbols Nerd Font 75 | 76 | # Codicons 77 | symbol_map U+EA60-U+EBEB Symbols Nerd Font 78 | 79 | # Additional sets 80 | symbol_map U+E276C-U+E2771 Symbols Nerd Font # Heavy Angle Brackets 81 | symbol_map U+2500-U+259F Symbols Nerd Font # Box Drawing 82 | 83 | # Some symbols not covered by Symbols Nerd Font 84 | # nonicons contains icons in the range: U+F101-U+F27D 85 | # U+F167 is HTML logo, but YouTube logo in Symbols Nerd Font 86 | symbol_map U+F102,U+F116-U+F118,U+F12F,U+F13E,U+F1AF,U+F1BF,U+F1CF,U+F1FF,U+F20F,U+F21F-U+F220,U+F22E-U+F22F,U+F23F,U+F24F,U+F25F nonicons 87 | -------------------------------------------------------------------------------- /config/kitty/test-fonts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Nerd Fonts Version: 3.0.2 3 | # Script Version: 1.1.1 4 | 5 | # Run this script in your local bash: 6 | # curl https://raw.githubusercontent.com/ryanoasis/nerd-fonts/master/bin/scripts/test-fonts.sh | bash 7 | # Is possible to change the number of columns passing a number as the first parameter (default=16): 8 | # ./test-fonts.sh 8 9 | 10 | # Given an array of decimal numbers print all unicode codepoint. 11 | function print-decimal-unicode-range() { 12 | local originalSequence=("$@") 13 | local counter=0 14 | # Use alternating colors to see which symbols extend out of the bounding 15 | # box. 16 | local bgColorBorder='\033[48;5;8m' 17 | local bgColorCode='\033[48;5;246m' 18 | local alternateBgColorCode='\033[48;5;240m' 19 | local bgColorChar='\033[48;5;66m' 20 | local alternateBgColorChar='\033[48;5;60m' 21 | local underline='\033[4m' 22 | local currentColorCode="${bgColorCode}" 23 | local currentColorChar="${bgColorChar}" 24 | local reset_color='\033[0m' 25 | local allChars="" 26 | local allCodes="" 27 | local wrapAt=16 28 | [[ "$wrappingValue" =~ ^[0-9]+$ ]] && [ "$wrappingValue" -gt 2 ] && wrapAt="$wrappingValue" 29 | local topLineStart="${bgColorBorder}╔═══" 30 | local topLineMiddle="═══╦═══" 31 | local topLineEnd="═══╗${reset_color}" 32 | local bottomLineStart="${bgColorBorder}╚═══" 33 | local bottomLineMiddle="═══╩═══" 34 | local bottomLineEnd="═══╝${reset_color}" 35 | local lineStart="${bgColorBorder}╠═══" 36 | local lineMiddle="═══╬═══" 37 | local lineEnd="═══╣${reset_color}" 38 | local bar="${bgColorBorder}║${reset_color}" 39 | local originalSequenceLength=${#originalSequence[@]} 40 | local leftoverSpaces=$((wrapAt - (originalSequenceLength % wrapAt))) 41 | 42 | # add fillers to array to maintain table: 43 | if [ "$leftoverSpaces" -lt "$wrapAt" ]; then 44 | for ((c = 1; c <= leftoverSpaces; c++)); do 45 | originalSequence+=(0) 46 | done 47 | fi 48 | 49 | local sequenceLength=${#originalSequence[@]} 50 | 51 | printf "%b" "$topLineStart" 52 | for ((c = 2; c <= wrapAt; c++)); do 53 | printf "%b" "$topLineMiddle" 54 | done 55 | printf "%b\\n" "$topLineEnd" 56 | 57 | for decimalCode in "${originalSequence[@]}"; do 58 | local hexCode 59 | hexCode=$(printf '%x' "${decimalCode}") 60 | local code="${hexCode}" 61 | local char="\\U${hexCode}" 62 | 63 | # fill in placeholder cells properly formatted: 64 | if [ "${char}" = "\\U0" ]; then 65 | char=" " 66 | code="" 67 | fi 68 | 69 | filler="" 70 | for ((c = ${#code}; c < 5; c++)); do 71 | filler=" ${filler}" 72 | done 73 | 74 | allCodes+="${currentColorCode}${filler}${underline}${code}${reset_color}${currentColorCode} ${reset_color}$bar" 75 | allChars+="${currentColorChar} ${char} ${reset_color}$bar" 76 | counter=$((counter + 1)) 77 | count=$(( (count + 1) % wrapAt)) 78 | 79 | if [[ $count -eq 0 ]]; then 80 | 81 | if [[ "${currentColorCode}" = "${alternateBgColorCode}" ]]; then 82 | currentColorCode="${bgColorCode}" 83 | currentColorChar="${bgColorChar}" 84 | else 85 | currentColorCode="${alternateBgColorCode}" 86 | currentColorChar="${alternateBgColorChar}" 87 | fi 88 | 89 | printf "%b%b%b" "$bar" "$allCodes" "$reset_color" 90 | printf "\\n" 91 | printf "%b%b%b" "$bar" "$allChars" "$reset_color" 92 | printf "\\n" 93 | 94 | if [ "$counter" != "$sequenceLength" ]; then 95 | printf "%b" "$lineStart" 96 | for ((c = 2; c <= wrapAt; c++)); do 97 | printf "%b" "$lineMiddle" 98 | done 99 | printf "%b\\n" "$lineEnd" 100 | fi 101 | 102 | allCodes="" 103 | allChars="" 104 | fi 105 | 106 | done 107 | 108 | printf "%b" "$bottomLineStart" 109 | for ((c = 2; c <= wrapAt; c++)); do 110 | printf "%b" "$bottomLineMiddle" 111 | done 112 | printf "%b\\n" "$bottomLineEnd" 113 | 114 | 115 | } 116 | 117 | function print-unicode-ranges() { 118 | echo '' 119 | 120 | local arr=("$@") 121 | local len=$# 122 | local combinedRanges=() 123 | 124 | for ((j=0; j 33 | trustctime = false 34 | 35 | # Prevent showing files whose names contain non-ASCII symbols as unversioned. 36 | # http://michael-kuehnel.de/git/2014/11/21/git-mac-osx-and-german-umlaute.html 37 | precomposeUnicode = true 38 | 39 | # Speed up commands involving untracked files such as `git status`. 40 | # https://git-scm.com/docs/git-update-index#_untracked_cache 41 | untrackedCache = true 42 | 43 | [init] 44 | defaultBranch = main 45 | 46 | [apply] 47 | # Detect whitespace errors when applying a patch 48 | whitespace = fix 49 | 50 | 51 | [interactive] 52 | diffFilter = delta --color-only 53 | 54 | [delta] 55 | navigate = true # use n and N to move between diff sections 56 | light = false # set to true if you're in a terminal w/ a light background color (e.g. the default macOS terminal) 57 | 58 | [merge] 59 | # tool = vimdiff 60 | conflictstyle = diff3 61 | 62 | [pager] 63 | # https://github.com/dandavison/delta 64 | diff = delta | less --tabs=4 -RFX 65 | show = delta | less --tabs=4 -RFX 66 | 67 | [color] 68 | ui = auto 69 | 70 | [color "diff"] 71 | meta = yellow bold 72 | commit = green bold 73 | frag = magenta bold 74 | old = red bold 75 | new = green bold 76 | whitespace = red reverse 77 | newMoved = cyan 78 | oldMoved = blue 79 | 80 | [color "branch"] 81 | current = yellow reverse 82 | local = yellow 83 | remote = green 84 | 85 | [color "status"] 86 | added = yellow 87 | changed = green 88 | untracked = cyan 89 | 90 | 91 | [mergetool] 92 | prompt = true 93 | 94 | [mergetool "vimdiff"] 95 | cmd = nvim -d $LOCAL $REMOTE $MERGED -c '$wincmd w' -c 'wincmd J' 96 | 97 | [difftool] 98 | prompt = false 99 | 100 | [diff] 101 | # tool = vimdiff 102 | # Show blocks of moved text of at least 20 alphanumeric characters differently than adds/deletes 103 | # https://git-scm.com/docs/git-diff#git-diff-zebra 104 | colorMoved = default 105 | 106 | [url "git@github.com:"] 107 | insteadOf = "gh:" 108 | pushInsteadOf = "github:" 109 | pushInsteadOf = "git://github.com/" 110 | 111 | [url "git://github.com/"] 112 | insteadOf = "github:" 113 | 114 | [url "git@gist.github.com:"] 115 | insteadOf = "gst:" 116 | pushInsteadOf = "gist:" 117 | pushInsteadOf = "git://gist.github.com/" 118 | 119 | [url "git://gist.github.com/"] 120 | insteadOf = "gist:" 121 | 122 | [help] 123 | # If a command is mistyped, make Git automatically 124 | # run the command it thinks the user wanted to type. 125 | autocorrect = immediate 126 | 127 | [pull] 128 | rebase = false 129 | 130 | # push easily. http://stackoverflow.com/a/23918418/89484 131 | [push] 132 | default = current 133 | # Make `git push` automatically push relevant 134 | # annotated tags when pushing branches out. 135 | followTags = true 136 | 137 | [filter "lfs"] 138 | clean = git-lfs clean %f 139 | smudge = git-lfs smudge %f 140 | required = true 141 | 142 | [fetch] 143 | prune = true 144 | 145 | [stash] 146 | showPatch = true 147 | 148 | [log] 149 | date = relative 150 | 151 | [filter "gitignore"] 152 | clean = "sed '/#gitignore$/d'" 153 | smudge = cat 154 | 155 | [include] 156 | # Load local configs. 157 | # https://git-scm.com/docs/git-config#_includes 158 | # 159 | # [!] The following needs to remain at the end of this file in 160 | # order to allow any of the above configs to be overwritten 161 | # by the local ones 162 | 163 | path = ~/.gitconfig.local 164 | [gpg] 165 | format = ssh 166 | [commit] 167 | gpgSign = true 168 | -------------------------------------------------------------------------------- /hammerspoon/Spoons/AppLauncher.spoon/docs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Command": [], 4 | "Constant": [], 5 | "Constructor": [], 6 | "Deprecated": [], 7 | "Field": [], 8 | "Function": [], 9 | "Method": [ 10 | { 11 | "def": "AppLauncher:bindHotkeys(mapping)", 12 | "desc": "Binds hotkeys for AppLauncher", 13 | "doc": "Binds hotkeys for AppLauncher\n\nParameters:\n * mapping - A table containing single characters with their associated app", 14 | "name": "bindHotkeys", 15 | "parameters": [ 16 | " * mapping - A table containing single characters with their associated app" 17 | ], 18 | "signature": "AppLauncher:bindHotkeys(mapping)", 19 | "stripped_doc": "", 20 | "type": "Method" 21 | } 22 | ], 23 | "Variable": [ 24 | { 25 | "def": "AppLauncher.modifiers", 26 | "desc": "Modifier keys used when launching apps", 27 | "doc": "Modifier keys used when launching apps\n\nDefault value: `{\"ctrl\", \"alt\"}`", 28 | "name": "modifiers", 29 | "signature": "AppLauncher.modifiers", 30 | "stripped_doc": "Default value: `{\"ctrl\", \"alt\"}`", 31 | "type": "Variable" 32 | } 33 | ], 34 | "desc": "Simple spoon for launching apps with single letter hotkeys.", 35 | "doc": "Simple spoon for launching apps with single letter hotkeys.\n\nExample configuration using SpoonInstall.spoon:\n```\nspoon.SpoonInstall:andUse(\"AppLauncher\", {\n hotkeys = {\n c = \"Calendar\",\n d = \"Discord\",\n f = \"Firefox Developer Edition\",\n n = \"Notes\",\n p = \"1Password 7\",\n r = \"Reeder\",\n t = \"Kitty\",\n z = \"Zoom.us\",\n }\n})\n```\n\nDownload: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/AppLauncher.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/AppLauncher.spoon.zip)", 36 | "items": [ 37 | { 38 | "def": "AppLauncher:bindHotkeys(mapping)", 39 | "desc": "Binds hotkeys for AppLauncher", 40 | "doc": "Binds hotkeys for AppLauncher\n\nParameters:\n * mapping - A table containing single characters with their associated app", 41 | "name": "bindHotkeys", 42 | "parameters": [ 43 | " * mapping - A table containing single characters with their associated app" 44 | ], 45 | "signature": "AppLauncher:bindHotkeys(mapping)", 46 | "stripped_doc": "", 47 | "type": "Method" 48 | }, 49 | { 50 | "def": "AppLauncher.modifiers", 51 | "desc": "Modifier keys used when launching apps", 52 | "doc": "Modifier keys used when launching apps\n\nDefault value: `{\"ctrl\", \"alt\"}`", 53 | "name": "modifiers", 54 | "signature": "AppLauncher.modifiers", 55 | "stripped_doc": "Default value: `{\"ctrl\", \"alt\"}`", 56 | "type": "Variable" 57 | } 58 | ], 59 | "name": "AppLauncher", 60 | "stripped_doc": "\nExample configuration using SpoonInstall.spoon:\n```\nspoon.SpoonInstall:andUse(\"AppLauncher\", {\n hotkeys = {\n c = \"Calendar\",\n d = \"Discord\",\n f = \"Firefox Developer Edition\",\n n = \"Notes\",\n p = \"1Password 7\",\n r = \"Reeder\",\n t = \"Kitty\",\n z = \"Zoom.us\",\n }\n})\n```\n\nDownload: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/AppLauncher.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/AppLauncher.spoon.zip)", 61 | "submodules": [], 62 | "type": "Module" 63 | } 64 | ] -------------------------------------------------------------------------------- /hammerspoon/Spoons/AppLauncher.spoon/init.lua: -------------------------------------------------------------------------------- 1 | --- === AppLauncher === 2 | --- 3 | --- Simple spoon for launching apps with single letter hotkeys. 4 | --- 5 | --- Example configuration using SpoonInstall.spoon: 6 | --- ``` 7 | --- spoon.SpoonInstall:andUse("AppLauncher", { 8 | --- hotkeys = { 9 | --- c = "Calendar", 10 | --- d = "Discord", 11 | --- f = "Firefox Developer Edition", 12 | --- n = "Notes", 13 | --- p = "1Password 7", 14 | --- r = "Reeder", 15 | --- t = "Kitty", 16 | --- z = "Zoom.us", 17 | --- } 18 | --- }) 19 | --- ``` 20 | --- 21 | --- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/AppLauncher.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/AppLauncher.spoon.zip) 22 | 23 | local obj = {} 24 | obj.__index = obj 25 | 26 | -- Metadata 27 | obj.name = "AppLauncher" 28 | obj.version = "1.0.0" 29 | obj.author = "Mathias Jean Johansen " 30 | obj.homepage = "https://github.com/Hammerspoon/Spoons" 31 | obj.license = "ISC - https://opensource.org/licenses/ISC" 32 | 33 | --- AppLauncher.modifiers 34 | --- Variable 35 | --- Modifier keys used when launching apps 36 | --- 37 | --- Default value: `{"ctrl", "alt"}` 38 | obj.modifiers = {"ctrl", "alt"} 39 | 40 | --- AppLauncher:bindHotkeys(mapping) 41 | --- Method 42 | --- Binds hotkeys for AppLauncher 43 | --- 44 | --- Parameters: 45 | --- * mapping - A table containing single characters with their associated app 46 | function obj:bindHotkeys(mapping) 47 | for key, app in pairs(mapping) do 48 | hs.hotkey.bind(obj.modifiers, key, function() 49 | hs.application.launchOrFocus(app) 50 | end) 51 | end 52 | end 53 | 54 | return obj 55 | -------------------------------------------------------------------------------- /hammerspoon/Spoons/Cherry.spoon/docs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Command": [], 4 | "Constant": [], 5 | "Constructor": [], 6 | "Deprecated": [], 7 | "Field": [], 8 | "Function": [], 9 | "Method": [ 10 | { 11 | "def": "Cherry:bindHotkeys(mapping)", 12 | "desc": "Binds hotkeys for Cherry", 13 | "doc": "Binds hotkeys for Cherry\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * start - start the pomodoro timer (Default: cmd-ctrl-alt-C)", 14 | "name": "bindHotkeys", 15 | "parameters": [ 16 | " * mapping - A table containing hotkey details for the following items:", 17 | " * start - start the pomodoro timer (Default: cmd-ctrl-alt-C)" 18 | ], 19 | "signature": "Cherry:bindHotkeys(mapping)", 20 | "stripped_doc": "", 21 | "type": "Method" 22 | }, 23 | { 24 | "def": "Cherry:popup()", 25 | "desc": "Popup an alert or notification when time is up.", 26 | "doc": "Popup an alert or notification when time is up.\n\nParameters:\n * None\n\nReturns:\n * None", 27 | "name": "popup", 28 | "parameters": [ 29 | " * None" 30 | ], 31 | "returns": [ 32 | " * None" 33 | ], 34 | "signature": "Cherry:popup()", 35 | "stripped_doc": "", 36 | "type": "Method" 37 | }, 38 | { 39 | "def": "Cherry:start()", 40 | "desc": "Starts the timer and displays the countdown in a menubar item", 41 | "doc": "Starts the timer and displays the countdown in a menubar item\n\nParameters:\n * resume - boolean when true resumes countdown at current value of self.timeLeft\n\nReturns:\n * None", 42 | "name": "start", 43 | "parameters": [ 44 | " * resume - boolean when true resumes countdown at current value of self.timeLeft" 45 | ], 46 | "returns": [ 47 | " * None" 48 | ], 49 | "signature": "Cherry:start()", 50 | "stripped_doc": "", 51 | "type": "Method" 52 | } 53 | ], 54 | "Variable": [], 55 | "desc": "Cherry tomato (a tiny Pomodoro) -- a Pomodoro Timer for the menubar", 56 | "doc": "Cherry tomato (a tiny Pomodoro) -- a Pomodoro Timer for the menubar\n\nDownload: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/Cherry.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/Cherry.spoon.zip)\n", 57 | "items": [ 58 | { 59 | "def": "Cherry:bindHotkeys(mapping)", 60 | "desc": "Binds hotkeys for Cherry", 61 | "doc": "Binds hotkeys for Cherry\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * start - start the pomodoro timer (Default: cmd-ctrl-alt-C)", 62 | "name": "bindHotkeys", 63 | "parameters": [ 64 | " * mapping - A table containing hotkey details for the following items:", 65 | " * start - start the pomodoro timer (Default: cmd-ctrl-alt-C)" 66 | ], 67 | "signature": "Cherry:bindHotkeys(mapping)", 68 | "stripped_doc": "", 69 | "type": "Method" 70 | }, 71 | { 72 | "def": "Cherry:popup()", 73 | "desc": "Popup an alert or notification when time is up.", 74 | "doc": "Popup an alert or notification when time is up.\n\nParameters:\n * None\n\nReturns:\n * None", 75 | "name": "popup", 76 | "parameters": [ 77 | " * None" 78 | ], 79 | "returns": [ 80 | " * None" 81 | ], 82 | "signature": "Cherry:popup()", 83 | "stripped_doc": "", 84 | "type": "Method" 85 | }, 86 | { 87 | "def": "Cherry:start()", 88 | "desc": "Starts the timer and displays the countdown in a menubar item", 89 | "doc": "Starts the timer and displays the countdown in a menubar item\n\nParameters:\n * resume - boolean when true resumes countdown at current value of self.timeLeft\n\nReturns:\n * None", 90 | "name": "start", 91 | "parameters": [ 92 | " * resume - boolean when true resumes countdown at current value of self.timeLeft" 93 | ], 94 | "returns": [ 95 | " * None" 96 | ], 97 | "signature": "Cherry:start()", 98 | "stripped_doc": "", 99 | "type": "Method" 100 | } 101 | ], 102 | "name": "Cherry", 103 | "stripped_doc": "\nDownload: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/Cherry.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/Cherry.spoon.zip)\n", 104 | "submodules": [], 105 | "type": "Module" 106 | } 107 | ] -------------------------------------------------------------------------------- /hammerspoon/Spoons/Cherry.spoon/init.lua: -------------------------------------------------------------------------------- 1 | --- === Cherry === 2 | --- 3 | --- Cherry tomato (a tiny Pomodoro) -- a Pomodoro Timer for the menubar 4 | --- 5 | --- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/Cherry.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/Cherry.spoon.zip) 6 | --- 7 | 8 | local obj = {} 9 | obj.__index = obj 10 | 11 | -- Metadata 12 | obj.name = "Cherry" 13 | obj.version = "0.1" 14 | obj.author = "Daniel Marques and Omar El-Domeiri " 15 | obj.license = "MIT" 16 | obj.homepage = "https://github.com/Hammerspoon/Spoons" 17 | 18 | -- Settings 19 | 20 | -- timer duration in minutes 21 | obj.duration = 25 22 | 23 | -- set this to true to always show the menubar item 24 | obj.alwaysShow = true 25 | 26 | -- duration in seconds for alert to stay on screen 27 | -- set to 0 to turn off alert completely 28 | obj.alertDuration = 5 29 | 30 | -- Font size for alert 31 | obj.alertTextSize = 80 32 | 33 | -- set to nil to turn off notification when time's up or provide a hs.notify notification 34 | obj.notification = nil 35 | -- obj.notification = hs.notify.new({ title = "Done! 🍒", withdrawAfter = 0}) 36 | 37 | -- set to nil to turn off notification sound when time's up or provide a hs.sound 38 | obj.sound = nil 39 | -- obj.sound = hs.sound.getByFile("System/Library/PrivateFrameworks/ScreenReader.framework/Versions/A/Resources/Sounds") 40 | 41 | obj.defaultMapping = { 42 | start = {{"cmd", "ctrl", "alt"}, "C"} 43 | } 44 | 45 | 46 | --- Cherry:bindHotkeys(mapping) 47 | --- Method 48 | --- Binds hotkeys for Cherry 49 | --- 50 | --- Parameters: 51 | --- * mapping - A table containing hotkey details for the following items: 52 | --- * start - start the pomodoro timer (Default: cmd-ctrl-alt-C) 53 | function obj:bindHotkeys(mapping) 54 | if (self.hotkey) then 55 | self.hotkey.delete() 56 | end 57 | 58 | if mapping and mapping["start"] then 59 | hs.hotkey.bind(mapping["start"][1], mapping["start"][2], function() self:start() end) 60 | else 61 | hs.hotkey.bind(self.defaultMapping["start"][1], self.defaultMapping["start"][2], function() self:start() end) 62 | end 63 | end 64 | 65 | 66 | function obj:init() 67 | self.menu = hs.menubar.new(self.alwaysShow) 68 | self:reset() 69 | end 70 | 71 | 72 | function obj:reset() 73 | local items = { 74 | { title = "Start", fn = function() self:start() end } 75 | } 76 | self.menu:setMenu(items) 77 | self.menu:setTitle("🍒") 78 | self.timerRunning = false 79 | if not self.alwaysShow then 80 | self.menu:removeFromMenuBar() 81 | end 82 | end 83 | 84 | 85 | function obj:updateTimerString() 86 | local minutes = math.floor(self.timeLeft / 60) 87 | local seconds = self.timeLeft - (minutes * 60) 88 | local timerString = string.format("%02d:%02d 🍒", minutes, seconds) 89 | self.menu:setTitle(timerString) 90 | end 91 | 92 | 93 | --- Cherry:popup() 94 | --- Method 95 | --- Popup an alert or notification when time is up. 96 | --- 97 | --- Parameters: 98 | --- * None 99 | --- 100 | --- Returns: 101 | --- * None 102 | function obj:popup() 103 | if 0 < self.alertDuration then 104 | hs.alert.show("Done! 🍒", { textSize = self.alertTextSize }, self.alertDuration) 105 | end 106 | if self.notification then 107 | self.notification:send() 108 | end 109 | if self.sound then 110 | self.sound:play() 111 | end 112 | end 113 | 114 | 115 | function obj:tick() 116 | self.timeLeft = self.timeLeft - 1 117 | self:updateTimerString() 118 | if self.timeLeft <= 0 then 119 | self:reset() 120 | self:popup() 121 | end 122 | end 123 | 124 | 125 | --- Cherry:start() 126 | --- Method 127 | --- Starts the timer and displays the countdown in a menubar item 128 | --- 129 | --- Parameters: 130 | --- * resume - boolean when true resumes countdown at current value of self.timeLeft 131 | --- 132 | --- Returns: 133 | --- * None 134 | function obj:start(resume) 135 | if not self.menu:isInMenuBar() then 136 | self.menu:returnToMenuBar() 137 | end 138 | if not resume then 139 | self.timeLeft = self.duration * 60 140 | self:updateTimerString() 141 | end 142 | self.timerRunning = true 143 | self.timer = hs.timer.doWhile(function() return self.timerRunning end, function() self:tick() end, 1) 144 | local items = { 145 | { title = "Stop", fn = function() self:reset() end }, 146 | { title = "Pause", fn = function() self:pause() end } 147 | } 148 | self.menu:setMenu(items) 149 | end 150 | 151 | 152 | function obj:pause() 153 | self.timerRunning = false 154 | local items = { 155 | { title = "Stop", fn = function() self:reset() end }, 156 | { title = "Resume", fn = function() self:start(true) end } 157 | } 158 | self.menu:setMenu(items) 159 | end 160 | 161 | 162 | return obj 163 | -------------------------------------------------------------------------------- /hammerspoon/Spoons/ShiftIt.spoon/init.lua: -------------------------------------------------------------------------------- 1 | --- === HammerspoonShiftIt === 2 | --- 3 | --- Manages windows and positions in MacOS with key binding from ShiftIt. 4 | --- 5 | --- Download: https://github.com/peterklijn/hammerspoon-shiftit/raw/master/Spoons/ShiftIt.spoon.zip 6 | 7 | local obj = { 8 | hs = hs 9 | } 10 | obj.__index = obj 11 | 12 | -- Metadata 13 | obj.name = "HammerspoonShiftIt" 14 | obj.version = "1.1" 15 | obj.author = "Peter Klijn" 16 | obj.homepage = "https://github.com/peterklijn/hammerspoon-shiftit" 17 | obj.license = "https://github.com/peterklijn/hammerspoon-shiftit/blob/master/LICENSE.md" 18 | 19 | obj.mash = { 'ctrl', 'alt', 'cmd' } 20 | obj.mapping = { 21 | left = { obj.mash, 'left' }, 22 | right = { obj.mash, 'right' }, 23 | up = { obj.mash, 'up' }, 24 | down = { obj.mash, 'down' }, 25 | upleft = { obj.mash, '1' }, 26 | upright = { obj.mash, '2' }, 27 | botleft = { obj.mash, '3' }, 28 | botright = { obj.mash, '4' }, 29 | maximum = { obj.mash, 'm' }, 30 | toggleFullScreen = { obj.mash, 'f' }, 31 | toggleZoom = { obj.mash, 'z' }, 32 | center = { obj.mash, 'c' }, 33 | nextScreen = { obj.mash, 'n' }, 34 | previousScreen = { obj.mash, 'p' }, 35 | resizeOut = { obj.mash, '=' }, 36 | resizeIn = { obj.mash, '-' }, 37 | } 38 | 39 | local units = { 40 | left = function(x, _) return { x = 0.00, y = 0.00, w = x / 100, h = 1.00 } end, 41 | right = function(x, _) return { x = 1 - (x / 100), y = 0.00, w = x / 100, h = 1.00 } end, 42 | top = function(_, y) return { x = 0.00, y = 0.00, w = 1.00, h = y / 100 } end, 43 | bot = function(_, y) return { x = 0.00, y = 1 - (y / 100), w = 1.00, h = y / 100 } end, 44 | 45 | upleft = function(x, y) return { x = 0.00, y = 0.00, w = x / 100, h = y / 100 } end, 46 | upright = function(x, y) return { x = 1 - (x / 100), y = 0.00, w = x / 100, h = y / 100 } end, 47 | botleft = function(x, y) return { x = 0.00, y = 1 - (y / 100), w = x / 100, h = y / 100 } end, 48 | botright = function(x, y) return { x = 1 - (x / 100), y = 1 - (y / 100), w = x / 100, h = y / 100 } end, 49 | 50 | maximum = { x = 0.00, y = 0.00, w = 1.00, h = 1.00 }, 51 | } 52 | 53 | local latestMove = { 54 | windowId = -1, 55 | direction = 'unknown', 56 | stepX = -1, 57 | stepY = -1, 58 | } 59 | 60 | function obj:move(unit) self.hs.window.focusedWindow():move(unit, nil, true, 0) end 61 | 62 | function obj:moveWithCycles(unitFn) 63 | local windowId = self.hs.window.focusedWindow():id() 64 | local sameMoveAction = latestMove.windowId == windowId and latestMove.direction == unitFn 65 | if sameMoveAction then 66 | latestMove.stepX = obj.nextCycleSizeX[latestMove.stepX] 67 | latestMove.stepY = obj.nextCycleSizeY[latestMove.stepY] 68 | else 69 | latestMove.stepX = obj.cycleSizesX[1] 70 | latestMove.stepY = obj.cycleSizesY[1] 71 | end 72 | latestMove.windowId = windowId 73 | latestMove.direction = unitFn 74 | 75 | local before = self.hs.window.focusedWindow():frame() 76 | self:move(unitFn(latestMove.stepX, latestMove.stepY)) 77 | 78 | if not sameMoveAction then 79 | -- if the window is not moved or resized, it was already at the required location, 80 | -- in that case we'll call this method again, so it will go to the next cycle. 81 | local after = self.hs.window.focusedWindow():frame() 82 | if before.x == after.x and before.y == after.y 83 | and before.w == after.w and before.h == after.h then 84 | self:moveWithCycles(unitFn) 85 | end 86 | end 87 | end 88 | 89 | function obj:resizeWindowInSteps(increment) 90 | local screen = self.hs.window.focusedWindow():screen():frame() 91 | local window = self.hs.window.focusedWindow():frame() 92 | local wStep = math.floor(screen.w / 12) 93 | local hStep = math.floor(screen.h / 12) 94 | local x, y, w, h = window.x, window.y, window.w, window.h 95 | 96 | if increment then 97 | local xu = math.max(screen.x, x - wStep) 98 | w = w + (x - xu) 99 | x = xu 100 | local yu = math.max(screen.y, y - hStep) 101 | h = h + (y - yu) 102 | y = yu 103 | w = math.min(screen.w - x + screen.x, w + wStep) 104 | h = math.min(screen.h - y + screen.y, h + hStep) 105 | else 106 | local noChange = true 107 | local notMinWidth = w > wStep * 3 108 | local notMinHeight = h > hStep * 3 109 | 110 | local snapLeft = x <= screen.x 111 | local snapTop = y <= screen.y 112 | -- add one pixel in case of odd number of pixels 113 | local snapRight = (x + w + 1) >= (screen.x + screen.w) 114 | local snapBottom = (y + h + 1) >= (screen.y + screen.h) 115 | 116 | local b2n = { [true] = 1, [false] = 0 } 117 | local totalSnaps = b2n[snapLeft] + b2n[snapRight] + b2n[snapTop] + b2n[snapBottom] 118 | 119 | if notMinWidth and (totalSnaps <= 1 or not snapLeft) then 120 | x = x + wStep 121 | w = w - wStep 122 | noChange = false 123 | end 124 | if notMinHeight and (totalSnaps <= 1 or not snapTop) then 125 | y = y + hStep 126 | h = h - hStep 127 | noChange = false 128 | end 129 | if notMinWidth and (totalSnaps <= 1 or not snapRight) then 130 | w = w - wStep 131 | noChange = false 132 | end 133 | if notMinHeight and (totalSnaps <= 1 or not snapBottom) then 134 | h = h - hStep 135 | noChange = false 136 | end 137 | if noChange then 138 | x = notMinWidth and x + wStep or x 139 | y = notMinHeight and y + hStep or y 140 | w = notMinWidth and w - wStep * 2 or w 141 | h = notMinHeight and h - hStep * 2 or h 142 | end 143 | end 144 | self:move({ x = x, y = y, w = w, h = h }) 145 | end 146 | 147 | function obj:left() self:moveWithCycles(units.left) end 148 | 149 | function obj:right() self:moveWithCycles(units.right) end 150 | 151 | function obj:up() self:moveWithCycles(units.top) end 152 | 153 | function obj:down() self:moveWithCycles(units.bot) end 154 | 155 | function obj:upleft() self:moveWithCycles(units.upleft) end 156 | 157 | function obj:upright() self:moveWithCycles(units.upright) end 158 | 159 | function obj:botleft() self:moveWithCycles(units.botleft) end 160 | 161 | function obj:botright() self:moveWithCycles(units.botright) end 162 | 163 | function obj:maximum() 164 | latestMove.direction = 'maximum' 165 | self:move(units.maximum) 166 | end 167 | 168 | function obj:toggleFullScreen() self.hs.window.focusedWindow():toggleFullScreen() end 169 | 170 | function obj:toggleZoom() self.hs.window.focusedWindow():toggleZoom() end 171 | 172 | function obj:center() 173 | latestMove.direction = 'center' 174 | self.hs.window.focusedWindow():centerOnScreen(nil, true, 0) 175 | end 176 | 177 | function obj:nextScreen() 178 | self.hs.window.focusedWindow():moveToScreen(self.hs.window.focusedWindow():screen():next(), false, true, 0) 179 | end 180 | 181 | function obj:prevScreen() 182 | self.hs.window.focusedWindow():moveToScreen(self.hs.window.focusedWindow():screen():previous(), false, true, 0) 183 | end 184 | 185 | function obj:resizeOut() self:resizeWindowInSteps(true) end 186 | 187 | function obj:resizeIn() self:resizeWindowInSteps(false) end 188 | 189 | --- HammerspoonShiftIt:bindHotkeys(mapping) 190 | --- Method 191 | --- Binds hotkeys for HammerspoonShiftIt 192 | --- 193 | --- Parameters: 194 | --- * mapping - A table containing hotkey modifier/key details (everything is optional) for the following items: 195 | --- * left 196 | --- * right 197 | --- * up 198 | --- * down 199 | --- * upleft 200 | --- * upright 201 | --- * botleft 202 | --- * botright 203 | --- * maximum 204 | --- * toggleFullScreen 205 | --- * toggleZoom 206 | --- * center 207 | --- * nextScreen 208 | --- * previousScreen 209 | --- * resizeOut 210 | --- * resizeIn 211 | function obj:bindHotkeys(mapping) 212 | 213 | if (mapping) then 214 | for k, v in pairs(mapping) do self.mapping[k] = v end 215 | end 216 | 217 | self.hs.hotkey.bind(self.mapping.left[1], self.mapping.left[2], function() self:left() end) 218 | self.hs.hotkey.bind(self.mapping.right[1], self.mapping.right[2], function() self:right() end) 219 | self.hs.hotkey.bind(self.mapping.up[1], self.mapping.up[2], function() self:up() end) 220 | self.hs.hotkey.bind(self.mapping.down[1], self.mapping.down[2], function() self:down() end) 221 | self.hs.hotkey.bind(self.mapping.upleft[1], self.mapping.upleft[2], function() self:upleft() end) 222 | self.hs.hotkey.bind(self.mapping.upright[1], self.mapping.upright[2], function() self:upright() end) 223 | self.hs.hotkey.bind(self.mapping.botleft[1], self.mapping.botleft[2], function() self:botleft() end) 224 | self.hs.hotkey.bind(self.mapping.botright[1], self.mapping.botright[2], function() self:botright() end) 225 | self.hs.hotkey.bind(self.mapping.maximum[1], self.mapping.maximum[2], function() self:maximum() end) 226 | self.hs.hotkey.bind(self.mapping.toggleFullScreen[1], self.mapping.toggleFullScreen[2], function() 227 | self:toggleFullScreen() 228 | end) 229 | self.hs.hotkey.bind(self.mapping.toggleZoom[1], self.mapping.toggleZoom[2], function() self:toggleZoom() end) 230 | self.hs.hotkey.bind(self.mapping.center[1], self.mapping.center[2], function() self:center() end) 231 | self.hs.hotkey.bind(self.mapping.nextScreen[1], self.mapping.nextScreen[2], function() self:nextScreen() end) 232 | self.hs.hotkey.bind(self.mapping.previousScreen[1], self.mapping.previousScreen[2], function() self:prevScreen() end) 233 | self.hs.hotkey.bind(self.mapping.resizeOut[1], self.mapping.resizeOut[2], function() self:resizeOut() end) 234 | self.hs.hotkey.bind(self.mapping.resizeIn[1], self.mapping.resizeIn[2], function() self:resizeIn() end) 235 | 236 | return self 237 | end 238 | 239 | local function join(items, separator) 240 | local res = '' 241 | for _, item in pairs(items) do 242 | if res ~= '' then 243 | res = res .. separator 244 | end 245 | res = res .. item 246 | end 247 | return res 248 | end 249 | 250 | function obj:setWindowCyclingSizes(stepsX, stepsY, skip_print) 251 | if #stepsX < 1 or #stepsY < 1 then 252 | print('Invalid arguments in setWindowCyclingSizes, both dimensions should have at least 1 step') 253 | return 254 | end 255 | local function listToNextMap(list) 256 | local res = {} 257 | for i, item in ipairs(list) do 258 | local prev = (list[i - 1] == nil and list[#list] or list[i - 1]) 259 | res[prev] = item 260 | end 261 | return res 262 | end 263 | 264 | self.cycleSizesX = stepsX 265 | self.cycleSizesY = stepsY 266 | self.nextCycleSizeX = listToNextMap(stepsX) 267 | self.nextCycleSizeY = listToNextMap(stepsY) 268 | 269 | if not skip_print then 270 | print('Cycle sizes for horizontal:', join(stepsX, ' -> ')) 271 | print('Cycle sizes for vertical:', join(stepsY, ' -> ')) 272 | end 273 | end 274 | 275 | -- Set default steps to 50%, as it's the ShiftIt default 276 | obj:setWindowCyclingSizes({ 50 }, { 50 }, true) 277 | 278 | return obj 279 | -------------------------------------------------------------------------------- /hammerspoon/Spoons/SpoonInstall.spoon/docs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Command": [], 4 | "Constant": [], 5 | "Constructor": [], 6 | "Deprecated": [], 7 | "Field": [], 8 | "Function": [], 9 | "Method": [ 10 | { 11 | "def": "SpoonInstall:andUse(name, arg)", 12 | "desc": "Declaratively install, load and configure a Spoon", 13 | "doc": "Declaratively install, load and configure a Spoon\n\nParameters:\n * name - the name of the Spoon to install (without the `.spoon` extension). If the Spoon is already installed, it will be loaded using `hs.loadSpoon()`. If it is not installed, it will be installed using `SpoonInstall:asyncInstallSpoonFromRepo()` and then loaded.\n * arg - if provided, can be used to specify the configuration of the Spoon. The following keys are recognized (all are optional):\n * repo - repository from where the Spoon should be installed if not present in the system, as defined in `SpoonInstall.repos`. Defaults to `\"default\"`.\n * config - a table containing variables to be stored in the Spoon object to configure it. For example, `config = { answer = 42 }` will result in `spoon..answer` being set to 42.\n * hotkeys - a table containing hotkey bindings. If provided, will be passed as-is to the Spoon's `bindHotkeys()` method. The special string `\"default\"` can be given to use the Spoons `defaultHotkeys` variable, if it exists.\n * fn - a function which will be called with the freshly-loaded Spoon object as its first argument.\n * loglevel - if the Spoon has a variable called `logger`, its `setLogLevel()` method will be called with this value.\n * start - if `true`, call the Spoon's `start()` method after configuring everything else.\n * disable - if `true`, do nothing. Easier than commenting it out when you want to temporarily disable a spoon.\n\nReturns:\n * None", 14 | "name": "andUse", 15 | "parameters": [ 16 | " * name - the name of the Spoon to install (without the `.spoon` extension). If the Spoon is already installed, it will be loaded using `hs.loadSpoon()`. If it is not installed, it will be installed using `SpoonInstall:asyncInstallSpoonFromRepo()` and then loaded.", 17 | " * arg - if provided, can be used to specify the configuration of the Spoon. The following keys are recognized (all are optional):", 18 | " * repo - repository from where the Spoon should be installed if not present in the system, as defined in `SpoonInstall.repos`. Defaults to `\"default\"`.", 19 | " * config - a table containing variables to be stored in the Spoon object to configure it. For example, `config = { answer = 42 }` will result in `spoon..answer` being set to 42.", 20 | " * hotkeys - a table containing hotkey bindings. If provided, will be passed as-is to the Spoon's `bindHotkeys()` method. The special string `\"default\"` can be given to use the Spoons `defaultHotkeys` variable, if it exists.", 21 | " * fn - a function which will be called with the freshly-loaded Spoon object as its first argument.", 22 | " * loglevel - if the Spoon has a variable called `logger`, its `setLogLevel()` method will be called with this value.", 23 | " * start - if `true`, call the Spoon's `start()` method after configuring everything else.", 24 | " * disable - if `true`, do nothing. Easier than commenting it out when you want to temporarily disable a spoon." 25 | ], 26 | "returns": [ 27 | " * None" 28 | ], 29 | "signature": "SpoonInstall:andUse(name, arg)", 30 | "stripped_doc": "", 31 | "type": "Method" 32 | }, 33 | { 34 | "def": "SpoonInstall:asyncInstallSpoonFromRepo(name, repo, callback)", 35 | "desc": "Asynchronously install a Spoon from a registered repository", 36 | "doc": "Asynchronously install a Spoon from a registered repository\n\nParameters:\n * name - Name of the Spoon to install.\n * repo - Name of the repository to use. Defaults to `\"default\"`\n * callback - if given, a function to call after the installation finishes (also if it fails). The function receives the following arguments:\n * urlparts - Result of calling `hs.http.urlParts` on the URL of the Spoon zip file\n * success - boolean indicating whether the installation was successful\n\nReturns:\n * `true` if the installation was correctly initiated (i.e. the repo and spoon name were correct), `false` otherwise.", 37 | "name": "asyncInstallSpoonFromRepo", 38 | "parameters": [ 39 | " * name - Name of the Spoon to install.", 40 | " * repo - Name of the repository to use. Defaults to `\"default\"`", 41 | " * callback - if given, a function to call after the installation finishes (also if it fails). The function receives the following arguments:", 42 | " * urlparts - Result of calling `hs.http.urlParts` on the URL of the Spoon zip file", 43 | " * success - boolean indicating whether the installation was successful" 44 | ], 45 | "returns": [ 46 | " * `true` if the installation was correctly initiated (i.e. the repo and spoon name were correct), `false` otherwise." 47 | ], 48 | "signature": "SpoonInstall:asyncInstallSpoonFromRepo(name, repo, callback)", 49 | "stripped_doc": "", 50 | "type": "Method" 51 | }, 52 | { 53 | "def": "SpoonInstall:asyncInstallSpoonFromZipURL(url, callback)", 54 | "desc": "Asynchronously download a Spoon zip file and install it.", 55 | "doc": "Asynchronously download a Spoon zip file and install it.\n\nParameters:\n * url - URL of the zip file to install.\n * callback - if given, a function to call after the installation finishes (also if it fails). The function receives the following arguments:\n * urlparts - Result of calling `hs.http.urlParts` on the URL of the Spoon zip file\n * success - boolean indicating whether the installation was successful\n\nReturns:\n * `true` if the installation was correctly initiated (i.e. the URL is valid), `false` otherwise", 56 | "name": "asyncInstallSpoonFromZipURL", 57 | "parameters": [ 58 | " * url - URL of the zip file to install.", 59 | " * callback - if given, a function to call after the installation finishes (also if it fails). The function receives the following arguments:", 60 | " * urlparts - Result of calling `hs.http.urlParts` on the URL of the Spoon zip file", 61 | " * success - boolean indicating whether the installation was successful" 62 | ], 63 | "returns": [ 64 | " * `true` if the installation was correctly initiated (i.e. the URL is valid), `false` otherwise" 65 | ], 66 | "signature": "SpoonInstall:asyncInstallSpoonFromZipURL(url, callback)", 67 | "stripped_doc": "", 68 | "type": "Method" 69 | }, 70 | { 71 | "def": "SpoonInstall:asyncUpdateAllRepos()", 72 | "desc": "Asynchronously fetch the information about the contents of all Spoon repositories registered in `SpoonInstall.repos`", 73 | "doc": "Asynchronously fetch the information about the contents of all Spoon repositories registered in `SpoonInstall.repos`\n\nParameters:\n * None\n\nReturns:\n * None\n\nNotes:\n * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions.", 74 | "name": "asyncUpdateAllRepos", 75 | "notes": [ 76 | " * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions." 77 | ], 78 | "parameters": [ 79 | " * None" 80 | ], 81 | "returns": [ 82 | " * None" 83 | ], 84 | "signature": "SpoonInstall:asyncUpdateAllRepos()", 85 | "stripped_doc": "", 86 | "type": "Method" 87 | }, 88 | { 89 | "def": "SpoonInstall:asyncUpdateRepo(repo, callback)", 90 | "desc": "Asynchronously fetch the information about the contents of a Spoon repository", 91 | "doc": "Asynchronously fetch the information about the contents of a Spoon repository\n\nParameters:\n * repo - name of the repository to update. Defaults to `\"default\"`.\n * callback - if given, a function to be called after the update finishes (also if it fails). The function will receive the following arguments:\n * repo - name of the repository\n * success - boolean indicating whether the update succeeded\n\nReturns:\n * `true` if the update was correctly initiated (i.e. the repo name is valid), `nil` otherwise\n\nNotes:\n * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions.", 92 | "name": "asyncUpdateRepo", 93 | "notes": [ 94 | " * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions." 95 | ], 96 | "parameters": [ 97 | " * repo - name of the repository to update. Defaults to `\"default\"`.", 98 | " * callback - if given, a function to be called after the update finishes (also if it fails). The function will receive the following arguments:", 99 | " * repo - name of the repository", 100 | " * success - boolean indicating whether the update succeeded" 101 | ], 102 | "returns": [ 103 | " * `true` if the update was correctly initiated (i.e. the repo name is valid), `nil` otherwise" 104 | ], 105 | "signature": "SpoonInstall:asyncUpdateRepo(repo, callback)", 106 | "stripped_doc": "", 107 | "type": "Method" 108 | }, 109 | { 110 | "def": "SpoonInstall:installSpoonFromRepo(name, repo)", 111 | "desc": "Synchronously install a Spoon from a registered repository", 112 | "doc": "Synchronously install a Spoon from a registered repository\n\nParameters:\n * name = Name of the Spoon to install.\n * repo - Name of the repository to use. Defaults to `\"default\"`\n\nReturns:\n * `true` if the installation was successful, `nil` otherwise.", 113 | "name": "installSpoonFromRepo", 114 | "parameters": [ 115 | " * name = Name of the Spoon to install.", 116 | " * repo - Name of the repository to use. Defaults to `\"default\"`" 117 | ], 118 | "returns": [ 119 | " * `true` if the installation was successful, `nil` otherwise." 120 | ], 121 | "signature": "SpoonInstall:installSpoonFromRepo(name, repo)", 122 | "stripped_doc": "", 123 | "type": "Method" 124 | }, 125 | { 126 | "def": "SpoonInstall:installSpoonFromZipURL(url)", 127 | "desc": "Synchronously download a Spoon zip file and install it.", 128 | "doc": "Synchronously download a Spoon zip file and install it.\n\nParameters:\n * url - URL of the zip file to install.\n\nReturns:\n * `true` if the installation was successful, `nil` otherwise", 129 | "name": "installSpoonFromZipURL", 130 | "parameters": [ 131 | " * url - URL of the zip file to install." 132 | ], 133 | "returns": [ 134 | " * `true` if the installation was successful, `nil` otherwise" 135 | ], 136 | "signature": "SpoonInstall:installSpoonFromZipURL(url)", 137 | "stripped_doc": "", 138 | "type": "Method" 139 | }, 140 | { 141 | "def": "SpoonInstall:repolist()", 142 | "desc": "Return a sorted list of registered Spoon repositories", 143 | "doc": "Return a sorted list of registered Spoon repositories\n\nParameters:\n * None\n\nReturns:\n * Table containing a list of strings with the repository identifiers", 144 | "name": "repolist", 145 | "parameters": [ 146 | " * None" 147 | ], 148 | "returns": [ 149 | " * Table containing a list of strings with the repository identifiers" 150 | ], 151 | "signature": "SpoonInstall:repolist()", 152 | "stripped_doc": "", 153 | "type": "Method" 154 | }, 155 | { 156 | "def": "SpoonInstall:search(pat)", 157 | "desc": "Search repositories for a pattern", 158 | "doc": "Search repositories for a pattern\n\nParameters:\n * pat - Lua pattern that will be matched against the name and description of each spoon in the registered repositories. All text is converted to lowercase before searching it, so you can use all-lowercase in your pattern.\n\nReturns:\n * Table containing a list of matching entries. Each entry is a table with the following keys:\n * name - Spoon name\n * desc - description of the spoon\n * repo - identifier in the repository where the match was found", 159 | "name": "search", 160 | "parameters": [ 161 | " * pat - Lua pattern that will be matched against the name and description of each spoon in the registered repositories. All text is converted to lowercase before searching it, so you can use all-lowercase in your pattern." 162 | ], 163 | "returns": [ 164 | " * Table containing a list of matching entries. Each entry is a table with the following keys:", 165 | " * name - Spoon name", 166 | " * desc - description of the spoon", 167 | " * repo - identifier in the repository where the match was found" 168 | ], 169 | "signature": "SpoonInstall:search(pat)", 170 | "stripped_doc": "", 171 | "type": "Method" 172 | }, 173 | { 174 | "def": "SpoonInstall:updateAllRepos()", 175 | "desc": "Synchronously fetch the information about the contents of all Spoon repositories registered in `SpoonInstall.repos`", 176 | "doc": "Synchronously fetch the information about the contents of all Spoon repositories registered in `SpoonInstall.repos`\n\nParameters:\n * None\n\nReturns:\n * None\n\nNotes:\n * This is a synchronous call, which means Hammerspoon will be blocked until it finishes.\n * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions.", 177 | "name": "updateAllRepos", 178 | "notes": [ 179 | " * This is a synchronous call, which means Hammerspoon will be blocked until it finishes.", 180 | " * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions." 181 | ], 182 | "parameters": [ 183 | " * None" 184 | ], 185 | "returns": [ 186 | " * None" 187 | ], 188 | "signature": "SpoonInstall:updateAllRepos()", 189 | "stripped_doc": "", 190 | "type": "Method" 191 | }, 192 | { 193 | "def": "SpoonInstall:updateRepo(repo)", 194 | "desc": "Synchronously fetch the information about the contents of a Spoon repository", 195 | "doc": "Synchronously fetch the information about the contents of a Spoon repository\n\nParameters:\n * repo - name of the repository to update. Defaults to `\"default\"`.\n\nReturns:\n * `true` if the update was successful, `nil` otherwise\n\nNotes:\n * This is a synchronous call, which means Hammerspoon will be blocked until it finishes. For use in your configuration files, it's advisable to use `SpoonInstall.asyncUpdateRepo()` instead.\n * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions.", 196 | "name": "updateRepo", 197 | "notes": [ 198 | " * This is a synchronous call, which means Hammerspoon will be blocked until it finishes. For use in your configuration files, it's advisable to use `SpoonInstall.asyncUpdateRepo()` instead.", 199 | " * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions." 200 | ], 201 | "parameters": [ 202 | " * repo - name of the repository to update. Defaults to `\"default\"`." 203 | ], 204 | "returns": [ 205 | " * `true` if the update was successful, `nil` otherwise" 206 | ], 207 | "signature": "SpoonInstall:updateRepo(repo)", 208 | "stripped_doc": "", 209 | "type": "Method" 210 | } 211 | ], 212 | "Variable": [ 213 | { 214 | "def": "SpoonInstall.logger", 215 | "desc": "Logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.", 216 | "doc": "Logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.", 217 | "name": "logger", 218 | "signature": "SpoonInstall.logger", 219 | "stripped_doc": "", 220 | "type": "Variable" 221 | }, 222 | { 223 | "def": "SpoonInstall.repos", 224 | "desc": "Table containing the list of available Spoon repositories. The key", 225 | "doc": "Table containing the list of available Spoon repositories. The key\nof each entry is an identifier for the repository, and its value\nis a table with the following entries:\n * desc - Human-readable description for the repository\n * branch - Active git branch for the Spoon files\n * url - Base URL for the repository. For now the repository is assumed to be hosted in GitHub, and the URL should be the main base URL of the repository. Repository metadata needs to be stored under `docs/docs.json`, and the Spoon zip files need to be stored under `Spoons/`.\n\nDefault value:\n```\n{\n default = {\n url = \"https://github.com/Hammerspoon/Spoons\",\n desc = \"Main Hammerspoon Spoon repository\",\n branch = \"master\",\n }\n}\n```", 226 | "name": "repos", 227 | "signature": "SpoonInstall.repos", 228 | "stripped_doc": "of each entry is an identifier for the repository, and its value\nis a table with the following entries:\n * desc - Human-readable description for the repository\n * branch - Active git branch for the Spoon files\n * url - Base URL for the repository. For now the repository is assumed to be hosted in GitHub, and the URL should be the main base URL of the repository. Repository metadata needs to be stored under `docs/docs.json`, and the Spoon zip files need to be stored under `Spoons/`.\nDefault value:\n```\n{\n default = {\n url = \"https://github.com/Hammerspoon/Spoons\",\n desc = \"Main Hammerspoon Spoon repository\",\n branch = \"master\",\n }\n}\n```", 229 | "type": "Variable" 230 | }, 231 | { 232 | "def": "SpoonInstall.use_syncinstall", 233 | "desc": "If `true`, `andUse()` will update repos and install packages synchronously. Defaults to `false`.", 234 | "doc": "If `true`, `andUse()` will update repos and install packages synchronously. Defaults to `false`.\n\nKeep in mind that if you set this to `true`, Hammerspoon will\nblock until all missing Spoons are installed, but the notifications\nwill happen at a more \"human readable\" rate.", 235 | "name": "use_syncinstall", 236 | "signature": "SpoonInstall.use_syncinstall", 237 | "stripped_doc": "Keep in mind that if you set this to `true`, Hammerspoon will\nblock until all missing Spoons are installed, but the notifications\nwill happen at a more \"human readable\" rate.", 238 | "type": "Variable" 239 | } 240 | ], 241 | "desc": "Install and manage Spoons and Spoon repositories", 242 | "doc": "Install and manage Spoons and Spoon repositories\n\nDownload: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/SpoonInstall.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/SpoonInstall.spoon.zip)", 243 | "items": [ 244 | { 245 | "def": "SpoonInstall:andUse(name, arg)", 246 | "desc": "Declaratively install, load and configure a Spoon", 247 | "doc": "Declaratively install, load and configure a Spoon\n\nParameters:\n * name - the name of the Spoon to install (without the `.spoon` extension). If the Spoon is already installed, it will be loaded using `hs.loadSpoon()`. If it is not installed, it will be installed using `SpoonInstall:asyncInstallSpoonFromRepo()` and then loaded.\n * arg - if provided, can be used to specify the configuration of the Spoon. The following keys are recognized (all are optional):\n * repo - repository from where the Spoon should be installed if not present in the system, as defined in `SpoonInstall.repos`. Defaults to `\"default\"`.\n * config - a table containing variables to be stored in the Spoon object to configure it. For example, `config = { answer = 42 }` will result in `spoon..answer` being set to 42.\n * hotkeys - a table containing hotkey bindings. If provided, will be passed as-is to the Spoon's `bindHotkeys()` method. The special string `\"default\"` can be given to use the Spoons `defaultHotkeys` variable, if it exists.\n * fn - a function which will be called with the freshly-loaded Spoon object as its first argument.\n * loglevel - if the Spoon has a variable called `logger`, its `setLogLevel()` method will be called with this value.\n * start - if `true`, call the Spoon's `start()` method after configuring everything else.\n * disable - if `true`, do nothing. Easier than commenting it out when you want to temporarily disable a spoon.\n\nReturns:\n * None", 248 | "name": "andUse", 249 | "parameters": [ 250 | " * name - the name of the Spoon to install (without the `.spoon` extension). If the Spoon is already installed, it will be loaded using `hs.loadSpoon()`. If it is not installed, it will be installed using `SpoonInstall:asyncInstallSpoonFromRepo()` and then loaded.", 251 | " * arg - if provided, can be used to specify the configuration of the Spoon. The following keys are recognized (all are optional):", 252 | " * repo - repository from where the Spoon should be installed if not present in the system, as defined in `SpoonInstall.repos`. Defaults to `\"default\"`.", 253 | " * config - a table containing variables to be stored in the Spoon object to configure it. For example, `config = { answer = 42 }` will result in `spoon..answer` being set to 42.", 254 | " * hotkeys - a table containing hotkey bindings. If provided, will be passed as-is to the Spoon's `bindHotkeys()` method. The special string `\"default\"` can be given to use the Spoons `defaultHotkeys` variable, if it exists.", 255 | " * fn - a function which will be called with the freshly-loaded Spoon object as its first argument.", 256 | " * loglevel - if the Spoon has a variable called `logger`, its `setLogLevel()` method will be called with this value.", 257 | " * start - if `true`, call the Spoon's `start()` method after configuring everything else.", 258 | " * disable - if `true`, do nothing. Easier than commenting it out when you want to temporarily disable a spoon." 259 | ], 260 | "returns": [ 261 | " * None" 262 | ], 263 | "signature": "SpoonInstall:andUse(name, arg)", 264 | "stripped_doc": "", 265 | "type": "Method" 266 | }, 267 | { 268 | "def": "SpoonInstall:asyncInstallSpoonFromRepo(name, repo, callback)", 269 | "desc": "Asynchronously install a Spoon from a registered repository", 270 | "doc": "Asynchronously install a Spoon from a registered repository\n\nParameters:\n * name - Name of the Spoon to install.\n * repo - Name of the repository to use. Defaults to `\"default\"`\n * callback - if given, a function to call after the installation finishes (also if it fails). The function receives the following arguments:\n * urlparts - Result of calling `hs.http.urlParts` on the URL of the Spoon zip file\n * success - boolean indicating whether the installation was successful\n\nReturns:\n * `true` if the installation was correctly initiated (i.e. the repo and spoon name were correct), `false` otherwise.", 271 | "name": "asyncInstallSpoonFromRepo", 272 | "parameters": [ 273 | " * name - Name of the Spoon to install.", 274 | " * repo - Name of the repository to use. Defaults to `\"default\"`", 275 | " * callback - if given, a function to call after the installation finishes (also if it fails). The function receives the following arguments:", 276 | " * urlparts - Result of calling `hs.http.urlParts` on the URL of the Spoon zip file", 277 | " * success - boolean indicating whether the installation was successful" 278 | ], 279 | "returns": [ 280 | " * `true` if the installation was correctly initiated (i.e. the repo and spoon name were correct), `false` otherwise." 281 | ], 282 | "signature": "SpoonInstall:asyncInstallSpoonFromRepo(name, repo, callback)", 283 | "stripped_doc": "", 284 | "type": "Method" 285 | }, 286 | { 287 | "def": "SpoonInstall:asyncInstallSpoonFromZipURL(url, callback)", 288 | "desc": "Asynchronously download a Spoon zip file and install it.", 289 | "doc": "Asynchronously download a Spoon zip file and install it.\n\nParameters:\n * url - URL of the zip file to install.\n * callback - if given, a function to call after the installation finishes (also if it fails). The function receives the following arguments:\n * urlparts - Result of calling `hs.http.urlParts` on the URL of the Spoon zip file\n * success - boolean indicating whether the installation was successful\n\nReturns:\n * `true` if the installation was correctly initiated (i.e. the URL is valid), `false` otherwise", 290 | "name": "asyncInstallSpoonFromZipURL", 291 | "parameters": [ 292 | " * url - URL of the zip file to install.", 293 | " * callback - if given, a function to call after the installation finishes (also if it fails). The function receives the following arguments:", 294 | " * urlparts - Result of calling `hs.http.urlParts` on the URL of the Spoon zip file", 295 | " * success - boolean indicating whether the installation was successful" 296 | ], 297 | "returns": [ 298 | " * `true` if the installation was correctly initiated (i.e. the URL is valid), `false` otherwise" 299 | ], 300 | "signature": "SpoonInstall:asyncInstallSpoonFromZipURL(url, callback)", 301 | "stripped_doc": "", 302 | "type": "Method" 303 | }, 304 | { 305 | "def": "SpoonInstall:asyncUpdateAllRepos()", 306 | "desc": "Asynchronously fetch the information about the contents of all Spoon repositories registered in `SpoonInstall.repos`", 307 | "doc": "Asynchronously fetch the information about the contents of all Spoon repositories registered in `SpoonInstall.repos`\n\nParameters:\n * None\n\nReturns:\n * None\n\nNotes:\n * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions.", 308 | "name": "asyncUpdateAllRepos", 309 | "notes": [ 310 | " * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions." 311 | ], 312 | "parameters": [ 313 | " * None" 314 | ], 315 | "returns": [ 316 | " * None" 317 | ], 318 | "signature": "SpoonInstall:asyncUpdateAllRepos()", 319 | "stripped_doc": "", 320 | "type": "Method" 321 | }, 322 | { 323 | "def": "SpoonInstall:asyncUpdateRepo(repo, callback)", 324 | "desc": "Asynchronously fetch the information about the contents of a Spoon repository", 325 | "doc": "Asynchronously fetch the information about the contents of a Spoon repository\n\nParameters:\n * repo - name of the repository to update. Defaults to `\"default\"`.\n * callback - if given, a function to be called after the update finishes (also if it fails). The function will receive the following arguments:\n * repo - name of the repository\n * success - boolean indicating whether the update succeeded\n\nReturns:\n * `true` if the update was correctly initiated (i.e. the repo name is valid), `nil` otherwise\n\nNotes:\n * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions.", 326 | "name": "asyncUpdateRepo", 327 | "notes": [ 328 | " * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions." 329 | ], 330 | "parameters": [ 331 | " * repo - name of the repository to update. Defaults to `\"default\"`.", 332 | " * callback - if given, a function to be called after the update finishes (also if it fails). The function will receive the following arguments:", 333 | " * repo - name of the repository", 334 | " * success - boolean indicating whether the update succeeded" 335 | ], 336 | "returns": [ 337 | " * `true` if the update was correctly initiated (i.e. the repo name is valid), `nil` otherwise" 338 | ], 339 | "signature": "SpoonInstall:asyncUpdateRepo(repo, callback)", 340 | "stripped_doc": "", 341 | "type": "Method" 342 | }, 343 | { 344 | "def": "SpoonInstall:installSpoonFromRepo(name, repo)", 345 | "desc": "Synchronously install a Spoon from a registered repository", 346 | "doc": "Synchronously install a Spoon from a registered repository\n\nParameters:\n * name = Name of the Spoon to install.\n * repo - Name of the repository to use. Defaults to `\"default\"`\n\nReturns:\n * `true` if the installation was successful, `nil` otherwise.", 347 | "name": "installSpoonFromRepo", 348 | "parameters": [ 349 | " * name = Name of the Spoon to install.", 350 | " * repo - Name of the repository to use. Defaults to `\"default\"`" 351 | ], 352 | "returns": [ 353 | " * `true` if the installation was successful, `nil` otherwise." 354 | ], 355 | "signature": "SpoonInstall:installSpoonFromRepo(name, repo)", 356 | "stripped_doc": "", 357 | "type": "Method" 358 | }, 359 | { 360 | "def": "SpoonInstall:installSpoonFromZipURL(url)", 361 | "desc": "Synchronously download a Spoon zip file and install it.", 362 | "doc": "Synchronously download a Spoon zip file and install it.\n\nParameters:\n * url - URL of the zip file to install.\n\nReturns:\n * `true` if the installation was successful, `nil` otherwise", 363 | "name": "installSpoonFromZipURL", 364 | "parameters": [ 365 | " * url - URL of the zip file to install." 366 | ], 367 | "returns": [ 368 | " * `true` if the installation was successful, `nil` otherwise" 369 | ], 370 | "signature": "SpoonInstall:installSpoonFromZipURL(url)", 371 | "stripped_doc": "", 372 | "type": "Method" 373 | }, 374 | { 375 | "def": "SpoonInstall.logger", 376 | "desc": "Logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.", 377 | "doc": "Logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.", 378 | "name": "logger", 379 | "signature": "SpoonInstall.logger", 380 | "stripped_doc": "", 381 | "type": "Variable" 382 | }, 383 | { 384 | "def": "SpoonInstall:repolist()", 385 | "desc": "Return a sorted list of registered Spoon repositories", 386 | "doc": "Return a sorted list of registered Spoon repositories\n\nParameters:\n * None\n\nReturns:\n * Table containing a list of strings with the repository identifiers", 387 | "name": "repolist", 388 | "parameters": [ 389 | " * None" 390 | ], 391 | "returns": [ 392 | " * Table containing a list of strings with the repository identifiers" 393 | ], 394 | "signature": "SpoonInstall:repolist()", 395 | "stripped_doc": "", 396 | "type": "Method" 397 | }, 398 | { 399 | "def": "SpoonInstall.repos", 400 | "desc": "Table containing the list of available Spoon repositories. The key", 401 | "doc": "Table containing the list of available Spoon repositories. The key\nof each entry is an identifier for the repository, and its value\nis a table with the following entries:\n * desc - Human-readable description for the repository\n * branch - Active git branch for the Spoon files\n * url - Base URL for the repository. For now the repository is assumed to be hosted in GitHub, and the URL should be the main base URL of the repository. Repository metadata needs to be stored under `docs/docs.json`, and the Spoon zip files need to be stored under `Spoons/`.\n\nDefault value:\n```\n{\n default = {\n url = \"https://github.com/Hammerspoon/Spoons\",\n desc = \"Main Hammerspoon Spoon repository\",\n branch = \"master\",\n }\n}\n```", 402 | "name": "repos", 403 | "signature": "SpoonInstall.repos", 404 | "stripped_doc": "of each entry is an identifier for the repository, and its value\nis a table with the following entries:\n * desc - Human-readable description for the repository\n * branch - Active git branch for the Spoon files\n * url - Base URL for the repository. For now the repository is assumed to be hosted in GitHub, and the URL should be the main base URL of the repository. Repository metadata needs to be stored under `docs/docs.json`, and the Spoon zip files need to be stored under `Spoons/`.\nDefault value:\n```\n{\n default = {\n url = \"https://github.com/Hammerspoon/Spoons\",\n desc = \"Main Hammerspoon Spoon repository\",\n branch = \"master\",\n }\n}\n```", 405 | "type": "Variable" 406 | }, 407 | { 408 | "def": "SpoonInstall:search(pat)", 409 | "desc": "Search repositories for a pattern", 410 | "doc": "Search repositories for a pattern\n\nParameters:\n * pat - Lua pattern that will be matched against the name and description of each spoon in the registered repositories. All text is converted to lowercase before searching it, so you can use all-lowercase in your pattern.\n\nReturns:\n * Table containing a list of matching entries. Each entry is a table with the following keys:\n * name - Spoon name\n * desc - description of the spoon\n * repo - identifier in the repository where the match was found", 411 | "name": "search", 412 | "parameters": [ 413 | " * pat - Lua pattern that will be matched against the name and description of each spoon in the registered repositories. All text is converted to lowercase before searching it, so you can use all-lowercase in your pattern." 414 | ], 415 | "returns": [ 416 | " * Table containing a list of matching entries. Each entry is a table with the following keys:", 417 | " * name - Spoon name", 418 | " * desc - description of the spoon", 419 | " * repo - identifier in the repository where the match was found" 420 | ], 421 | "signature": "SpoonInstall:search(pat)", 422 | "stripped_doc": "", 423 | "type": "Method" 424 | }, 425 | { 426 | "def": "SpoonInstall:updateAllRepos()", 427 | "desc": "Synchronously fetch the information about the contents of all Spoon repositories registered in `SpoonInstall.repos`", 428 | "doc": "Synchronously fetch the information about the contents of all Spoon repositories registered in `SpoonInstall.repos`\n\nParameters:\n * None\n\nReturns:\n * None\n\nNotes:\n * This is a synchronous call, which means Hammerspoon will be blocked until it finishes.\n * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions.", 429 | "name": "updateAllRepos", 430 | "notes": [ 431 | " * This is a synchronous call, which means Hammerspoon will be blocked until it finishes.", 432 | " * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions." 433 | ], 434 | "parameters": [ 435 | " * None" 436 | ], 437 | "returns": [ 438 | " * None" 439 | ], 440 | "signature": "SpoonInstall:updateAllRepos()", 441 | "stripped_doc": "", 442 | "type": "Method" 443 | }, 444 | { 445 | "def": "SpoonInstall:updateRepo(repo)", 446 | "desc": "Synchronously fetch the information about the contents of a Spoon repository", 447 | "doc": "Synchronously fetch the information about the contents of a Spoon repository\n\nParameters:\n * repo - name of the repository to update. Defaults to `\"default\"`.\n\nReturns:\n * `true` if the update was successful, `nil` otherwise\n\nNotes:\n * This is a synchronous call, which means Hammerspoon will be blocked until it finishes. For use in your configuration files, it's advisable to use `SpoonInstall.asyncUpdateRepo()` instead.\n * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions.", 448 | "name": "updateRepo", 449 | "notes": [ 450 | " * This is a synchronous call, which means Hammerspoon will be blocked until it finishes. For use in your configuration files, it's advisable to use `SpoonInstall.asyncUpdateRepo()` instead.", 451 | " * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions." 452 | ], 453 | "parameters": [ 454 | " * repo - name of the repository to update. Defaults to `\"default\"`." 455 | ], 456 | "returns": [ 457 | " * `true` if the update was successful, `nil` otherwise" 458 | ], 459 | "signature": "SpoonInstall:updateRepo(repo)", 460 | "stripped_doc": "", 461 | "type": "Method" 462 | }, 463 | { 464 | "def": "SpoonInstall.use_syncinstall", 465 | "desc": "If `true`, `andUse()` will update repos and install packages synchronously. Defaults to `false`.", 466 | "doc": "If `true`, `andUse()` will update repos and install packages synchronously. Defaults to `false`.\n\nKeep in mind that if you set this to `true`, Hammerspoon will\nblock until all missing Spoons are installed, but the notifications\nwill happen at a more \"human readable\" rate.", 467 | "name": "use_syncinstall", 468 | "signature": "SpoonInstall.use_syncinstall", 469 | "stripped_doc": "Keep in mind that if you set this to `true`, Hammerspoon will\nblock until all missing Spoons are installed, but the notifications\nwill happen at a more \"human readable\" rate.", 470 | "type": "Variable" 471 | } 472 | ], 473 | "name": "SpoonInstall", 474 | "stripped_doc": "\nDownload: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/SpoonInstall.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/SpoonInstall.spoon.zip)", 475 | "submodules": [], 476 | "type": "Module" 477 | } 478 | ] -------------------------------------------------------------------------------- /hammerspoon/Spoons/SpoonInstall.spoon/init.lua: -------------------------------------------------------------------------------- 1 | --- === SpoonInstall === 2 | --- 3 | --- Install and manage Spoons and Spoon repositories 4 | --- 5 | --- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/SpoonInstall.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/SpoonInstall.spoon.zip) 6 | 7 | local obj={} 8 | obj.__index = obj 9 | 10 | -- Metadata 11 | obj.name = "SpoonInstall" 12 | obj.version = "0.1" 13 | obj.author = "Diego Zamboni " 14 | obj.homepage = "https://github.com/Hammerspoon/Spoons" 15 | obj.license = "MIT - https://opensource.org/licenses/MIT" 16 | 17 | --- SpoonInstall.logger 18 | --- Variable 19 | --- Logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon. 20 | obj.logger = hs.logger.new('SpoonInstall') 21 | 22 | --- SpoonInstall.repos 23 | --- Variable 24 | --- Table containing the list of available Spoon repositories. The key 25 | --- of each entry is an identifier for the repository, and its value 26 | --- is a table with the following entries: 27 | --- * desc - Human-readable description for the repository 28 | --- * branch - Active git branch for the Spoon files 29 | --- * url - Base URL for the repository. For now the repository is assumed to be hosted in GitHub, and the URL should be the main base URL of the repository. Repository metadata needs to be stored under `docs/docs.json`, and the Spoon zip files need to be stored under `Spoons/`. 30 | --- 31 | --- Default value: 32 | --- ``` 33 | --- { 34 | --- default = { 35 | --- url = "https://github.com/Hammerspoon/Spoons", 36 | --- desc = "Main Hammerspoon Spoon repository", 37 | --- branch = "master", 38 | --- } 39 | --- } 40 | --- ``` 41 | obj.repos = { 42 | default = { 43 | url = "https://github.com/Hammerspoon/Spoons", 44 | desc = "Main Hammerspoon Spoon repository", 45 | branch = "master", 46 | } 47 | } 48 | 49 | --- SpoonInstall.use_syncinstall 50 | --- Variable 51 | --- If `true`, `andUse()` will update repos and install packages synchronously. Defaults to `false`. 52 | --- 53 | --- Keep in mind that if you set this to `true`, Hammerspoon will 54 | --- block until all missing Spoons are installed, but the notifications 55 | --- will happen at a more "human readable" rate. 56 | obj.use_syncinstall = false 57 | 58 | -- Execute a command and return its output with trailing EOLs trimmed. If the command fails, an error message is logged. 59 | local function _x(cmd, errfmt, ...) 60 | local output, status = hs.execute(cmd) 61 | if status then 62 | local trimstr = string.gsub(output, "\n*$", "") 63 | return trimstr 64 | else 65 | obj.logger.ef(errfmt, ...) 66 | return nil 67 | end 68 | end 69 | 70 | -- -------------------------------------------------------------------- 71 | -- Spoon repository management 72 | 73 | -- Internal callback to process and store the data from docs.json about a repository 74 | -- callback is called with repo as arguments, only if the call is successful 75 | function obj:_storeRepoJSON(repo, callback, status, body, hdrs) 76 | local success=nil 77 | if (status < 100) or (status >= 400) then 78 | self.logger.ef("Error fetching JSON data for repository '%s'. Error code %d: %s", repo, status, body or "") 79 | else 80 | local json = hs.json.decode(body) 81 | if json then 82 | self.repos[repo].data = {} 83 | for i,v in ipairs(json) do 84 | v.download_url = self.repos[repo].download_base_url .. v.name .. ".spoon.zip" 85 | self.repos[repo].data[v.name] = v 86 | end 87 | self.logger.df("Updated JSON data for repository '%s'", repo) 88 | success=true 89 | else 90 | self.logger.ef("Invalid JSON received for repository '%s': %s", repo, body) 91 | end 92 | end 93 | if callback then 94 | callback(repo, success) 95 | end 96 | return success 97 | end 98 | 99 | -- Internal function to return the URL of the docs.json file based on the URL of a GitHub repo 100 | function obj:_build_repo_json_url(repo) 101 | if self.repos[repo] and self.repos[repo].url then 102 | local branch = self.repos[repo].branch or "master" 103 | self.repos[repo].json_url = string.gsub(self.repos[repo].url, "/$", "") .. "/raw/"..branch.."/docs/docs.json" 104 | self.repos[repo].download_base_url = string.gsub(self.repos[repo].url, "/$", "") .. "/raw/"..branch.."/Spoons/" 105 | return true 106 | else 107 | self.logger.ef("Invalid or unknown repository '%s'", repo) 108 | return nil 109 | end 110 | end 111 | 112 | --- SpoonInstall:asyncUpdateRepo(repo, callback) 113 | --- Method 114 | --- Asynchronously fetch the information about the contents of a Spoon repository 115 | --- 116 | --- Parameters: 117 | --- * repo - name of the repository to update. Defaults to `"default"`. 118 | --- * callback - if given, a function to be called after the update finishes (also if it fails). The function will receive the following arguments: 119 | --- * repo - name of the repository 120 | --- * success - boolean indicating whether the update succeeded 121 | --- 122 | --- Returns: 123 | --- * `true` if the update was correctly initiated (i.e. the repo name is valid), `nil` otherwise 124 | --- 125 | --- Notes: 126 | --- * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions. 127 | function obj:asyncUpdateRepo(repo, callback) 128 | if not repo then repo = 'default' end 129 | if self:_build_repo_json_url(repo) then 130 | hs.http.asyncGet(self.repos[repo].json_url, nil, hs.fnutils.partial(self._storeRepoJSON, self, repo, callback)) 131 | return true 132 | else 133 | return nil 134 | end 135 | end 136 | 137 | --- SpoonInstall:updateRepo(repo) 138 | --- Method 139 | --- Synchronously fetch the information about the contents of a Spoon repository 140 | --- 141 | --- Parameters: 142 | --- * repo - name of the repository to update. Defaults to `"default"`. 143 | --- 144 | --- Returns: 145 | --- * `true` if the update was successful, `nil` otherwise 146 | --- 147 | --- Notes: 148 | --- * This is a synchronous call, which means Hammerspoon will be blocked until it finishes. For use in your configuration files, it's advisable to use `SpoonInstall.asyncUpdateRepo()` instead. 149 | --- * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions. 150 | function obj:updateRepo(repo) 151 | if not repo then repo = 'default' end 152 | if self:_build_repo_json_url(repo) then 153 | local a,b,c = hs.http.get(self.repos[repo].json_url) 154 | return self:_storeRepoJSON(repo, nil, a, b, c) 155 | else 156 | return nil 157 | end 158 | end 159 | 160 | --- SpoonInstall:asyncUpdateAllRepos() 161 | --- Method 162 | --- Asynchronously fetch the information about the contents of all Spoon repositories registered in `SpoonInstall.repos` 163 | --- 164 | --- Parameters: 165 | --- * None 166 | --- 167 | --- Returns: 168 | --- * None 169 | --- 170 | --- Notes: 171 | --- * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions. 172 | function obj:asyncUpdateAllRepos() 173 | for k,v in pairs(self.repos) do 174 | self:asyncUpdateRepo(k) 175 | end 176 | end 177 | 178 | --- SpoonInstall:updateAllRepos() 179 | --- Method 180 | --- Synchronously fetch the information about the contents of all Spoon repositories registered in `SpoonInstall.repos` 181 | --- 182 | --- Parameters: 183 | --- * None 184 | --- 185 | --- Returns: 186 | --- * None 187 | --- 188 | --- Notes: 189 | --- * This is a synchronous call, which means Hammerspoon will be blocked until it finishes. 190 | --- * For now, the repository data is not persisted, so you need to update it after every restart if you want to use any of the install functions. 191 | function obj:updateAllRepos() 192 | for k,v in pairs(self.repos) do 193 | self:updateRepo(k) 194 | end 195 | end 196 | 197 | --- SpoonInstall:repolist() 198 | --- Method 199 | --- Return a sorted list of registered Spoon repositories 200 | --- 201 | --- Parameters: 202 | --- * None 203 | --- 204 | --- Returns: 205 | --- * Table containing a list of strings with the repository identifiers 206 | function obj:repolist() 207 | local keys={} 208 | -- Create sorted list of keys 209 | for k,v in pairs(self.repos) do table.insert(keys, k) end 210 | table.sort(keys) 211 | return keys 212 | end 213 | 214 | --- SpoonInstall:search(pat) 215 | --- Method 216 | --- Search repositories for a pattern 217 | --- 218 | --- Parameters: 219 | --- * pat - Lua pattern that will be matched against the name and description of each spoon in the registered repositories. All text is converted to lowercase before searching it, so you can use all-lowercase in your pattern. 220 | --- 221 | --- Returns: 222 | --- * Table containing a list of matching entries. Each entry is a table with the following keys: 223 | --- * name - Spoon name 224 | --- * desc - description of the spoon 225 | --- * repo - identifier in the repository where the match was found 226 | function obj:search(pat) 227 | local res={} 228 | for repo,v in pairs(self.repos) do 229 | if v.data then 230 | for spoon,rec in pairs(v.data) do 231 | if string.find(string.lower(rec.name .. "\n" .. rec.desc), pat) then 232 | table.insert(res, { name = rec.name, desc = rec.desc, repo = repo }) 233 | end 234 | end 235 | else 236 | self.logger.ef("Repository data for '%s' not available - call spoon.SpoonInstall:updateRepo('%s'), then try again.", repo, repo) 237 | end 238 | end 239 | return res 240 | end 241 | 242 | -- -------------------------------------------------------------------- 243 | -- Spoon installation 244 | 245 | -- Internal callback function to finalize the installation of a spoon after the zip file has been downloaded. 246 | -- callback, if given, is called with (urlparts, success) as arguments 247 | function obj:_installSpoonFromZipURLgetCallback(urlparts, callback, status, body, headers) 248 | local success=nil 249 | if (status < 100) or (status >= 400) then 250 | self.logger.ef("Error downloading %s. Error code %d: %s", urlparts.absoluteString, status, body or "") 251 | else 252 | -- Write the zip file to disk in a temporary directory 253 | local tmpdir=_x("/usr/bin/mktemp -d", "Error creating temporary directory to download new spoon.") 254 | if tmpdir then 255 | local outfile = string.format("%s/%s", tmpdir, urlparts.lastPathComponent) 256 | local f=assert(io.open(outfile, "w")) 257 | f:write(body) 258 | f:close() 259 | 260 | -- Check its contents - only one *.spoon directory should be in there 261 | output = _x(string.format("/usr/bin/unzip -l %s '*.spoon/' | /usr/bin/awk '$NF ~ /\\.spoon\\/$/ { print $NF }' | /usr/bin/wc -l", outfile), 262 | "Error examining downloaded zip file %s, leaving it in place for your examination.", outfile) 263 | if output then 264 | if (tonumber(output) or 0) == 1 then 265 | -- Uncompress the zip file 266 | local outdir = string.format("%s/Spoons", hs.configdir) 267 | if _x(string.format("/usr/bin/unzip -o %s -d %s 2>&1", outfile, outdir), 268 | "Error uncompressing file %s, leaving it in place for your examination.", outfile) then 269 | -- And finally, install it using Hammerspoon itself 270 | self.logger.f("Downloaded and installed %s", urlparts.absoluteString) 271 | _x(string.format("/bin/rm -rf '%s'", tmpdir), "Error removing directory %s", tmpdir) 272 | success=true 273 | end 274 | else 275 | self.logger.ef("The downloaded zip file %s is invalid - it should contain exactly one spoon. Leaving it in place for your examination.", outfile) 276 | end 277 | end 278 | end 279 | end 280 | if callback then 281 | callback(urlparts, success) 282 | end 283 | return success 284 | end 285 | 286 | --- SpoonInstall:asyncInstallSpoonFromZipURL(url, callback) 287 | --- Method 288 | --- Asynchronously download a Spoon zip file and install it. 289 | --- 290 | --- Parameters: 291 | --- * url - URL of the zip file to install. 292 | --- * callback - if given, a function to call after the installation finishes (also if it fails). The function receives the following arguments: 293 | --- * urlparts - Result of calling `hs.http.urlParts` on the URL of the Spoon zip file 294 | --- * success - boolean indicating whether the installation was successful 295 | --- 296 | --- Returns: 297 | --- * `true` if the installation was correctly initiated (i.e. the URL is valid), `false` otherwise 298 | function obj:asyncInstallSpoonFromZipURL(url, callback) 299 | local urlparts = hs.http.urlParts(url) 300 | local dlfile = urlparts.lastPathComponent 301 | if dlfile and dlfile ~= "" and urlparts.pathExtension == "zip" then 302 | hs.http.asyncGet(url, nil, hs.fnutils.partial(self._installSpoonFromZipURLgetCallback, self, urlparts, callback)) 303 | return true 304 | else 305 | self.logger.ef("Invalid URL %s, must point to a zip file", url) 306 | return nil 307 | end 308 | end 309 | 310 | --- SpoonInstall:installSpoonFromZipURL(url) 311 | --- Method 312 | --- Synchronously download a Spoon zip file and install it. 313 | --- 314 | --- Parameters: 315 | --- * url - URL of the zip file to install. 316 | --- 317 | --- Returns: 318 | --- * `true` if the installation was successful, `nil` otherwise 319 | function obj:installSpoonFromZipURL(url) 320 | local urlparts = hs.http.urlParts(url) 321 | local dlfile = urlparts.lastPathComponent 322 | if dlfile and dlfile ~= "" and urlparts.pathExtension == "zip" then 323 | a,b,c=hs.http.get(url) 324 | return self:_installSpoonFromZipURLgetCallback(urlparts, nil, a, b, c) 325 | else 326 | self.logger.ef("Invalid URL %s, must point to a zip file", url) 327 | return nil 328 | end 329 | end 330 | 331 | -- Internal function to check if a Spoon/Repo combination is valid 332 | function obj:_is_valid_spoon(name, repo) 333 | if self.repos[repo] then 334 | if self.repos[repo].data then 335 | if self.repos[repo].data[name] then 336 | return true 337 | else 338 | self.logger.ef("Spoon '%s' does not exist in repository '%s'. Please check and try again.", name, repo) 339 | end 340 | else 341 | self.logger.ef("Repository data for '%s' not available - call spoon.SpoonInstall:updateRepo('%s'), then try again.", repo, repo) 342 | end 343 | else 344 | self.logger.ef("Invalid or unknown repository '%s'", repo) 345 | end 346 | return nil 347 | end 348 | 349 | --- SpoonInstall:asyncInstallSpoonFromRepo(name, repo, callback) 350 | --- Method 351 | --- Asynchronously install a Spoon from a registered repository 352 | --- 353 | --- Parameters: 354 | --- * name - Name of the Spoon to install. 355 | --- * repo - Name of the repository to use. Defaults to `"default"` 356 | --- * callback - if given, a function to call after the installation finishes (also if it fails). The function receives the following arguments: 357 | --- * urlparts - Result of calling `hs.http.urlParts` on the URL of the Spoon zip file 358 | --- * success - boolean indicating whether the installation was successful 359 | --- 360 | --- Returns: 361 | --- * `true` if the installation was correctly initiated (i.e. the repo and spoon name were correct), `false` otherwise. 362 | function obj:asyncInstallSpoonFromRepo(name, repo, callback) 363 | if not repo then repo = 'default' end 364 | if self:_is_valid_spoon(name, repo) then 365 | self:asyncInstallSpoonFromZipURL(self.repos[repo].data[name].download_url, callback) 366 | end 367 | return nil 368 | end 369 | 370 | --- SpoonInstall:installSpoonFromRepo(name, repo) 371 | --- Method 372 | --- Synchronously install a Spoon from a registered repository 373 | --- 374 | --- Parameters: 375 | --- * name = Name of the Spoon to install. 376 | --- * repo - Name of the repository to use. Defaults to `"default"` 377 | --- 378 | --- Returns: 379 | --- * `true` if the installation was successful, `nil` otherwise. 380 | function obj:installSpoonFromRepo(name, repo, callback) 381 | if not repo then repo = 'default' end 382 | if self:_is_valid_spoon(name, repo) then 383 | return self:installSpoonFromZipURL(self.repos[repo].data[name].download_url) 384 | end 385 | return nil 386 | end 387 | 388 | --- SpoonInstall:andUse(name, arg) 389 | --- Method 390 | --- Declaratively install, load and configure a Spoon 391 | --- 392 | --- Parameters: 393 | --- * name - the name of the Spoon to install (without the `.spoon` extension). If the Spoon is already installed, it will be loaded using `hs.loadSpoon()`. If it is not installed, it will be installed using `SpoonInstall:asyncInstallSpoonFromRepo()` and then loaded. 394 | --- * arg - if provided, can be used to specify the configuration of the Spoon. The following keys are recognized (all are optional): 395 | --- * repo - repository from where the Spoon should be installed if not present in the system, as defined in `SpoonInstall.repos`. Defaults to `"default"`. 396 | --- * config - a table containing variables to be stored in the Spoon object to configure it. For example, `config = { answer = 42 }` will result in `spoon..answer` being set to 42. 397 | --- * hotkeys - a table containing hotkey bindings. If provided, will be passed as-is to the Spoon's `bindHotkeys()` method. The special string `"default"` can be given to use the Spoons `defaultHotkeys` variable, if it exists. 398 | --- * fn - a function which will be called with the freshly-loaded Spoon object as its first argument. 399 | --- * loglevel - if the Spoon has a variable called `logger`, its `setLogLevel()` method will be called with this value. 400 | --- * start - if `true`, call the Spoon's `start()` method after configuring everything else. 401 | --- * disable - if `true`, do nothing. Easier than commenting it out when you want to temporarily disable a spoon. 402 | --- 403 | --- Returns: 404 | --- * None 405 | function obj:andUse(name, arg) 406 | if not arg then arg = {} end 407 | if arg.disable then return true end 408 | if hs.spoons.use(name, arg, true) then 409 | return true 410 | else 411 | local repo = arg.repo or "default" 412 | if self.repos[repo] then 413 | if self.repos[repo].data then 414 | local load_and_config = function(_, success) 415 | if success then 416 | hs.notify.show("Spoon installed by SpoonInstall", name .. ".spoon is now available", "") 417 | hs.spoons.use(name, arg) 418 | else 419 | obj.logger.ef("Error installing Spoon '%s' from repo '%s'", name, repo) 420 | end 421 | end 422 | if self.use_syncinstall then 423 | return load_and_config(nil, self:installSpoonFromRepo(name, repo)) 424 | else 425 | self:asyncInstallSpoonFromRepo(name, repo, load_and_config) 426 | end 427 | else 428 | local update_repo_and_continue = function(_, success) 429 | if success then 430 | obj:andUse(name, arg) 431 | else 432 | obj.logger.ef("Error updating repository '%s'", repo) 433 | end 434 | end 435 | if self.use_syncinstall then 436 | return update_repo_and_continue(nil, self:updateRepo(repo)) 437 | else 438 | self:asyncUpdateRepo(repo, update_repo_and_continue) 439 | end 440 | end 441 | else 442 | obj.logger.ef("Unknown repository '%s' for Spoon", repo, name) 443 | end 444 | end 445 | end 446 | 447 | return obj 448 | -------------------------------------------------------------------------------- /hammerspoon/crypto.lua: -------------------------------------------------------------------------------- 1 | local menubar = hs.menubar.new() 2 | local menuData = {} 3 | local urlApi = 4 | "https://api.coingecko.com/api/v3/simple/price?ids=solana,bitcoin,ethereum,helium,frax-share,filecoin,bitcoin&vs_currencies=usd" 5 | 6 | function getTokenPrices() 7 | menubar:setTitle("⌛") 8 | hs.http.doAsyncRequest(urlApi, "GET", nil, nil, function(code, body) 9 | if code ~= 200 then 10 | print("get token prices error:" .. code) 11 | return 12 | end 13 | 14 | menuData = {} 15 | 16 | rawjson = hs.json.decode(body) 17 | table.insert(menuData, { title = "BTC $" .. rawjson.bitcoin.usd }) 18 | table.insert(menuData, { title = "ETH $" .. rawjson.ethereum.usd }) 19 | table.insert(menuData, { title = "FXS $" .. rawjson["frax-share"]["usd"] }) 20 | table.insert(menuData, { title = "SOL $" .. rawjson.solana.usd }) 21 | table.insert(menuData, { title = "HNT $" .. rawjson.helium.usd }) 22 | table.insert(menuData, { title = "FIL $" .. rawjson.filecoin.usd }) 23 | table.insert(menuData, { 24 | title = "Refresh", 25 | fn = function() 26 | getTokenPrices() 27 | end, 28 | }) 29 | 30 | menubar:setTitle("💰") 31 | menubar:setMenu(menuData) 32 | end) 33 | end 34 | 35 | menubar:setTitle("⌛") 36 | menubar:setTooltip("Crypto Prices") 37 | getTokenPrices() 38 | hs.timer.doEvery(30, getTokenPrices) 39 | -------------------------------------------------------------------------------- /hammerspoon/grid.lua: -------------------------------------------------------------------------------- 1 | log = hs.logger.new("hs") 2 | 3 | Grid = {} 4 | Grid.BORDER = 1 5 | 6 | -- Describe the share of the screen that the L/R "chunks" take: 7 | Grid.L_CHUNK_SHARE = 0.5 8 | Grid.R_CHUNK_SHARE = 0.5 9 | 10 | Grid.PEEK_PIXELS = 70 11 | 12 | function Grid.fullscreen() 13 | local win = hs.window.focusedWindow() 14 | if not win then 15 | return 16 | end 17 | 18 | local screenframe = Grid.screenframe(win) 19 | win:setFrame(screenframe) 20 | end 21 | 22 | function Grid.topHalf() 23 | local win = hs.window.focusedWindow() 24 | if not win then 25 | return 26 | end 27 | 28 | local screenframe = Grid.screenframe(win) 29 | local newframe = { 30 | x = screenframe.x, 31 | y = screenframe.y, 32 | w = screenframe.w, 33 | h = screenframe.h / 2 - Grid.BORDER, 34 | } 35 | 36 | win:setFrame(newframe) 37 | end 38 | 39 | function Grid.bottomHalf() 40 | local win = hs.window.focusedWindow() 41 | if not win then 42 | return 43 | end 44 | 45 | local screenframe = Grid.screenframe(win) 46 | local newframe = { 47 | x = screenframe.x, 48 | y = screenframe.y + screenframe.h / 2 + Grid.BORDER, 49 | w = screenframe.w, 50 | h = screenframe.h / 2 - Grid.BORDER, 51 | } 52 | 53 | win:setFrame(newframe) 54 | end 55 | 56 | function Grid.topleft() 57 | local win = hs.window.focusedWindow() 58 | if not win then 59 | return 60 | end 61 | 62 | local screenframe = Grid.screenframe(win) 63 | local newframe = { 64 | x = screenframe.x, 65 | y = screenframe.y, 66 | w = screenframe.w * Grid.L_CHUNK_SHARE - Grid.BORDER, 67 | h = screenframe.h / 2 - Grid.BORDER, 68 | } 69 | 70 | win:setFrame(newframe) 71 | end 72 | 73 | function Grid.bottomleft() 74 | local win = hs.window.focusedWindow() 75 | if not win then 76 | return 77 | end 78 | 79 | local screenframe = Grid.screenframe(win) 80 | local newframe = { 81 | x = screenframe.x, 82 | y = screenframe.y + screenframe.h / 2 + Grid.BORDER, 83 | w = screenframe.w * Grid.L_CHUNK_SHARE - Grid.BORDER, 84 | h = screenframe.h / 2 - Grid.BORDER, 85 | } 86 | 87 | win:setFrame(newframe) 88 | end 89 | 90 | function Grid.topright() 91 | local win = hs.window.focusedWindow() 92 | if not win then 93 | return 94 | end 95 | 96 | local screenframe = Grid.screenframe(win) 97 | local newframe = { 98 | x = screenframe.x + screenframe.w * Grid.L_CHUNK_SHARE + Grid.BORDER, 99 | y = screenframe.y, 100 | w = screenframe.w * Grid.R_CHUNK_SHARE - Grid.BORDER, 101 | h = screenframe.h / 2 - Grid.BORDER, 102 | } 103 | 104 | win:setFrame(newframe) 105 | end 106 | 107 | function Grid.bottomright() 108 | local win = hs.window.focusedWindow() 109 | if not win then 110 | return 111 | end 112 | 113 | local screenframe = Grid.screenframe(win) 114 | local newframe = { 115 | x = screenframe.x + screenframe.w * Grid.L_CHUNK_SHARE + Grid.BORDER, 116 | y = screenframe.y + screenframe.h / 2 + Grid.BORDER, 117 | w = screenframe.w * Grid.R_CHUNK_SHARE - Grid.BORDER, 118 | h = screenframe.h / 2 - Grid.BORDER, 119 | } 120 | 121 | win:setFrame(newframe) 122 | end 123 | 124 | function Grid.pushwindow() 125 | local win = hs.window.focusedWindow() 126 | if not win then 127 | return 128 | end 129 | 130 | local winframe = win:frame() 131 | local nextscreen = win:screen():next() 132 | local screenframe = nextscreen:frame() 133 | local newframe = { 134 | x = screenframe.x, 135 | y = screenframe.y, 136 | w = math.min(winframe.w, screenframe.w), 137 | h = math.min(winframe.h, screenframe.h), 138 | } 139 | 140 | win:setFrame(newframe) 141 | end 142 | 143 | function Grid.screenframe(win) 144 | return win:screen():frame() 145 | end 146 | 147 | -- Customized versions of lefthalf() and righthalf() that make the left side slightly wider: 148 | function Grid.leftHalf() 149 | local win = hs.window.focusedWindow() 150 | if not win then 151 | return 152 | end 153 | 154 | local screenframe = Grid.screenframe(win) 155 | local newframe = { 156 | x = screenframe.x, 157 | y = screenframe.y, 158 | w = screenframe.w * Grid.L_CHUNK_SHARE - Grid.BORDER, 159 | h = screenframe.h, 160 | } 161 | 162 | win:setFrame(newframe) 163 | end 164 | 165 | function Grid.rightHalf() 166 | local win = hs.window.focusedWindow() 167 | if not win then 168 | return 169 | end 170 | 171 | local screenframe = Grid.screenframe(win) 172 | local newframe = { 173 | x = screenframe.x + screenframe.w * Grid.L_CHUNK_SHARE + Grid.BORDER, 174 | y = screenframe.y, 175 | w = screenframe.w * Grid.R_CHUNK_SHARE - Grid.BORDER, 176 | h = screenframe.h, 177 | } 178 | 179 | win:setFrame(newframe) 180 | end 181 | 182 | function Grid.rightpeek() 183 | local win = hs.window.focusedWindow() 184 | if not win then 185 | return 186 | end 187 | 188 | local screenframe = Grid.screenframe(win) 189 | local newframe = { 190 | x = screenframe.x + screenframe.w - Grid.PEEK_PIXELS, 191 | y = screenframe.y, 192 | w = screenframe.w * Grid.R_CHUNK_SHARE - Grid.BORDER, 193 | h = screenframe.h, 194 | } 195 | 196 | win:setFrame(newframe) 197 | end 198 | 199 | return Grid 200 | -------------------------------------------------------------------------------- /hammerspoon/init.lua: -------------------------------------------------------------------------------- 1 | hs.loadSpoon("SpoonInstall") 2 | 3 | local grid = require("hs.grid") 4 | local hotkey = require("hs.hotkey") 5 | local alert = require("hs.alert") 6 | 7 | require("wifi") 8 | require("session") 9 | -- require("menubar") 10 | -- require("crypto") 11 | 12 | alert("Hammerspoon is locked and loaded", 1) 13 | for i, screen in ipairs(hs.screen.allScreens()) do 14 | alert("Screen " .. i, {}, screen) 15 | end 16 | 17 | grid.setGrid("16x4") 18 | grid.setMargins("0x0") 19 | 20 | -- Disable window animations (janky for iTerm) 21 | hs.window.animationDuration = 0 22 | 23 | spoon.SpoonInstall.repos.ShiftIt = { 24 | url = "https://github.com/peterklijn/hammerspoon-shiftit", 25 | desc = "ShiftIt spoon repository", 26 | branch = "master", 27 | } 28 | 29 | spoon.SpoonInstall:andUse("ShiftIt", { repo = "ShiftIt" }) 30 | 31 | spoon.ShiftIt:bindHotkeys({ 32 | left = { { "ctrl", "alt", "cmd" }, "h" }, 33 | down = { { "ctrl", "alt", "cmd" }, "j" }, 34 | up = { { "ctrl", "alt", "cmd" }, "k" }, 35 | right = { { "ctrl", "alt", "cmd" }, "l" }, 36 | upleft = { { "ctrl", "alt", "cmd" }, "u" }, 37 | upright = { { "ctrl", "alt", "cmd" }, "i" }, 38 | botleft = { { "ctrl", "alt", "cmd" }, "n" }, 39 | botright = { { "ctrl", "alt", "cmd" }, "m" }, 40 | maximum = { { "ctrl", "alt", "cmd" }, "o" }, 41 | }) 42 | 43 | local mashGeneral = { 44 | "cmd", 45 | "ctrl", 46 | } 47 | 48 | local screenKeys = { 49 | "cmd", 50 | "ctrl", 51 | "shift", 52 | } 53 | 54 | -- Pushes windows to different screens 55 | for screenIndex = 1, 3 do 56 | hotkey.bind(screenKeys, tostring(screenIndex), function() 57 | local win = hs.window.focusedWindow() 58 | local screen = hs.screen.allScreens()[screenIndex] 59 | 60 | win:moveToScreen(screen) 61 | win:setFrame(screen:frame()) 62 | end) 63 | end 64 | 65 | -- lists each screen index (Screen 1, 2, 3, etc) 66 | hotkey.bind(screenKeys, "S", function() 67 | for i, screen in ipairs(hs.screen.allScreens()) do 68 | alert("Screen " .. i, {}, screen) 69 | end 70 | end) 71 | 72 | -- Hammerspoon rep 73 | hotkey.bind(mashGeneral, "C", hs.openConsole) 74 | 75 | -- Easy config reloading 76 | reloader = hs.pathwatcher.new(os.getenv("HOME") .. "/.hammerspoon/", function(files) 77 | doReload = false 78 | for _, file in pairs(files) do 79 | if file:sub(-4) == ".lua" then 80 | doReload = true 81 | end 82 | end 83 | if doReload then 84 | hs.reload() 85 | end 86 | end) 87 | reloader:start() 88 | -------------------------------------------------------------------------------- /hammerspoon/menubar.lua: -------------------------------------------------------------------------------- 1 | -- luacheck: globals hs 2 | 3 | 4 | 5 | local turnOnUrl = baseUrl .. "/" .. turnOnSpeakersWebhook 6 | local turnOffUrl = baseUrl .. "/" .. turnOffSpeakersWebhook 7 | 8 | local headers = { ["Content-Type"] = "application/json" } 9 | 10 | local function postToHomeAssistant(url) 11 | local payload = url == turnOnUrl and [[ { "speakers": "switch.turn_on" }]] or [[ { "speakers": "switch.turn_off" }]] 12 | hs.http.post(url, payload, headers) 13 | end 14 | 15 | local function toggleSpeakers(action) 16 | if action == "on" then 17 | postToHomeAssistant(turnOnUrl) 18 | elseif action == "off" then 19 | postToHomeAssistant(turnOffUrl) 20 | else 21 | print("Invalid action for toggleSpeakers") 22 | end 23 | end 24 | 25 | local function toggleBacklight(mode) 26 | if mode == "on" then 27 | local payload = [[ { "speakers": "switch.turn_on" }]] 28 | local url = baseUrl .. "/" .. backlightWebhook 29 | hs.http.post(url, payload, headers) 30 | end 31 | end 32 | 33 | -- Create menubar 34 | local menu = hs.menubar.new() 35 | local menuItems = { 36 | { 37 | title = "Turn ON Speakers", 38 | fn = function() 39 | toggleSpeakers("on") 40 | end, 41 | }, 42 | { 43 | title = "Turn OFF Speakers", 44 | fn = function() 45 | toggleSpeakers("off") 46 | end, 47 | }, 48 | { 49 | title = "Turn ON Backlight", 50 | fn = function() 51 | toggleBacklight("on") 52 | end, 53 | }, 54 | -- { 55 | -- title = "Default Backlight", 56 | -- fn = function() 57 | -- toggleBacklight("default") 58 | -- end, 59 | -- }, 60 | -- { 61 | -- title = "Gamer Backlight", 62 | -- fn = function() 63 | -- toggleBacklight("gamer") 64 | -- end, 65 | -- }, 66 | -- { 67 | -- title = "Turn OFF Backlight", 68 | -- fn = function() 69 | -- toggleBacklight("") 70 | -- end, 71 | -- }, 72 | } 73 | 74 | -- Set menu items 75 | menu:setTitle("🎮") 76 | menu:setMenu(menuItems) 77 | -------------------------------------------------------------------------------- /hammerspoon/pomodoro.lua: -------------------------------------------------------------------------------- 1 | -- WIP pomodoro menubar timer 2 | -- 3 | -- Notes: 4 | -- * You want to set your Hammerspoon notification settings to Alert 5 | -- if you wish to make the notifications dismissable 6 | -- 7 | -- Resources: 8 | -- https://learnxinyminutes.com/docs/lua/ 9 | -- http://www.hammerspoon.org/docs/hs.menubar.html 10 | -- http://www.hammerspoon.org/docs/hs.timer.html#doEvery 11 | 12 | local menu = hs.menubar.new() 13 | local timer = nil 14 | local currentPomo = nil 15 | local alertId = nil 16 | 17 | local INTERVAL_SECONDS = 60 -- Set to 60 (one minute) for real use; set lower for debugging 18 | local POMO_LENGTH = 25 -- Number of intervals (minutes) in one work pomodoro 19 | local BREAK_LENGTH = 5 -- Number of intervals (minutes) in one break time 20 | local LOG_FILE = "~/.pomo" 21 | 22 | -- Namespace tables 23 | local Commands = {} 24 | local Log = {} 25 | local App = {} 26 | 27 | -- (ab)use hs.chooser as a text input with the possibility of using other options 28 | local showChooserPrompt = function(items, callback) 29 | local chooser = nil 30 | chooser = hs.chooser.new(function(item) 31 | if item then 32 | callback(item.text) 33 | end 34 | if chooser then 35 | chooser:delete() 36 | end 37 | end) 38 | 39 | -- The table of choices to present to the user. It's comprised of one empty 40 | -- item (which we update as the user types), and those passed in as items 41 | local choiceList = { { text = "" } } 42 | for i = 1, #items do 43 | choiceList[#choiceList + 1] = items[i] 44 | end 45 | 46 | chooser:choices(function() 47 | choiceList[1]["text"] = chooser:query() 48 | return choiceList 49 | end) 50 | 51 | -- Re-compute the choices every time a key is pressed, to ensure that the top 52 | -- choice is always the entered text: 53 | chooser:queryChangedCallback(function() 54 | chooser:refreshChoicesCallback() 55 | end) 56 | 57 | chooser:show() 58 | end 59 | 60 | -- Read the last {count} lines of the log file, ordered with the most recent one first 61 | Log.read = function(count) 62 | if not count then 63 | count = 10 64 | end 65 | -- Note the funky sed command at the end is to reverse the ordering of the lines: 66 | return hs.execute("tail -" .. count .. " " .. LOG_FILE .. " | sed '1!G;h;$!d' ${inputfile}") 67 | end 68 | 69 | Log.writeItem = function(pomo) 70 | local timestamp = os.date("%Y-%m-%d %H:%M") 71 | local isFirstToday = #(Log.getCompletedToday()) == 0 72 | 73 | if isFirstToday then 74 | hs.execute('echo "" >> ' .. LOG_FILE) 75 | end -- Add linebreak between days 76 | hs.execute('echo "[' .. timestamp .. "] " .. pomo.name .. '" >> ' .. LOG_FILE) 77 | end 78 | 79 | Log.getLatestItems = function(count) 80 | local logs = Log.read(count) 81 | local logItems = {} 82 | for match in logs:gmatch("(.-)\r?\n") do 83 | table.insert(logItems, match) 84 | end 85 | return logItems 86 | end 87 | 88 | Log.getCompletedToday = function() 89 | local logItems = Log.getLatestItems(20) 90 | local timestamp = os.date("%Y-%m-%d") 91 | local todayItems = hs.fnutils.filter(logItems, function(s) 92 | return string.find(s, timestamp, 1, true) ~= nil 93 | end) 94 | return todayItems 95 | end 96 | 97 | -- Return a table of recent tasks ({text, subText}), most recent first 98 | Log.getRecentTaskNames = function() 99 | local tasks = Log.getLatestItems(12) 100 | local nonEmptyTasks = hs.fnutils.filter(tasks, function(t) 101 | return t ~= "" 102 | end) 103 | local names = hs.fnutils.map(nonEmptyTasks, function(taskWithTimestamp) 104 | local timeStampEnd = string.find(taskWithTimestamp, "]") 105 | return { 106 | text = string.sub(taskWithTimestamp, timeStampEnd + 2), 107 | subText = string.sub(taskWithTimestamp, 2, timeStampEnd - 1), -- slice braces off 108 | } 109 | end) 110 | 111 | -- TODO: dedupe these items before returning 112 | return names 113 | end 114 | 115 | Commands.startNew = function() 116 | local options = Log.getRecentTaskNames() 117 | showChooserPrompt(options, function(taskName) 118 | if taskName then 119 | currentPomo = { minutesLeft = POMO_LENGTH, name = taskName } 120 | if timer then 121 | timer:stop() 122 | end 123 | timer = hs.timer.doEvery(INTERVAL_SECONDS, App.timerCallback) 124 | end 125 | App.updateUI() 126 | end) 127 | end 128 | 129 | Commands.togglePaused = function() 130 | if not currentPomo then 131 | return 132 | end 133 | currentPomo.paused = not currentPomo.paused 134 | App.updateUI() 135 | end 136 | 137 | Commands.toggleLatestDisplay = function() 138 | local logs = Log.read(30) 139 | if alertId then 140 | hs.alert.closeSpecific(alertId) 141 | alertId = nil 142 | else 143 | local msg = "LATEST ACTIVITY\n\n" .. logs 144 | if currentPomo then 145 | msg = "NOW: " .. currentPomo.name .. "\n==========\n\n" .. msg 146 | end 147 | alertId = hs.alert(msg, { textSize = 17, textFont = "Courier" }, "indefinite") 148 | end 149 | end 150 | 151 | App.timerCallback = function() 152 | if not currentPomo then 153 | return 154 | end 155 | if currentPomo.paused then 156 | return 157 | end 158 | currentPomo.minutesLeft = currentPomo.minutesLeft - 1 159 | if currentPomo.minutesLeft <= 0 then 160 | App.completePomo(currentPomo) 161 | end 162 | App.updateUI() 163 | end 164 | 165 | App.completePomo = function(pomo) 166 | local n = hs.notify.new({ 167 | title = "Pomodoro complete", 168 | subTitle = pomo.name, 169 | informativeText = "Completed at " .. os.date("%H:%M"), 170 | soundName = "Hero", 171 | }) 172 | n:autoWithdraw(false) 173 | n:hasActionButton(false) 174 | n:send() 175 | 176 | Log.writeItem(pomo) 177 | currentPomo = nil 178 | 179 | if timer then 180 | timer:stop() 181 | end 182 | timer = hs.timer.doAfter(INTERVAL_SECONDS * BREAK_LENGTH, function() 183 | local n2 = hs.notify.new({ 184 | title = "Get back to work", 185 | subTitle = "Break time is over", 186 | informativeText = "Sent at " .. os.date("%H:%M"), 187 | soundName = "Hero", 188 | }) 189 | n2:autoWithdraw(false) 190 | n2:hasActionButton(false) 191 | n2:send() 192 | end) 193 | end 194 | 195 | App.getMenubarTitle = function(pomo) 196 | local title = "🍅" 197 | if pomo then 198 | title = title .. ("0:" .. string.format("%02d", pomo.minutesLeft)) 199 | if pomo.paused then 200 | title = title .. " (paused)" 201 | end 202 | end 203 | return title 204 | end 205 | 206 | App.updateUI = function() 207 | menu:setTitle(App.getMenubarTitle(currentPomo)) 208 | end 209 | 210 | App.init = function() 211 | menu:setMenu(function() 212 | local completedCount = #(Log.getCompletedToday()) 213 | return { 214 | -- TODO: make these menu items contextual: 215 | { title = completedCount .. " pomos completed today", disabled = true }, 216 | { title = "Start", fn = Commands.startNew }, 217 | { title = "Pause", fn = Commands.togglePaused }, 218 | } 219 | end) 220 | 221 | App.updateUI() 222 | end 223 | 224 | App.init() 225 | 226 | return Commands 227 | -------------------------------------------------------------------------------- /hammerspoon/session.lua: -------------------------------------------------------------------------------- 1 | -- luacheck: globals hs 2 | local wifi = require("wifi") 3 | 4 | local headers = { ["Content-Type"] = "application/json" } 5 | 6 | local function isTargetDeviceConnected() 7 | for _, device in ipairs(hs.usb.attachedDevices()) do 8 | if device.productName == targetDeviceName then 9 | return true 10 | end 11 | end 12 | return false 13 | end 14 | 15 | local function postToHomeAssistant(url) 16 | local payload = url == turnOnUrl and [[ { "speakers": "switch.turn_on" }]] or [[ { "speakers": "switch.turn_off" }]] 17 | hs.http.post(url, payload, headers) 18 | end 19 | 20 | local function handleSessionEvent(eventType) 21 | if wifi.homeSSID == wifi.lastSSID then 22 | if eventType == hs.caffeinate.watcher.screensDidUnlock and isTargetDeviceConnected() then 23 | postToHomeAssistant(turnOnUrl) 24 | elseif 25 | eventType == hs.caffeinate.watcher.screensDidLock 26 | or eventType == hs.caffeinate.watcher.screensDidSleep 27 | or eventType == hs.caffeinate.watcher.screensDidPowerOff 28 | then 29 | postToHomeAssistant(turnOffUrl) 30 | end 31 | end 32 | end 33 | 34 | local sessionWatcher = hs.caffeinate.watcher.new(function(eventType) 35 | hs.timer.doAfter(5, function() 36 | handleSessionEvent(eventType) 37 | end) 38 | end) 39 | 40 | sessionWatcher:start() 41 | return { sessionWatcher = sessionWatcher } 42 | -------------------------------------------------------------------------------- /hammerspoon/weather.lua: -------------------------------------------------------------------------------- 1 | local urlApi = "https://www.tianqiapi.com/api/?version=v1" 2 | local menubar = hs.menubar.new() 3 | local menuData = {} 4 | 5 | local weaEmoji = { 6 | lei = "⛈", 7 | qing = "☀️", 8 | shachen = "😷", 9 | wu = "🌫", 10 | xue = "❄️", 11 | yu = "🌧", 12 | yujiaxue = "🌨", 13 | yun = "☁️", 14 | zhenyu = "🌧", 15 | yin = "⛅️", 16 | default = "", 17 | } 18 | 19 | function updateMenubar() 20 | menubar:setTooltip("Weather Info") 21 | menubar:setMenu(menuData) 22 | end 23 | 24 | function getWeather() 25 | hs.http.doAsyncRequest(urlApi, "GET", nil, nil, function(code, body, htable) 26 | if code ~= 200 then 27 | print("get weather error:" .. code) 28 | return 29 | end 30 | rawjson = hs.json.decode(body) 31 | city = rawjson.city 32 | menuData = {} 33 | for k, v in pairs(rawjson.data) do 34 | if k == 1 then 35 | menubar:setTitle(weaEmoji[v.wea_img]) 36 | titlestr = string.format( 37 | "%s %s %s 🌡️%s 💧%s 💨%s 🌬 %s %s", 38 | city, 39 | weaEmoji[v.wea_img], 40 | v.day, 41 | v.tem, 42 | v.humidity, 43 | v.air, 44 | v.win_speed, 45 | v.wea 46 | ) 47 | item = { title = titlestr } 48 | table.insert(menuData, item) 49 | table.insert(menuData, { title = "-" }) 50 | else 51 | -- titlestr = string.format("%s %s %s %s", v.day, v.wea, v.tem, v.win_speed) 52 | titlestr = string.format( 53 | "%s %s %s 🌡️%s 🌬%s %s", 54 | city, 55 | weaEmoji[v.wea_img], 56 | v.day, 57 | v.tem, 58 | v.win_speed, 59 | v.wea 60 | ) 61 | item = { title = titlestr } 62 | table.insert(menuData, item) 63 | end 64 | end 65 | updateMenubar() 66 | end) 67 | end 68 | 69 | menubar:setTitle("⌛") 70 | getWeather() 71 | updateMenubar() 72 | hs.timer.doEvery(180, getWeather) 73 | -------------------------------------------------------------------------------- /hammerspoon/wifi.lua: -------------------------------------------------------------------------------- 1 | wifi = {} 2 | 3 | wifi.homeSSID = "NETFLIX & CHILL" --git-ignore 4 | wifi.lastSSID = nil -- Initialize as nil and let the watcher update it 5 | 6 | function handleWifiNetwork() 7 | local newSSID = hs.wifi.currentNetwork() 8 | -- hs.alert.show("Current SSID: " .. (newSSID or "None")) 9 | 10 | if newSSID == wifi.homeSSID and wifi.lastSSID ~= wifi.homeSSID then 11 | -- We just joined our home WiFi network 12 | hs.audiodevice.defaultOutputDevice():setVolume(25) 13 | elseif newSSID ~= wifi.homeSSID and wifi.lastSSID == wifi.homeSSID then 14 | -- We just departed our home WiFi network 15 | hs.audiodevice.defaultOutputDevice():setVolume(0) 16 | hs.alert.show("Disconnected from Home Network") 17 | end 18 | 19 | wifi.lastSSID = newSSID 20 | end 21 | 22 | wifi.wifiWatcher = hs.wifi.watcher.new(handleWifiNetwork) 23 | wifi.wifiWatcher:start() 24 | 25 | -- Optionally, call handleWifiNetwork() to set the initial state 26 | handleWifiNetwork() 27 | 28 | return wifi 29 | -------------------------------------------------------------------------------- /hosts-allowlist: -------------------------------------------------------------------------------- 1 | # Domains added below will be ignored, For example: 2 | # your-domain-name.com 3 | app.segment.com 4 | app.segment.io 5 | amplitude.com 6 | fullstory.com 7 | sentry.io 8 | newrelic.com 9 | mask.icloud.com 10 | mask-h2.icloud.com 11 | captive.apple.com 12 | click.redditmail.com 13 | redditmail.com 14 | dashboard-03.braze.com 15 | braze.com 16 | trk.s.sephora.com 17 | email.cycle-em.io 18 | -------------------------------------------------------------------------------- /hosts-denylist: -------------------------------------------------------------------------------- 1 | # blacklist 2 | # 3 | # The contents of this file (containing a listing of additional domains in 4 | # 'hosts' file format) are appended to the unified hosts file during the 5 | # update process. For example, uncomment the following line to block 6 | # 'example.com': 7 | 8 | # 0.0.0.0 example.com 9 | # 10 | # 0.0.0.0 instagram.com 11 | # 0.0.0.0 facebook.com 12 | 0.0.0.0 news.ycombinator.com 13 | # 0.0.0.0 my.nextdns.io 14 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DOTFILES="$(pwd)" 4 | COLOR_GRAY="\033[1;38;5;243m" 5 | COLOR_BLUE="\033[1;34m" 6 | COLOR_GREEN="\033[1;32m" 7 | COLOR_RED="\033[1;31m" 8 | COLOR_PURPLE="\033[1;35m" 9 | COLOR_YELLOW="\033[1;33m" 10 | COLOR_NONE="\033[0m" 11 | 12 | title() { 13 | echo -e "\n${COLOR_PURPLE}$1${COLOR_NONE}" 14 | echo -e "${COLOR_GRAY}==============================${COLOR_NONE}\n" 15 | } 16 | 17 | error() { 18 | echo -e "${COLOR_RED}Error: ${COLOR_NONE}$1" 19 | exit 1 20 | } 21 | 22 | warning() { 23 | echo -e "${COLOR_YELLOW}Warning: ${COLOR_NONE}$1" 24 | } 25 | 26 | info() { 27 | echo -e "${COLOR_BLUE}Info: ${COLOR_NONE}$1" 28 | } 29 | 30 | success() { 31 | echo -e "${COLOR_GREEN}$1${COLOR_NONE}" 32 | } 33 | 34 | get_linkables() { 35 | find -H "$DOTFILES" -maxdepth 3 -name '*.symlink' 36 | } 37 | 38 | backup() { 39 | BACKUP_DIR=$HOME/dotfiles-backup 40 | 41 | echo "Creating backup directory at $BACKUP_DIR" 42 | mkdir -p "$BACKUP_DIR" 43 | 44 | for file in $(get_linkables); do 45 | filename=".$(basename "$file" '.symlink')" 46 | target="$HOME/$filename" 47 | if [ -f "$target" ]; then 48 | echo "backing up $filename" 49 | cp "$target" "$BACKUP_DIR" 50 | else 51 | warning "$filename does not exist at this location or is a symlink" 52 | fi 53 | done 54 | 55 | for filename in "$HOME/.config/nvim" "$HOME/.vim" "$HOME/.vimrc"; do 56 | if [ ! -L "$filename" ]; then 57 | echo "backing up $filename" 58 | cp -rf "$filename" "$BACKUP_DIR" 59 | else 60 | warning "$filename does not exist at this location or is a symlink" 61 | fi 62 | done 63 | } 64 | 65 | setup_symlinks() { 66 | title "Creating symlinks" 67 | 68 | for file in $(get_linkables) ; do 69 | target="$HOME/.$(basename "$file" '.symlink')" 70 | if [ -e "$target" ]; then 71 | info "~${target#$HOME} already exists... Skipping." 72 | else 73 | info "Creating symlink for $file" 74 | ln -s "$file" "$target" 75 | fi 76 | done 77 | 78 | echo -e 79 | info "installing to ~/.config" 80 | if [ ! -d "$HOME/.config" ]; then 81 | info "Creating ~/.config" 82 | mkdir -p "$HOME/.config" 83 | fi 84 | 85 | config_files=$(find "$DOTFILES/config" -maxdepth 1 2>/dev/null) 86 | for config in $config_files; do 87 | target="$HOME/.config/$(basename "$config")" 88 | if [ -e "$target" ]; then 89 | info "~${target#$HOME} already exists... Skipping." 90 | else 91 | info "Creating symlink for $config" 92 | ln -s "$config" "$target" 93 | fi 94 | done 95 | 96 | info "Symlinking hammerspoon" 97 | if [ ! -d "$HOME/.hammerspoon" ]; then 98 | ln -s "$DOTFILES/hammerspoon" "$HOME/.hammerspoon" 99 | else 100 | info "~/.hammerspoon already exists... Skipping." 101 | fi 102 | 103 | info "Symlinking nvchad custom config" 104 | mkdir -p ~/.config/nvim/lua 105 | if [ ! -d "$HOME/.config/nvim/lua/custom" ]; then 106 | ln -s "$DOTFILES/nvim-custom" "$HOME/.config/nvim/lua/custom" 107 | else 108 | info "~/.config/nvim/lua/custom already exists... Skipping." 109 | fi 110 | } 111 | 112 | setup_git() { 113 | title "Setting up Git" 114 | 115 | defaultName=$(git config user.name) 116 | defaultEmail=$(git config user.email) 117 | defaultGithub=$(git config github.user) 118 | 119 | read -rp "Name [$defaultName] " name 120 | read -rp "Email [$defaultEmail] " email 121 | read -rp "Github username [$defaultGithub] " github 122 | 123 | git config -f ~/.gitconfig-local user.name "${name:-$defaultName}" 124 | git config -f ~/.gitconfig-local user.email "${email:-$defaultEmail}" 125 | git config -f ~/.gitconfig-local github.user "${github:-$defaultGithub}" 126 | 127 | if [[ "$(uname)" == "Darwin" ]]; then 128 | git config --global credential.helper "osxkeychain" 129 | else 130 | read -rn 1 -p "Save user and password to an unencrypted file to avoid writing? [y/N] " save 131 | if [[ $save =~ ^([Yy])$ ]]; then 132 | git config --global credential.helper "store" 133 | else 134 | git config --global credential.helper "cache --timeout 3600" 135 | fi 136 | fi 137 | } 138 | 139 | setup_homebrew() { 140 | title "Setting up Homebrew" 141 | 142 | if test ! "$(command -v brew)"; then 143 | info "Homebrew not installed. Installing." 144 | # Run as a login shell (non-interactive) so that the script doesn't pause for user input 145 | curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh | bash --login 146 | fi 147 | 148 | if [ "$(uname)" == "Linux" ]; then 149 | test -d ~/.linuxbrew && eval "$(~/.linuxbrew/bin/brew shellenv)" 150 | test -d /home/linuxbrew/.linuxbrew && eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 151 | test -r ~/.bash_profile && echo "eval \$($(brew --prefix)/bin/brew shellenv)" >>~/.bash_profile 152 | fi 153 | 154 | # install brew dependencies from Brewfile 155 | brew bundle 156 | brew bundle cleanup 157 | 158 | # install fzf 159 | echo -e 160 | info "Installing fzf" 161 | "$(brew --prefix)"/opt/fzf/install --key-bindings --completion --no-update-rc --no-bash --no-fish 162 | } 163 | 164 | setup_shell() { 165 | title "Configuring shell" 166 | 167 | [[ -n "$(command -v brew)" ]] && zsh_path="$(brew --prefix)/bin/zsh" || zsh_path="$(which zsh)" 168 | if ! grep "$zsh_path" /etc/shells; then 169 | info "adding $zsh_path to /etc/shells" 170 | echo "$zsh_path" | sudo tee -a /etc/shells 171 | fi 172 | 173 | if [[ "$SHELL" != "$zsh_path" ]]; then 174 | chsh -s "$zsh_path" 175 | info "default shell changed to $zsh_path" 176 | fi 177 | } 178 | 179 | function setup_terminfo() { 180 | title "Configuring terminfo" 181 | 182 | info "adding tmux.terminfo" 183 | tic -x "$DOTFILES/resources/tmux.terminfo" 184 | 185 | info "adding xterm-256color-italic.terminfo" 186 | tic -x "$DOTFILES/resources/xterm-256color-italic.terminfo" 187 | } 188 | 189 | setup_zsh_prezto() { 190 | source prezto_setup.zsh 191 | } 192 | 193 | setup_hosts_file() { 194 | info 'Set up hosts file' 195 | git clone git@github.com:StevenBlack/hosts.git --depth 1 $DOTFILES/hosts 196 | cd $DOTFILES/hosts 197 | pip3 install --user -r requirements.txt 198 | cp $DOTFILES/hosts-allowlist $DOTFILES/hosts/whitelist 199 | cp $DOTFILES/hosts-denylist $DOTFILES/hosts/blacklist 200 | python3 updateHostsFile.py -a -f -r -e gambling fakenews porn -w whitelist -x blacklist 201 | } 202 | 203 | setup_misc() { 204 | info 'Install tmux plugin manager' 205 | git clone https://github.com/tmux-plugins/tpm --depth 1 ~/.tmux/plugins/tpm 206 | 207 | info "Create .ssh/control file for multiplexing..." 208 | mkdir -p ~/.ssh/control 209 | 210 | info "Installing Node.js LTS via fnm..." 211 | fnm install --lts 212 | 213 | info "Installing neovim python libraries..." 214 | python3 -m pip install --user --upgrade pynvim 215 | 216 | info 'Installing Yarn...' 217 | curl -o- -L https://yarnpkg.com/install.sh | bash 218 | 219 | echo 'Installing rust...' 220 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 221 | 222 | echo 'Configuring dock...' 223 | source scripts/dock.sh 224 | 225 | } 226 | 227 | setup_macos() { 228 | title "Configuring macOS" 229 | if [[ "$(uname)" == "Darwin" ]]; then 230 | source scripts/osx.sh 231 | else 232 | warning "macOS not detected. Skipping." 233 | fi 234 | } 235 | 236 | # Only runs if you pass in parameters. Won't run everything by default unless you pass in: `./install.sh all` 237 | case "$1" in 238 | backup) 239 | backup 240 | ;; 241 | link) 242 | setup_symlinks 243 | ;; 244 | git) 245 | setup_git 246 | ;; 247 | homebrew) 248 | setup_homebrew 249 | ;; 250 | shell) 251 | setup_shell 252 | ;; 253 | terminfo) 254 | setup_terminfo 255 | ;; 256 | hosts) 257 | setup_hosts_file 258 | ;; 259 | misc) 260 | setup_misc 261 | ;; 262 | macos) 263 | setup_macos 264 | ;; 265 | all) 266 | setup_symlinks 267 | setup_terminfo 268 | setup_homebrew 269 | setup_shell 270 | setup_git 271 | setup_terminfo 272 | setup_hosts_file 273 | setup_misc 274 | setup_macos 275 | ;; 276 | *) 277 | echo -e $"\nUsage: $(basename "$0") {backup|link|git|homebrew|shell|terminfo|macos|all}\n" 278 | exit 1 279 | ;; 280 | esac 281 | 282 | echo -e 283 | success "Done." 284 | -------------------------------------------------------------------------------- /npmbin/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /npmbin/.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/** linguist-vendored 2 | /.yarn/releases/* binary 3 | /.yarn/plugins/**/* binary 4 | /.pnp.* binary linguist-generated 5 | -------------------------------------------------------------------------------- /npmbin/.gitignore: -------------------------------------------------------------------------------- 1 | .yarn/* 2 | !.yarn/patches 3 | !.yarn/plugins 4 | !.yarn/releases 5 | !.yarn/sdks 6 | !.yarn/versions 7 | 8 | # Swap the comments on the following lines if you don't wish to use zero-installs 9 | # Documentation here: https://yarnpkg.com/features/zero-installs 10 | !.yarn/cache 11 | #.pnp.* 12 | -------------------------------------------------------------------------------- /npmbin/.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /npmbin/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | plugins: 4 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 5 | spec: "@yarnpkg/plugin-interactive-tools" 6 | 7 | yarnPath: .yarn/releases/yarn-3.5.0.cjs 8 | -------------------------------------------------------------------------------- /npmbin/README.md: -------------------------------------------------------------------------------- 1 | # npmbin 2 | -------------------------------------------------------------------------------- /npmbin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "global npm bin", 3 | "private": true, 4 | "license": "MIT", 5 | "postinstall": "yarn-deduplicate yarn.lock", 6 | "dependencies": { 7 | "@expo/ngrok": "^4.1.3", 8 | "@fsouza/prettierd": "^0.25.2", 9 | "@githubnext/github-copilot-cli": "^0.1.36", 10 | "@tailwindcss/language-server": "^0.0.16", 11 | "bash-language-server": "^5.1.2", 12 | "eas-cli": "^10.2.1", 13 | "eslint_d": "^13.1.2", 14 | "gatsby-cli": "^5.13.2", 15 | "git-delete-squashed": "^1.0.4", 16 | "jscodeshift": "^0.15.1", 17 | "jshint": "^2.13.6", 18 | "jsonlint": "^1.6.3", 19 | "neovim": "^4.10.1", 20 | "netlify-cli": "^17.15.3", 21 | "react-devtools": "^5.3.1", 22 | "sharp-cli": "^4.2.0", 23 | "stylelint-lsp": "^2.0.0", 24 | "tmex": "^1.0.11", 25 | "typescript": "^5.3.3", 26 | "typescript-language-server": "^4.3.1", 27 | "vercel": "^33.5.3", 28 | "vscode-css-languageserver-bin": "^1.4.0", 29 | "vscode-html-languageserver-bin": "^1.4.0", 30 | "vscode-json-languageserver-bin": "^1.0.1", 31 | "vscode-langservers-extracted": "^4.8.0", 32 | "yaml-language-server": "1.14.0" 33 | }, 34 | "devDependencies": { 35 | "yarn-deduplicate": "^6.0.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /nvim-custom/chadrc.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- path for lazy.nvim 4 | M.plugins = "custom.plugins" 5 | 6 | -- Path to overriding theme and highlights files 7 | local highlights = require("custom.highlights") 8 | 9 | M.ui = { 10 | theme = "chadracula", 11 | transparency = false, 12 | hl_override = highlights.override, 13 | hl_add = highlights.add, 14 | nvdash = { 15 | load_on_startup = true, 16 | header = { 17 | " █████ █████ █████ ", 18 | "░░███ ░░███ ░░███ ", 19 | " ░███████ ██████ ██████ ░███ █████ ████████ ██████ ██████ ░███ █████", 20 | " ░███░░███ ░░░░░███ ███░░███ ░███░░███ ░░███░░███ ░░░░░███ ███░░███ ░███░░███ ", 21 | " ░███ ░███ ███████ ░███ ░░░ ░██████░ ░███ ░███ ███████ ░███ ░░░ ░██████░ ", 22 | " ░███ ░███ ███░░███ ░███ ███ ░███░░███ ░███ ░███ ███░░███ ░███ ███ ░███░░███ ", 23 | " ████████ ░░████████░░██████ ████ █████ ░███████ ░░████████░░██████ ████ █████", 24 | "░░░░░░░░ ░░░░░░░░ ░░░░░░ ░░░░ ░░░░░ ░███░░░ ░░░░░░░░ ░░░░░░ ░░░░ ░░░░░ ", 25 | " ░███ ", 26 | " █████ ", 27 | " ░░░░░", 28 | }, 29 | }, 30 | } 31 | 32 | M.mappings = require("custom.mappings") 33 | 34 | return M 35 | 36 | -- :MasonInstallAll 37 | -------------------------------------------------------------------------------- /nvim-custom/configs/conform.lua: -------------------------------------------------------------------------------- 1 | local formatters = { 2 | -- Initial setup for specific formatters 3 | -- css = { "stylelint", "prettierd" }, 4 | sh = { "shellcheck", "shfmt" }, 5 | lua = { "stylua" }, 6 | } 7 | 8 | -- List of file types that will use "prettier" as their formatter 9 | local prettierFileTypes = { 10 | "css", 11 | "javascript", 12 | "javascriptreact", 13 | "typescript", 14 | "typescriptreact", 15 | "json", 16 | "jsonc", 17 | "html", 18 | "yaml", 19 | } 20 | 21 | for _, fileType in ipairs(prettierFileTypes) do 22 | formatters[fileType] = { "prettierd" } 23 | end 24 | 25 | local options = { 26 | lsp_fallback = true, 27 | 28 | formatters = { 29 | prettier = { 30 | require_cwd = true, 31 | }, 32 | }, 33 | 34 | formatters_by_ft = formatters, 35 | 36 | format_on_save = { 37 | -- These options will be passed to conform.format() 38 | timeout_ms = 300, 39 | lsp_fallback = true, 40 | }, 41 | } 42 | 43 | require("conform").setup(options) 44 | -------------------------------------------------------------------------------- /nvim-custom/configs/copilot.lua: -------------------------------------------------------------------------------- 1 | 2 | local get_nvm_node_dir = function (path) 3 | local entries = {} 4 | local handle = vim.loop.fs_scandir(path) 5 | 6 | if type(handle) == 'userdata' then 7 | local function iterator() 8 | return vim.loop.fs_scandir_next(handle) 9 | end 10 | 11 | for name in iterator do 12 | local absolute_path = path .. '/' .. name 13 | local relative_path = vim.fn.fnamemodify(absolute_path, ':.') 14 | local version_match = relative_path:match('v16.*') 15 | if version_match ~= nil then 16 | table.insert(entries, absolute_path) 17 | end 18 | end 19 | table.sort(entries) 20 | end 21 | return entries[#entries] 22 | end 23 | 24 | local node_fallback = function () 25 | local node = vim.fn.exepath("node") 26 | 27 | if not node then 28 | print('Node not found in path') 29 | return 30 | end 31 | return node 32 | end 33 | 34 | local resolve_node_cmd = function () 35 | local nvm_dir = vim.fn.expand('$NVM_DIR') 36 | 37 | if nvm_dir == "$NVM_DIR" then 38 | return node_fallback() 39 | end 40 | 41 | nvm_dir = nvm_dir .. "/versions/node" 42 | local node = get_nvm_node_dir(nvm_dir) 43 | if not node then 44 | return node_fallback() 45 | end 46 | node = node .. "/bin/node" 47 | return node 48 | end 49 | 50 | 51 | -- https://github.com/zbirenbaum/copilot.lua 52 | require("copilot").setup({ 53 | suggestion = { 54 | auto_trigger = true, 55 | }, 56 | ft_disable = { "go", "dap-repl" }, 57 | -- i don't think we need either of these tbh 58 | -- copilot_node_command = "~/.fnm/node-versions/v16.18.1/installation/bin/node" 59 | -- copilot_node_command = resolve_node_cmd(), 60 | }) 61 | -------------------------------------------------------------------------------- /nvim-custom/configs/lspconfig.lua: -------------------------------------------------------------------------------- 1 | local lspconfig = require("lspconfig") 2 | local configs = require("plugins.configs.lspconfig") 3 | 4 | -- Store the imported on_attach function for later use 5 | local imported_on_attach = configs.on_attach 6 | local capabilities = configs.capabilities 7 | 8 | -- https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md 9 | -- :help lspconfig-all 10 | local servers = { 11 | "vimls", 12 | "lua_ls", 13 | "eslint", 14 | "bashls", 15 | "html", 16 | "tsserver", 17 | "jsonls", 18 | "cssls", 19 | -- "stylelint" 20 | } 21 | 22 | local function on_attach(client, bufnr) 23 | -- Disable document formatting for tsserver 24 | if client.name == "tsserver" then 25 | client.resolved_capabilities.document_formatting = false 26 | end 27 | 28 | -- Call the imported on_attach function to ensure any setup defined there is also executed 29 | if imported_on_attach then 30 | imported_on_attach(client, bufnr) 31 | end 32 | end 33 | 34 | for _, lsp in ipairs(servers) do 35 | lspconfig[lsp].setup({ 36 | on_attach = on_attach, 37 | capabilities = capabilities, 38 | }) 39 | end 40 | 41 | -- lspconfig for each 42 | require("lspconfig").lua_ls.setup({ 43 | settings = { 44 | Lua = { 45 | diagnostics = { 46 | globals = { "vim", "hs" }, 47 | }, 48 | }, 49 | }, 50 | }) 51 | 52 | -- hide all the errors in the file 53 | vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { 54 | virtual_text = false, 55 | underline = true, 56 | signs = true, 57 | severity_sort = true, 58 | update_in_insert = false, 59 | }) 60 | 61 | vim.diagnostic.config({ 62 | virtual_text = false, 63 | }) 64 | 65 | -- 66 | -- ruby / rails 67 | -- https://github.com/Shopify/ruby-lsp-rails 68 | -- https://shopify.github.io/ruby-lsp/ (gem install ruby-lsp) 69 | -------------------------------------------------------------------------------- /nvim-custom/configs/overrides.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- Installation 4 | -- :MasonInstallAll 5 | M.mason = { 6 | ensure_installed = { 7 | -- ruby 8 | "ruby-lsp", 9 | -- lua 10 | "lua-language-server", 11 | "stylua", 12 | 13 | -- web 14 | "css-lsp", 15 | "html-lsp", 16 | "tailwindcss-language-server", 17 | 18 | -- js/ts 19 | "typescript-language-server", 20 | "eslint_d", 21 | "prettierd", 22 | -- "deno", 23 | 24 | -- misc 25 | "json-lsp", 26 | "markdownlint", 27 | "yaml-language-server", 28 | "graphql-language-service-cli", 29 | 30 | -- shell 31 | "shfmt", 32 | "shellcheck", 33 | "bash-language-server", 34 | 35 | -- rust 36 | "rust", 37 | }, 38 | } 39 | 40 | M.treesitter = { 41 | ensure_installed = { 42 | "bash", 43 | "css", 44 | "html", 45 | "javascript", 46 | "json", 47 | "lua", 48 | "markdown", 49 | "scss", 50 | "solidity", 51 | "sql", 52 | "toml", 53 | "tsx", 54 | "typescript", 55 | "yaml", 56 | }, 57 | } 58 | 59 | M.cmp = { 60 | sources = { 61 | -- trigger_characters is for unocss lsp 62 | -- { name = "nvim_lsp", trigger_characters = { "-" } }, 63 | { name = "path" }, 64 | { name = "luasnip" }, 65 | { name = "buffer" }, 66 | -- { name = "codeium" }, 67 | { name = "nvim_lua" }, 68 | }, 69 | experimental = { 70 | ghost_text = true, 71 | }, 72 | } 73 | 74 | M.nvimtree = { 75 | filters = { 76 | dotfiles = true, 77 | custom = { "node_modules" }, 78 | }, 79 | 80 | git = { 81 | enable = true, 82 | ignore = true, 83 | }, 84 | 85 | renderer = { 86 | highlight_git = true, 87 | icons = { 88 | show = { 89 | git = true, 90 | }, 91 | }, 92 | }, 93 | } 94 | 95 | return M 96 | -------------------------------------------------------------------------------- /nvim-custom/highlights.lua: -------------------------------------------------------------------------------- 1 | -- To find any highlight groups: " Telescope highlights" 2 | -- Each highlight group can take a table with variables fg, bg, bold, italic, etc 3 | -- base30 variable names can also be used as colors 4 | 5 | local M = {} 6 | 7 | ---@type Base46HLGroupsList 8 | M.override = { 9 | -- Visual = { 10 | -- bg = "one_bg2", 11 | -- }, 12 | Comment = { 13 | italic = true, 14 | }, 15 | Cursor = { 16 | bg = "red", 17 | fg = "purple", 18 | }, 19 | CursorColumn = { 20 | bg = "red", 21 | }, 22 | TermCursor = { 23 | bg = "red", 24 | }, 25 | -- NvDashAscii = { 26 | -- bg = "none", 27 | -- fg = "pink", 28 | -- }, 29 | -- NvDashButtons = { 30 | -- fg = "grey_fg", 31 | -- bg = "none", 32 | -- }, 33 | -- ColorColumn = { 34 | -- bg = "NONE", 35 | -- }, 36 | -- NvimTreeRootFolder = { 37 | -- fg = "darker_black", 38 | -- bg = "darker_black", 39 | -- }, 40 | -- TBTabTitle = { 41 | -- bg = "darker_black", 42 | -- }, 43 | } 44 | 45 | ---@type HLTable 46 | M.add = { 47 | -- NvimTreeOpenedFolderName = { fg = "green", bold = true }, 48 | } 49 | 50 | return M 51 | -------------------------------------------------------------------------------- /nvim-custom/init.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/siduck/dotfiles/tree/master/nvchad/custom 2 | local opt = vim.opt 3 | local cmd = vim.cmd 4 | 5 | opt.title = true 6 | 7 | cmd([[abbr funciton function]]) 8 | cmd([[abbr teh the]]) 9 | cmd([[abbr tempalte template]]) 10 | cmd([[abbr fitler filter]]) 11 | cmd([[abbr cosnt const]]) 12 | cmd([[abbr attribtue attribute]]) 13 | cmd([[abbr attribuet attribute]]) 14 | cmd([[abbr tamaguii tamagui]]) 15 | cmd([[abbr iimport import]]) 16 | 17 | opt.backup = false -- don't use backup files 18 | opt.writebackup = false -- don't backup the file while editing 19 | opt.swapfile = false -- don't create swap files for new buffers 20 | opt.updatecount = 0 -- don't write swap files after some number of updates 21 | 22 | opt.backspace = { "indent", "eol,start" } -- make backspace behave in a sane manner 23 | 24 | if vim.g.neovide then 25 | vim.g.neovide_refresh_rate = 75 26 | 27 | vim.g.neovide_cursor_vfx_mode = "railgun" 28 | 29 | vim.keymap.set("i", "", "+") 30 | vim.keymap.set("i", "", "") 31 | end 32 | -------------------------------------------------------------------------------- /nvim-custom/mappings.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function SearchFunctionScreen(word) 4 | local wordPattern = word and ".\\{-}" .. word or "" 5 | -- Add \c at the start of the pattern for case-insensitive search 6 | local pattern = "\\cfunction" .. wordPattern .. ".\\{-}Screen" 7 | vim.fn.search(pattern) 8 | end 9 | 10 | M.general = { 11 | n = { 12 | [";"] = { ":", "enter command mode", opts = { nowait = true } }, 13 | }, 14 | i = { 15 | ["jk"] = { "", "escape insert mode" }, 16 | }, 17 | } 18 | 19 | M.lspconfig = { 20 | n = { 21 | ["gpd"] = { "lua require('goto-preview').goto_preview_definition()", "goto-preview definition" }, 22 | ["gpt"] = { 23 | "lua require('goto-preview').goto_preview_type_definition()", 24 | "goto-preview type definition", 25 | }, 26 | ["gpi"] = { 27 | "lua require('goto-preview').goto_preview_implementation()", 28 | "goto-preview implementation", 29 | }, 30 | ["gpD"] = { "lua require('goto-preview').goto_preview_declaration()", "goto-preview declaration" }, 31 | ["gP"] = { "lua require('goto-preview').close_all_win()", "close all preview windows" }, 32 | ["gpr"] = { "lua require('goto-preview').goto_preview_references()", "goto-preview references" }, 33 | ["gS"] = { "lua SearchFunctionScreen('')", "cycle through Screen functions" }, 34 | ["gs"] = { "lua SearchFunctionScreen(vim.fn.input('Screen: '))", "cycle through Screen functions" }, 35 | }, 36 | } 37 | 38 | M.lsp = { 39 | n = { 40 | [";"] = { ":", "enter command mode", opts = { nowait = true } }, 41 | -- format with conform 42 | ["fm"] = { 43 | function() 44 | require("conform").format() 45 | end, 46 | "formatting", 47 | }, 48 | }, 49 | v = { 50 | [">"] = { ">gv", "indent" }, 51 | }, 52 | } 53 | 54 | return M 55 | -------------------------------------------------------------------------------- /nvim-custom/plugins.lua: -------------------------------------------------------------------------------- 1 | local overrides = require("custom.configs.overrides") 2 | 3 | return { 4 | { 5 | "rmagatti/goto-preview", 6 | config = function() 7 | require("goto-preview").setup({}) 8 | end, 9 | }, 10 | 11 | { "wakatime/vim-wakatime", lazy = false }, 12 | 13 | { 14 | "hrsh7th/nvim-cmp", 15 | opts = { 16 | sources = { 17 | -- trigger_characters is for unocss lsp 18 | { name = "nvim_lsp", trigger_characters = { "-" } }, 19 | -- { name = "luasnip" }, 20 | { name = "buffer" }, 21 | { name = "nvim_lua" }, 22 | { name = "path" }, 23 | }, 24 | }, 25 | }, 26 | 27 | { 28 | "zbirenbaum/copilot.lua", 29 | event = { "InsertEnter" }, 30 | cmd = { "Copilot" }, 31 | opts = { 32 | suggestion = { 33 | auto_trigger = true, 34 | }, 35 | }, 36 | }, 37 | 38 | { 39 | "neovim/nvim-lspconfig", 40 | config = function() 41 | require("plugins.configs.lspconfig") 42 | require("custom.configs.lspconfig") 43 | end, 44 | }, 45 | 46 | { 47 | "stevearc/conform.nvim", 48 | -- for users those who want auto-save conform + lazyloading! 49 | event = "BufWritePre", 50 | config = function() 51 | require("custom.configs.conform") 52 | end, 53 | }, 54 | 55 | -- override default configs 56 | { "nvim-tree/nvim-tree.lua", opts = overrides.nvimtree }, 57 | { "williamboman/mason.nvim", opts = overrides.mason }, 58 | { 59 | "nvim-treesitter/nvim-treesitter", 60 | opts = overrides.treesitter, 61 | 62 | config = function(_, opts) 63 | dofile(vim.g.base46_cache .. "syntax") 64 | require("nvim-treesitter.configs").setup(opts) 65 | 66 | -- register mdx ft 67 | vim.filetype.add({ 68 | extension = { mdx = "mdx" }, 69 | }) 70 | 71 | vim.treesitter.language.register("markdown", "mdx") 72 | end, 73 | }, 74 | 75 | --------------------------------------------- custom plugins ---------------------------------------------- 76 | 77 | -- autoclose tags in html, jsx only 78 | { 79 | "windwp/nvim-ts-autotag", 80 | event = "InsertEnter", 81 | config = function() 82 | require("nvim-ts-autotag").setup() 83 | end, 84 | }, 85 | 86 | -- get highlight group under cursor 87 | { 88 | "nvim-treesitter/playground", 89 | cmd = "TSCaptureUnderCursor", 90 | config = function() 91 | require("nvim-treesitter.configs").setup() 92 | end, 93 | }, 94 | 95 | -- dim inactive windows 96 | { 97 | "andreadev-it/shade.nvim", 98 | keys = "", 99 | config = function() 100 | require("shade").setup({ 101 | exclude_filetypes = { "NvimTree" }, 102 | }) 103 | end, 104 | }, 105 | { 106 | "folke/trouble.nvim", 107 | cmd = "Trouble", 108 | config = function() 109 | require("trouble").setup() 110 | end, 111 | }, 112 | 113 | -- distraction free mode 114 | { 115 | "folke/zen-mode.nvim", 116 | cmd = "ZenMode", 117 | config = function() 118 | require("custom.configs.zenmode") 119 | end, 120 | }, 121 | } 122 | -------------------------------------------------------------------------------- /resources/tmux-256color-italic.terminfo: -------------------------------------------------------------------------------- 1 | # A xterm-256color based TERMINFO that adds the escape sequences for italic. 2 | # 3 | # Install: 4 | # 5 | # tic -x xterm-256color-italic.terminfo 6 | # 7 | # Usage: 8 | # 9 | # export TERM=tmux-256color-italic 10 | # 11 | tmux-256color-italic|tmux with 256 colors, 12 | ritm=\E[23m, rmso=\E[27m, sitm=\E[3m, smso=\E[7m, Ms@, 13 | khome=\E[1~, kend=\E[4~, 14 | use=xterm-256color, use=screen-256color, 15 | -------------------------------------------------------------------------------- /resources/tmux.terminfo: -------------------------------------------------------------------------------- 1 | tmux|tmux terminal multiplexer, 2 | ritm=\E[23m, rmso=\E[27m, sitm=\E[3m, smso=\E[7m, Ms@, 3 | use=xterm, use=screen, 4 | 5 | tmux-256color|tmux with 256 colors, 6 | use=xterm-256color, use=tmux, 7 | -------------------------------------------------------------------------------- /resources/xterm-256color-italic.terminfo: -------------------------------------------------------------------------------- 1 | # A xterm-256color based TERMINFO that adds the escape sequences for italic. 2 | # 3 | # Install: 4 | # 5 | # tic -x xterm-256color-italic.terminfo 6 | # 7 | # Usage: 8 | # 9 | # export TERM=xterm-256color-italic 10 | # 11 | # A xterm-256color based TERMINFO that adds the escape sequences for italic. 12 | xterm-256color-italic|xterm with 256 colors and italic, 13 | sitm=\E[3m, ritm=\E[23m, 14 | use=xterm-256color, 15 | -------------------------------------------------------------------------------- /rgrc.symlink: -------------------------------------------------------------------------------- 1 | ## Type additions 2 | 3 | # add new types to search by 4 | --type-add 5 | style:*.{css,sass,less,stylus} 6 | 7 | --type-add 8 | bs:*.{bs,gen} 9 | 10 | --type-add 11 | re:*.{res,re,rei,ml,mli} 12 | 13 | --type-add 14 | pug:*.{pug,jade} 15 | 16 | --type-add 17 | tmpl:*.{html,hbs,pug} 18 | 19 | --type-add 20 | dts:*.d.ts 21 | 22 | --type-add 23 | spec:*.{spec,test}.{ts,tsx,js,jsx} 24 | 25 | --type-add 26 | test:*.{spec,test}.{ts,tsx,js,jsx} 27 | 28 | --type-add 29 | stories:**/*.stories.{ts,tsx,js,jsx} 30 | 31 | --type-add 32 | tsx:*.tsx 33 | 34 | --type-add 35 | jsx:*.jsx 36 | 37 | --type-add 38 | ejs:*.ejs 39 | 40 | --type-add 41 | gql:*.{graphql,gql} 42 | 43 | --type-add 44 | pkg:package.json 45 | 46 | # ignore case 47 | --smart-case 48 | 49 | # follow symlinks 50 | --follow 51 | -------------------------------------------------------------------------------- /screenshots/forgit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterpme/dotfiles/bbd557015dcff32bd3d4eb44477c38f2538da4f7/screenshots/forgit.png -------------------------------------------------------------------------------- /screenshots/git-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterpme/dotfiles/bbd557015dcff32bd3d4eb44477c38f2538da4f7/screenshots/git-diff.png -------------------------------------------------------------------------------- /screenshots/neovim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterpme/dotfiles/bbd557015dcff32bd3d4eb44477c38f2538da4f7/screenshots/neovim.png -------------------------------------------------------------------------------- /screenshots/tmux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterpme/dotfiles/bbd557015dcff32bd3d4eb44477c38f2538da4f7/screenshots/tmux.png -------------------------------------------------------------------------------- /scripts/dock.sh: -------------------------------------------------------------------------------- 1 | #1/bin/bash 2 | 3 | # Remove Dock items 4 | echo -b 'Setting defaults for Dock...' 5 | 6 | main() { 7 | if type dockutil &>/dev/null; then 8 | 9 | dockutil --no-restart \ 10 | --remove 'System Preferences' \ 11 | --remove 'App Store' \ 12 | --remove 'Maps' \ 13 | --remove 'Photos' \ 14 | --remove 'Messages' \ 15 | --remove 'Contacts' \ 16 | --remove 'Calendar' \ 17 | --remove 'FaceTime' \ 18 | --remove 'Feedback Assistant' \ 19 | --remove 'Siri' \ 20 | --remove 'Launchpad' \ 21 | --remove 'Numbers' \ 22 | --remove 'Pages' \ 23 | --remove 'Keynote' \ 24 | --remove 'iBooks' \ 25 | --remove 'Mail' \ 26 | --remove 'Music' \ 27 | --remove 'Podcasts' \ 28 | --remove 'TV' \ 29 | --remove 'News' \ 30 | --add /Applications/kitty.app \ 31 | --add /Applications/Notion.app \ 32 | --add /Applications/Slack.app \ 33 | --add /Applications/Obsidian.app \ 34 | &>/dev/null 35 | 36 | killall cfprefsd &>/dev/null 37 | killall -HUP Dock &>/dev/null 38 | 39 | else 40 | echo 'ERROR: dockutil not found' 41 | fi 42 | } 43 | 44 | main 45 | 46 | -------------------------------------------------------------------------------- /scripts/osx.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # ~/.macos — https://mths.be/macos 4 | 5 | # Close any open System Preferences panes, to prevent them from overriding 6 | # settings we’re about to change 7 | osascript -e 'tell application "System Preferences" to quit' 8 | 9 | # Ask for the administrator password upfront 10 | sudo -v 11 | 12 | # Keep-alive: update existing `sudo` time stamp until `.macos` has finished 13 | while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null & 14 | 15 | ############################################################################### 16 | # General UI/UX # 17 | ############################################################################### 18 | 19 | echo 'General UI/UX' 20 | 21 | # Set sidebar icon size to medium 22 | defaults write NSGlobalDomain NSTableViewDefaultSizeMode -int 2 23 | 24 | # Always show scrollbars 25 | defaults write NSGlobalDomain AppleShowScrollBars -string "Automatic" 26 | 27 | # Disable the over-the-top focus ring animation 28 | defaults write NSGlobalDomain NSUseAnimatedFocusRing -bool false 29 | 30 | # Increase window resize speed for Cocoa applications 31 | defaults write NSGlobalDomain NSWindowResizeTime -float 0.001 32 | 33 | # Expand save panel by default 34 | defaults write NSGlobalDomain NSNavPanelExpandedStateForSaveMode -bool true 35 | defaults write NSGlobalDomain NSNavPanelExpandedStateForSaveMode2 -bool true 36 | 37 | # Expand print panel by default 38 | defaults write NSGlobalDomain PMPrintingExpandedStateForPrint -bool true 39 | defaults write NSGlobalDomain PMPrintingExpandedStateForPrint2 -bool true 40 | 41 | # Automatically quit printer app once the print jobs complete 42 | defaults write com.apple.print.PrintingPrefs "Quit When Finished" -bool true 43 | 44 | # Disable the “Are you sure you want to open this application?” dialog 45 | defaults write com.apple.LaunchServices LSQuarantine -bool false 46 | 47 | # Remove duplicates in the “Open With” menu (also see `lscleanup` alias) 48 | /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user 49 | 50 | # Reveal IP address, hostname, OS version, etc. when clicking the clock 51 | # in the login window 52 | sudo defaults write /Library/Preferences/com.apple.loginwindow AdminHostInfo HostName 53 | 54 | ############################################################################### 55 | # Trackpad, mouse, keyboard, Bluetooth accessories, and input # 56 | ############################################################################### 57 | 58 | echo 'Trackpad: enable tap to click for this user and for the login screen' 59 | defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad Clicking -bool true 60 | defaults -currentHost write NSGlobalDomain com.apple.mouse.tapBehavior -int 1 61 | defaults write NSGlobalDomain com.apple.mouse.tapBehavior -int 1 62 | 63 | echo 'Trackpad: map bottom right corner to right-click' 64 | defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad TrackpadCornerSecondaryClick -int 2 65 | defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad TrackpadRightClick -bool true 66 | defaults -currentHost write NSGlobalDomain com.apple.trackpad.trackpadCornerClickBehavior -int 1 67 | defaults -currentHost write NSGlobalDomain com.apple.trackpad.enableSecondaryClick -bool true 68 | 69 | echo 'Enable full keyboard access for all controls (e.g. enable Tab in modal dialogs)' 70 | defaults write NSGlobalDomain AppleKeyboardUIMode -int 3 71 | 72 | echo 'Disable press-and-hold for keys in favor of key repeat' 73 | defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false 74 | 75 | echo 'Set a blazingly fast keyboard repeat rate' 76 | defaults write NSGlobalDomain KeyRepeat -int 1 77 | defaults write NSGlobalDomain InitialKeyRepeat -int 15 78 | 79 | echo 'Stop iTunes from responding to the keyboard media keys' 80 | launchctl unload -w /System/Library/LaunchAgents/com.apple.rcd.plist 2> /dev/null 81 | 82 | ############################################################################### 83 | # Screen # 84 | ############################################################################### 85 | 86 | echo 'Screen' 87 | 88 | # Require password immediately after sleep or screen saver begins 89 | defaults write com.apple.screensaver askForPassword -int 1 90 | defaults write com.apple.screensaver askForPasswordDelay -int 0 91 | 92 | # Save screenshots to the desktop 93 | defaults write com.apple.screencapture location -string "${HOME}/Desktop" 94 | 95 | # Save screenshots in PNG format (other options: BMP, GIF, JPG, PDF, TIFF) 96 | defaults write com.apple.screencapture type -string "png" 97 | 98 | # Disable shadow in screenshots 99 | defaults write com.apple.screencapture disable-shadow -bool true 100 | 101 | # Enable subpixel font rendering on non-Apple LCDs 102 | # Reference: https://github.com/kevinSuttle/macOS-Defaults/issues/17#issuecomment-266633501 103 | defaults write NSGlobalDomain AppleFontSmoothing -int 1 104 | 105 | # Enable HiDPI display modes (requires restart) 106 | sudo defaults write /Library/Preferences/com.apple.windowserver DisplayResolutionEnabled -bool true 107 | 108 | ############################################################################### 109 | # Finder # 110 | ############################################################################### 111 | 112 | echo 'Finder' 113 | 114 | # Finder: allow quitting via ⌘ + Q; doing so will also hide desktop icons 115 | defaults write com.apple.finder QuitMenuItem -bool true 116 | 117 | # Finder: disable window animations and Get Info animations 118 | # defaults write com.apple.finder DisableAllAnimations -bool true 119 | 120 | # Set Desktop as the default location for new Finder windows 121 | # For other paths, use `PfLo` and `file:///full/path/here/` 122 | defaults write com.apple.finder NewWindowTarget -string "PfDe" 123 | defaults write com.apple.finder NewWindowTargetPath -string "file://${HOME}/Desktop/" 124 | 125 | # hide desktop icons 126 | defaults write com.apple.finder CreateDesktop false 127 | 128 | # Finder: show hidden files by default 129 | defaults write com.apple.finder AppleShowAllFiles -bool true 130 | 131 | # Finder: show all filename extensions 132 | defaults write NSGlobalDomain AppleShowAllExtensions -bool true 133 | 134 | # Finder: show status bar 135 | defaults write com.apple.finder ShowStatusBar -bool true 136 | 137 | # Finder: show path bar 138 | defaults write com.apple.finder ShowPathbar -bool true 139 | 140 | # Display full POSIX path as Finder window title 141 | defaults write com.apple.finder _FXShowPosixPathInTitle -bool true 142 | 143 | # Keep folders on top when sorting by name 144 | defaults write com.apple.finder _FXSortFoldersFirst -bool true 145 | 146 | # When performing a search, search the current folder by default 147 | defaults write com.apple.finder FXDefaultSearchScope -string "SCcf" 148 | 149 | # Disable the warning when changing a file extension 150 | defaults write com.apple.finder FXEnableExtensionChangeWarning -bool false 151 | 152 | # Enable spring loading for directories 153 | defaults write NSGlobalDomain com.apple.springing.enabled -bool true 154 | 155 | # Remove the spring loading delay for directories 156 | defaults write NSGlobalDomain com.apple.springing.delay -float 0 157 | 158 | # Avoid creating .DS_Store files on network or USB volumes 159 | defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true 160 | defaults write com.apple.desktopservices DSDontWriteUSBStores -bool true 161 | 162 | # Automatically open a new Finder window when a volume is mounted 163 | defaults write com.apple.frameworks.diskimages auto-open-ro-root -bool true 164 | defaults write com.apple.frameworks.diskimages auto-open-rw-root -bool true 165 | defaults write com.apple.finder OpenWindowForNewRemovableDisk -bool true 166 | 167 | # Use list view in all Finder windows by default 168 | # Four-letter codes for the other view modes: `icnv`, `clmv`, `glyv` 169 | defaults write com.apple.finder FXPreferredViewStyle -string "Nlsv" 170 | 171 | # Enable AirDrop over Ethernet and on unsupported Macs running Lion 172 | defaults write com.apple.NetworkBrowser BrowseAllInterfaces -bool true 173 | 174 | # Show the ~/Library folder 175 | chflags nohidden ~/Library 176 | 177 | # Show the /Volumes folder 178 | sudo chflags nohidden /Volumes 179 | 180 | # Remove Dropbox’s green checkmark icons in Finder 181 | file=/Applications/Dropbox.app/Contents/Resources/emblem-dropbox-uptodate.icns 182 | [ -e "${file}" ] && mv -f "${file}" "${file}.bak" 183 | 184 | # Expand the following File Info panes: 185 | # “General”, “Open with”, and “Sharing & Permissions” 186 | defaults write com.apple.finder FXInfoPanesExpanded -dict \ 187 | General -bool true \ 188 | OpenWith -bool true \ 189 | Privileges -bool true 190 | 191 | ############################################################################### 192 | # Dock, Dashboard, and hot corners # 193 | ############################################################################### 194 | 195 | echo 'Dock, Dashboard and hot corners' 196 | 197 | # Enable highlight hover effect for the grid view of a stack (Dock) 198 | defaults write com.apple.dock mouse-over-hilite-stack -bool true 199 | 200 | # Set the icon size of Dock items to 36 pixels 201 | defaults write com.apple.dock tilesize -int 36 202 | 203 | # Change minimize/maximize window effect 204 | defaults write com.apple.dock mineffect -string "scale" 205 | 206 | # Show indicator lights for open applications in the Dock 207 | defaults write com.apple.dock show-process-indicators -bool true 208 | 209 | # Don’t animate opening applications from the Dock 210 | defaults write com.apple.dock launchanim -bool false 211 | 212 | # Speed up Mission Control animations 213 | defaults write com.apple.dock expose-animation-duration -float 0.1 214 | 215 | # Remove the auto-hiding Dock delay 216 | defaults write com.apple.dock autohide-delay -float 0 217 | 218 | # Remove the animation when hiding/showing the Dock 219 | defaults write com.apple.dock autohide-time-modifier -float 0 220 | 221 | # Automatically hide and show the Dock 222 | defaults write com.apple.dock autohide -bool true 223 | 224 | # Make Dock icons of hidden applications translucent 225 | defaults write com.apple.dock showhidden -bool true 226 | 227 | # Hot corners 228 | # Possible values: 229 | # 0: no-op 230 | # 2: Mission Control 231 | # 3: Show application windows 232 | # 4: Desktop 233 | # 5: Start screen saver 234 | # 6: Disable screen saver 235 | # 7: Dashboard 236 | # 10: Put display to sleep 237 | # 11: Launchpad 238 | # 12: Notification Center 239 | # 13: Lock Screen 240 | 241 | # Top left screen corner → Mission Control 242 | defaults write com.apple.dock wvous-tl-corner -int 2 243 | defaults write com.apple.dock wvous-tl-modifier -int 0 244 | 245 | # Top right screen corner → Desktop 246 | defaults write com.apple.dock wvous-tr-corner -int 4 247 | defaults write com.apple.dock wvous-tr-modifier -int 0 248 | 249 | # Bottom left screen corner → Start screen saver 250 | defaults write com.apple.dock wvous-bl-corner -int 5 251 | defaults write com.apple.dock wvous-bl-modifier -int 0 252 | 253 | ############################################################################### 254 | # Safari & WebKit # 255 | ############################################################################### 256 | 257 | echo 'Safari & WebKit' 258 | 259 | # Privacy: don’t send search queries to Apple 260 | defaults write com.apple.Safari UniversalSearchEnabled -bool false 261 | defaults write com.apple.Safari SuppressSearchSuggestions -bool true 262 | 263 | # Press Tab to highlight each item on a web page 264 | defaults write com.apple.Safari WebKitTabToLinksPreferenceKey -bool true 265 | defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2TabsToLinks -bool true 266 | 267 | # Show the full URL in the address bar (note: this still hides the scheme) 268 | defaults write com.apple.Safari ShowFullURLInSmartSearchField -bool true 269 | 270 | # Set Safari’s home page to `about:blank` for faster loading 271 | defaults write com.apple.Safari HomePage -string "about:blank" 272 | 273 | # Prevent Safari from opening ‘safe’ files automatically after downloading 274 | defaults write com.apple.Safari AutoOpenSafeDownloads -bool false 275 | 276 | # Allow hitting the Backspace key to go to the previous page in history 277 | defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2BackspaceKeyNavigationEnabled -bool true 278 | 279 | # Hide Safari’s bookmarks bar by default 280 | defaults write com.apple.Safari ShowFavoritesBar -bool false 281 | 282 | # Hide Safari’s sidebar in Top Sites 283 | defaults write com.apple.Safari ShowSidebarInTopSites -bool false 284 | 285 | # Disable Safari’s thumbnail cache for History and Top Sites 286 | defaults write com.apple.Safari DebugSnapshotsUpdatePolicy -int 2 287 | 288 | # Enable Safari’s debug menu 289 | defaults write com.apple.Safari IncludeInternalDebugMenu -bool true 290 | 291 | # Make Safari’s search banners default to Contains instead of Starts With 292 | defaults write com.apple.Safari FindOnPageMatchesWordStartsOnly -bool false 293 | 294 | # Remove useless icons from Safari’s bookmarks bar 295 | defaults write com.apple.Safari ProxiesInBookmarksBar "()" 296 | 297 | # Enable the Develop menu and the Web Inspector in Safari 298 | defaults write com.apple.Safari IncludeDevelopMenu -bool true 299 | defaults write com.apple.Safari WebKitDeveloperExtrasEnabledPreferenceKey -bool true 300 | defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2DeveloperExtrasEnabled -bool true 301 | 302 | # Add a context menu item for showing the Web Inspector in web views 303 | defaults write NSGlobalDomain WebKitDeveloperExtras -bool true 304 | 305 | # Enable continuous spellchecking 306 | defaults write com.apple.Safari WebContinuousSpellCheckingEnabled -bool true 307 | 308 | # Disable AutoFill 309 | defaults write com.apple.Safari AutoFillFromAddressBook -bool false 310 | defaults write com.apple.Safari AutoFillPasswords -bool false 311 | defaults write com.apple.Safari AutoFillCreditCardData -bool false 312 | defaults write com.apple.Safari AutoFillMiscellaneousForms -bool false 313 | 314 | # Warn about fraudulent websites 315 | defaults write com.apple.Safari WarnAboutFraudulentWebsites -bool true 316 | 317 | # Disable plug-ins (Flash) 318 | defaults write com.apple.Safari WebKitPluginsEnabled -bool false 319 | defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2PluginsEnabled -bool false 320 | 321 | # Disable Java 322 | defaults write com.apple.Safari WebKitJavaEnabled -bool false 323 | defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2JavaEnabled -bool false 324 | defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2JavaEnabledForLocalFiles -bool false 325 | 326 | # Block pop-up windows 327 | defaults write com.apple.Safari WebKitJavaScriptCanOpenWindowsAutomatically -bool false 328 | defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2JavaScriptCanOpenWindowsAutomatically -bool false 329 | 330 | # Enable “Do Not Track” 331 | defaults write com.apple.Safari SendDoNotTrackHTTPHeader -bool true 332 | 333 | # Update extensions automatically 334 | defaults write com.apple.Safari InstallExtensionUpdatesAutomatically -bool true 335 | 336 | ############################################################################### 337 | # Mail # 338 | ############################################################################### 339 | 340 | # Disable send and reply animations in Mail.app 341 | defaults write com.apple.mail DisableReplyAnimations -bool true 342 | defaults write com.apple.mail DisableSendAnimations -bool true 343 | 344 | # Copy email addresses as `foo@example.com` instead of `Foo Bar ` in Mail.app 345 | defaults write com.apple.mail AddressesIncludeNameOnPasteboard -bool false 346 | 347 | # Add the keyboard shortcut ⌘ + Enter to send an email in Mail.app 348 | defaults write com.apple.mail NSUserKeyEquivalents -dict-add "Send" "@\U21a9" 349 | 350 | # Display emails in threaded mode, sorted by date (oldest at the top) 351 | defaults write com.apple.mail DraftsViewerAttributes -dict-add "DisplayInThreadedMode" -string "yes" 352 | defaults write com.apple.mail DraftsViewerAttributes -dict-add "SortedDescending" -string "yes" 353 | defaults write com.apple.mail DraftsViewerAttributes -dict-add "SortOrder" -string "received-date" 354 | 355 | # Disable inline attachments (just show the icons) 356 | defaults write com.apple.mail DisableInlineAttachmentViewing -bool true 357 | 358 | ############################################################################### 359 | # Spotlight # 360 | ############################################################################### 361 | 362 | echo 'Spotlight' 363 | 364 | defaults write com.apple.spotlight orderedItems -array \ 365 | '{"enabled" = 1;"name" = "APPLICATIONS";}' \ 366 | '{"enabled" = 1;"name" = "SYSTEM_PREFS";}' \ 367 | '{"enabled" = 1;"name" = "DIRECTORIES";}' \ 368 | '{"enabled" = 1;"name" = "PDF";}' \ 369 | '{"enabled" = 1;"name" = "FONTS";}' \ 370 | '{"enabled" = 0;"name" = "DOCUMENTS";}' \ 371 | '{"enabled" = 0;"name" = "MESSAGES";}' \ 372 | '{"enabled" = 0;"name" = "CONTACT";}' \ 373 | '{"enabled" = 0;"name" = "EVENT_TODO";}' \ 374 | '{"enabled" = 0;"name" = "IMAGES";}' \ 375 | '{"enabled" = 0;"name" = "BOOKMARKS";}' \ 376 | '{"enabled" = 0;"name" = "MUSIC";}' \ 377 | '{"enabled" = 0;"name" = "MOVIES";}' \ 378 | '{"enabled" = 0;"name" = "PRESENTATIONS";}' \ 379 | '{"enabled" = 0;"name" = "SPREADSHEETS";}' \ 380 | '{"enabled" = 0;"name" = "SOURCE";}' \ 381 | '{"enabled" = 0;"name" = "MENU_DEFINITION";}' \ 382 | '{"enabled" = 0;"name" = "MENU_OTHER";}' \ 383 | '{"enabled" = 0;"name" = "MENU_CONVERSION";}' \ 384 | '{"enabled" = 0;"name" = "MENU_EXPRESSION";}' \ 385 | '{"enabled" = 0;"name" = "MENU_WEBSEARCH";}' \ 386 | '{"enabled" = 0;"name" = "MENU_SPOTLIGHT_SUGGESTIONS";}' 387 | 388 | # Load new settings before rebuilding the index 389 | killall mds > /dev/null 2>&1 390 | 391 | # Make sure indexing is enabled for the main volume 392 | sudo mdutil -i on / > /dev/null 393 | 394 | # Rebuild the index from scratch 395 | sudo mdutil -E / > /dev/null 396 | 397 | ############################################################################### 398 | # Terminal & iTerm 2 # 399 | ############################################################################### 400 | 401 | echo 'Terminal' 402 | 403 | # Only use UTF-8 in Terminal.app 404 | defaults write com.apple.terminal StringEncodings -array 4 405 | 406 | # Enable Secure Keyboard Entry in Terminal.app 407 | # See: https://security.stackexchange.com/a/47786/8918 408 | defaults write com.apple.terminal SecureKeyboardEntry -bool true 409 | 410 | # Disable the annoying line marks 411 | defaults write com.apple.Terminal ShowLineMarks -int 0 412 | 413 | # Don’t display the annoying prompt when quitting iTerm 414 | defaults write com.googlecode.iterm2 PromptOnQuit -bool false 415 | 416 | ############################################################################### 417 | # Activity Monitor # 418 | ############################################################################### 419 | 420 | echo 'Activity Monitor' 421 | 422 | # Show the main window when launching Activity Monitor 423 | defaults write com.apple.ActivityMonitor OpenMainWindow -bool true 424 | 425 | # Visualize CPU usage in the Activity Monitor Dock icon 426 | defaults write com.apple.ActivityMonitor IconType -int 5 427 | 428 | # Show all processes in Activity Monitor 429 | defaults write com.apple.ActivityMonitor ShowCategory -int 0 430 | 431 | # Sort Activity Monitor results by CPU usage 432 | defaults write com.apple.ActivityMonitor SortColumn -string "CPUUsage" 433 | defaults write com.apple.ActivityMonitor SortDirection -int 0 434 | 435 | ############################################################################### 436 | # Address Book, Dashboard, iCal, TextEdit, and Disk Utility # 437 | ############################################################################### 438 | 439 | echo 'Address Book, Dashboard' 440 | 441 | # Use plain text mode for new TextEdit documents 442 | defaults write com.apple.TextEdit RichText -int 0 443 | 444 | # Open and save files as UTF-8 in TextEdit 445 | defaults write com.apple.TextEdit PlainTextEncoding -int 4 446 | defaults write com.apple.TextEdit PlainTextEncodingForWrite -int 4 447 | 448 | # Enable the debug menu in Disk Utility 449 | defaults write com.apple.DiskUtility DUDebugMenuEnabled -bool true 450 | defaults write com.apple.DiskUtility advanced-image-options -bool true 451 | 452 | # Auto-play videos when opened with QuickTime Player 453 | defaults write com.apple.QuickTimePlayerX MGPlayMovieOnOpen -bool true 454 | 455 | ############################################################################### 456 | # Mac App Store # 457 | ############################################################################### 458 | 459 | echo 'Mac App Store' 460 | 461 | # Enable the automatic update check 462 | defaults write com.apple.SoftwareUpdate AutomaticCheckEnabled -bool true 463 | 464 | # Check for software updates daily, not just once per week 465 | defaults write com.apple.SoftwareUpdate ScheduleFrequency -int 1 466 | 467 | # Download newly available updates in background 468 | defaults write com.apple.SoftwareUpdate AutomaticDownload -int 1 469 | 470 | # Install System data files & security updates 471 | defaults write com.apple.SoftwareUpdate CriticalUpdateInstall -int 1 472 | 473 | # Automatically download apps purchased on other Macs 474 | defaults write com.apple.SoftwareUpdate ConfigDataInstall -int 1 475 | 476 | # Turn on app auto-update 477 | defaults write com.apple.commerce AutoUpdate -bool true 478 | 479 | # Allow the App Store to reboot machine on macOS updates 480 | defaults write com.apple.commerce AutoUpdateRestartRequired -bool true 481 | 482 | ############################################################################### 483 | # Kill affected applications # 484 | ############################################################################### 485 | 486 | for app in "Activity Monitor" \ 487 | "Address Book" \ 488 | "Calendar" \ 489 | "cfprefsd" \ 490 | "Contacts" \ 491 | "Dock" \ 492 | "Finder" \ 493 | "Google Chrome" \ 494 | "Messages" \ 495 | "Photos" \ 496 | "Safari" \ 497 | "SystemUIServer" \ 498 | "Terminal" \ 499 | "Transmission" \ 500 | "Twitter" \ 501 | "iCal"; do 502 | killall "${app}" &> /dev/null 503 | done 504 | 505 | echo "Done. Note that some of these changes require a logout/restart to take effect." 506 | -------------------------------------------------------------------------------- /scripts/prezto_setup.zsh: -------------------------------------------------------------------------------- 1 | git clone --recursive https://github.com/peterpme/prezto.git "${ZDOTDIR:-$HOME}/.zprezto" 2 | 3 | # Symlink prezto dotfiles 4 | setopt EXTENDED_GLOB 5 | for rcfile in "${ZDOTDIR:-$HOME}"/.zprezto/runcoms/^README.md(.N); do 6 | ln -s "$rcfile" "${ZDOTDIR:-$HOME}/.${rcfile:t}" 7 | done 8 | -------------------------------------------------------------------------------- /settings/Preferences.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "auto_complete_selector": "source - comment, meta.tag - punctuation.definition.tag.begin", 3 | "binary_file_patterns": 4 | [ 5 | "node_modules/*", 6 | "bower_components/*" 7 | ], 8 | "bold_folder_labels": true, 9 | "caret_extra_bottom": 3, 10 | "caret_extra_top": 3, 11 | "caret_extra_width": 2, 12 | "close_windows_when_empty": true, 13 | "color_scheme": "Packages/ayu/ayu-mirage.sublime-color-scheme", 14 | "detect_slow_plugins": false, 15 | "fade_fold_buttons": false, 16 | "file_exclude_patterns": 17 | [ 18 | "*.DS_store", 19 | "*.cur", 20 | "*.eot", 21 | "*.gem", 22 | "*.gz", 23 | "*.jar", 24 | "*.log", 25 | "*.pdf", 26 | "*.psd", 27 | "*.sassc", 28 | "*.scssc", 29 | "*.ttf", 30 | "*.woff", 31 | "*.sublime-project", 32 | "*.sublime-workspace", 33 | "*.xap", 34 | "*.zip" 35 | ], 36 | "folder_exclude_patterns": 37 | [ 38 | ".cache", 39 | ".sass-cache", 40 | ".git", 41 | ".idea", 42 | "nbproject", 43 | ".hg", 44 | ".bin" 45 | ], 46 | "font_face": "Fira Code Retina", 47 | "font_options": 48 | [ 49 | "gray_antialias", 50 | "subpixel_antialias" 51 | ], 52 | "font_size": 14, 53 | "freesia_large_tabs": true, 54 | "highlight_line": true, 55 | "highlight_modified_tabs": true, 56 | "ignored_packages": 57 | [ 58 | "Markdown Extended", 59 | "Vintage", 60 | ], 61 | "line_padding_bottom": 1.2, 62 | "line_padding_top": 1.2, 63 | "overlay_scroll_bars": "enabled", 64 | "remember_open_files": false, 65 | "scroll_past_end": false, 66 | "show_tab_close_buttons": true, 67 | "spacegray_tabs_large": true, 68 | "tab_size": 2, 69 | "theme": "ayu-mirage.sublime-theme", 70 | "translate_tabs_to_spaces": true, 71 | "tree_animation_enabled": false, 72 | "trim_trailing_white_space_on_save": true, 73 | "use_simple_full_screen": true, 74 | "use_tab_stops": true, 75 | "vintage_start_in_command_mode": true, 76 | "word_separators": "./\\()\"':,.;<>~!@#$%^&*|+=[]{}`~?", 77 | "word_wrap": false, 78 | } 79 | -------------------------------------------------------------------------------- /tmux/base16.sh: -------------------------------------------------------------------------------- 1 | # Base16 Styling Guidelines: 2 | 3 | base00=default # - Default 4 | base01='#151515' # - Lighter Background (Used for status bars) 5 | base02='#202020' # - Selection Background 6 | base03='#909090' # - Comments, Invisibles, Line Highlighting 7 | base04='#505050' # - Dark Foreground (Used for status bars) 8 | base05='#D0D0D0' # - Default Foreground, Caret, Delimiters, Operators 9 | base06='#E0E0E0' # - Light Foreground (Not often used) 10 | base07='#F5F5F5' # - Light Background (Not often used) 11 | base08='#AC4142' # - Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted 12 | base09='#D28445' # - Integers, Boolean, Constants, XML Attributes, Markup Link Url 13 | base0A='#F4BF75' # - Classes, Markup Bold, Search Text Background 14 | base0B='#90A959' # - Strings, Inherited Class, Markup Code, Diff Inserted 15 | base0C='#75B5AA' # - Support, Regular Expressions, Escape Characters, Markup Quotes 16 | base0D='#6A9FB5' # - Functions, Methods, Attribute IDs, Headings 17 | base0E='#AA759F' # - Keywords, Storage, Selector, Markup Italic, Diff Changed 18 | base0F='#8F5536' # - Deprecated, Opening/Closing Embedded Language Tags, e.g. 19 | 20 | set -g status-left-length 32 21 | set -g status-right-length 150 22 | set -g status-interval 5 23 | 24 | # default statusbar colors 25 | set-option -g status-style fg=$base02,bg=$base00,default 26 | 27 | set-window-option -g window-status-style fg=$base03,bg=$base00 28 | set-window-option -g window-status-format " #I #W" 29 | 30 | # active window title colors 31 | set-window-option -g window-status-current-style fg=$base0C,bg=$base00 32 | set-window-option -g window-status-current-format " #[bold]#W" 33 | 34 | # pane border colors 35 | set-window-option -g pane-active-border-style fg=$base0C 36 | set-window-option -g pane-border-style fg=$base03 37 | 38 | # message text 39 | set-option -g message-style bg=$base00,fg=$base0C 40 | 41 | # pane number display 42 | set-option -g display-panes-active-colour $base0C 43 | set-option -g display-panes-colour $base01 44 | 45 | # clock 46 | set-window-option -g clock-mode-colour $base0C 47 | 48 | tm_session_name="#[default,bg=$base00,fg=$base0E] #S " 49 | set -g status-left "$tm_session_name" 50 | 51 | tm_tunes="#[bg=$base00,fg=$base0D] ♫ #(osascript -l JavaScript ~/.dotfiles/applescripts/tunes.js)" 52 | # tm_tunes="#[fg=$tm_color_music]#(osascript ~/.dotfiles/applescripts/tunes.scpt | cut -c 1-50)" 53 | # tm_battery="#(~/.dotfiles/bin/battery_indicator.sh)" 54 | tm_battery="#[fg=$base0F,bg=$base00] ♥ #(battery)" 55 | tm_date="#[default,bg=$base00,fg=$base0C] %R" 56 | tm_host="#[fg=$base0E,bg=$base00] #h " 57 | set -g status-right "$tm_battery $tm_date $tm_host" 58 | -------------------------------------------------------------------------------- /tmux/theme.sh: -------------------------------------------------------------------------------- 1 | #### COLOUR 2 | 3 | tm_color_active=colour32 4 | tm_color_inactive=colour241 5 | tm_color_feature=colour206 6 | tm_color_music=colour215 7 | tm_active_border_color=colour240 8 | 9 | # separators 10 | tm_separator_left_bold="◀" 11 | tm_separator_left_thin="❮" 12 | tm_separator_right_bold="▶" 13 | tm_separator_right_thin="❯" 14 | 15 | set -g status-left-length 32 16 | set -g status-right-length 150 17 | set -g status-interval 5 18 | 19 | # default statusbar colors 20 | # set-option -g status-bg colour0 21 | set-option -g status-fg $tm_color_active 22 | set-option -g status-bg default 23 | set-option -g status-attr default 24 | 25 | # default window title colors 26 | set-window-option -g window-status-fg $tm_color_inactive 27 | set-window-option -g window-status-bg default 28 | set -g window-status-format "#I #W" 29 | 30 | # active window title colors 31 | set-window-option -g window-status-current-fg $tm_color_active 32 | set-window-option -g window-status-current-bg default 33 | set-window-option -g window-status-current-format "#[bold]#I #W" 34 | 35 | # pane border 36 | set-option -g pane-border-fg $tm_color_inactive 37 | set-option -g pane-active-border-fg $tm_active_border_color 38 | 39 | # message text 40 | set-option -g message-bg default 41 | set-option -g message-fg $tm_color_active 42 | 43 | # pane number display 44 | set-option -g display-panes-active-colour $tm_color_active 45 | set-option -g display-panes-colour $tm_color_inactive 46 | 47 | # clock 48 | set-window-option -g clock-mode-colour $tm_color_active 49 | 50 | tm_battery="#(~/.dotfiles/bin/battery_indicator.sh)" 51 | 52 | tm_date="#[fg=$tm_color_inactive] %R %d %b" 53 | tm_host="#[fg=$tm_color_feature,bold]#h" 54 | tm_session_name="#[fg=$tm_color_feature,bold]#S" 55 | 56 | set -g status-left $tm_session_name' ' 57 | set -g status-right $tm_date' '$tm_host 58 | -------------------------------------------------------------------------------- /tmux/tmux.conf.symlink: -------------------------------------------------------------------------------- 1 | # Press prefix + I (capital I) to fetch the plugins 2 | # use C-a, since it's on the home row and easier to hit than C-b 3 | set-option -g prefix C-a 4 | unbind-key C-a 5 | 6 | # C-a for nested tmux sessions 7 | bind-key C-a send-prefix 8 | 9 | # make window/pane index start with 1 10 | set -g base-index 1 11 | setw -g pane-base-index 1 12 | 13 | # Enable Mouse mode 14 | set-option -g mouse on 15 | 16 | # automatically renumber tmux windows 17 | set -g renumber-windows on 18 | 19 | # tmux 256 color support 20 | set -g default-terminal "xterm-kitty" 21 | set -as terminal-overrides ",xterm-kitty:RGB" 22 | 23 | # window/pane navigation 24 | bind-key space next-window 25 | bind-key bspace previous-window 26 | bind-key enter next-layout 27 | 28 | # use vim-like keys for splits and windows 29 | bind-key v split-window -h -c "#{pane_current_path}" 30 | bind-key s split-window -v -c "#{pane_current_path}" 31 | 32 | bind-key [ copy-mode 33 | bind-key ] paste-buffer 34 | 35 | # List of plugins 36 | set -g @plugin 'tmux-plugins/tpm' 37 | set -g @plugin 'tmux-plugins/tmux-sensible' 38 | # keybindings to control panes 39 | set -g @plugin 'tmux-plugins/tmux-pain-control' 40 | set -g @plugin 'tmux-plugins/tmux-yank' 41 | set -g @plugin 'nhdaly/tmux-better-mouse-mode' 42 | # https://github.com/tmux-plugins/tmux-resurrect 43 | set -g @plugin 'tmux-plugins/tmux-resurrect' 44 | set -g @plugin 'tmux-plugins/tmux-open' 45 | 46 | # https://github.com/catppuccin/tmux 47 | set -g @plugin 'catppuccin/tmux' 48 | set -g @catppuccin_flavour 'mocha' # or frappe, macchiato, mocha, latte 49 | set -g @catppuccin_window_tabs_enabled on # or off to disable window_tabs 50 | 51 | # set -g @plugin 'jimeh/tmux-themepack' 52 | # set -g @themepack 'powerline/default/cyan' 53 | 54 | # Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf) 55 | run -b '~/.tmux/plugins/tpm/tpm' 56 | 57 | # TokyoNight Undercurl https://github.com/folke/tokyonight.nvim 58 | set -g default-terminal "${TERM}" 59 | set -as terminal-overrides ',*:Smulx=\E[4::%p1%dm' # undercurl support 60 | set -as terminal-overrides ',*:Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m' # underscore colours - needs tmux-3.0 61 | -------------------------------------------------------------------------------- /zsh/functions/fzf-functions: -------------------------------------------------------------------------------- 1 | # fzf https://github.com/junegunn/fzf/wiki/Examples 2 | 3 | # fbr - checkout git branch (including remote branches), sorted by most recent commit, limit 30 last branches 4 | fbr() { 5 | local branches branch 6 | branches=$(git for-each-ref --count=30 --sort=-committerdate refs/heads/ --format="%(refname:short)") && 7 | branch=$(echo "$branches" | 8 | fzf-tmux -d $(( 2 + $(wc -l <<< "$branches") )) +m) && 9 | git checkout $(echo "$branch" | sed "s/.* //" | sed "s#remotes/[^/]*/##") 10 | } 11 | 12 | # fbd - delete git branch (including remote branches) 13 | fbd() { 14 | local branches branch 15 | branches=$(git for-each-ref --count=30 --sort=-committerdate refs/heads/ --format="%(refname:short)") && 16 | branch=$(echo "$branches" | fzf --multi ) && 17 | git branch -D $(echo "$branch" | sed "s/.* //" | sed "s#remotes/[^/]*/##") 18 | } 19 | 20 | # fco - checkout git branch/tag 21 | # fco() { 22 | # local tags branches target 23 | # branches=$( 24 | # git --no-pager branch --all \ 25 | # --format="%(if)%(HEAD)%(then)%(else)%(if:equals=HEAD)%(refname:strip=3)%(then)%(else)%1B[0;34;1mbranch%09%1B[m%(refname:short)%(end)%(end)" \ 26 | # | sed '/^$/d') || return 27 | # tags=$( 28 | # git --no-pager tag | awk '{print "\x1b[35;1mtag\x1b[m\t" $1}') || return 29 | # target=$( 30 | # (echo "$branches"; echo "$tags") | 31 | # fzf --no-hscroll --no-multi -n 2 \ 32 | # --ansi) || return 33 | # git checkout $(awk '{print $2}' <<<"$target" ) 34 | # } 35 | fco() { 36 | local branches target 37 | branches=$(git for-each-ref refs/heads/ --sort=-committerdate \ 38 | --format="%(if)%(HEAD)%(then)* %(else) %(end)%1B[0;34;1mbranch %1B[m%(refname:short)" \ 39 | | sed '/^$/d') || return 40 | target=$(echo "$branches" | fzf --no-hscroll --no-multi -n 2 --ansi) || return 41 | git checkout $(awk '{print $2}' <<< "$target") 42 | } 43 | 44 | 45 | # fco_preview - checkout git branch/tag, with a preview showing the commits between the tag/branch and HEAD 46 | fco_preview() { 47 | local tags branches target 48 | branches=$( 49 | git --no-pager branch --all \ 50 | --format="%(if)%(HEAD)%(then)%(else)%(if:equals=HEAD)%(refname:strip=3)%(then)%(else)%1B[0;34;1mbranch%09%1B[m%(refname:short)%(end)%(end)" \ 51 | | sed '/^$/d') || return 52 | tags=$( 53 | git --no-pager tag | awk '{print "\x1b[35;1mtag\x1b[m\t" $1}') || return 54 | target=$( 55 | (echo "$branches"; echo "$tags") | 56 | fzf --no-hscroll --no-multi -n 2 \ 57 | --ansi --preview="git --no-pager log -150 --pretty=format:%s '..{2}'") || return 58 | git checkout $(awk '{print $2}' <<<"$target" ) 59 | } 60 | 61 | # fshow - git commit browser 62 | fshow() { 63 | git log --graph --color=always \ 64 | --format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" | 65 | fzf --ansi --no-sort --reverse --tiebreak=index --bind=ctrl-s:toggle-sort \ 66 | --bind "ctrl-m:execute: 67 | (grep -o '[a-f0-9]\{7\}' | head -1 | 68 | xargs -I % sh -c 'git show --color=always % | less -R') << 'FZF-EOF' 69 | {} 70 | FZF-EOF" 71 | } 72 | 73 | # fstash - easier way to deal with stashes 74 | # type fstash to get a list of your stashes 75 | # enter shows you the contents of the stash 76 | # ctrl-d shows a diff of the stash against your current HEAD 77 | # ctrl-b checks the stash out as a branch, for easier merging 78 | fstash() { 79 | local out q k sha 80 | while out=$( 81 | git stash list --pretty="%C(yellow)%h %>(14)%Cgreen%cr %C(blue)%gs" | 82 | fzf --ansi --no-sort --query="$q" --print-query \ 83 | --expect=ctrl-d,ctrl-b); 84 | do 85 | mapfile -t out <<< "$out" 86 | q="${out[0]}" 87 | k="${out[1]}" 88 | sha="${out[-1]}" 89 | sha="${sha%% *}" 90 | [[ -z "$sha" ]] && continue 91 | if [[ "$k" == 'ctrl-d' ]]; then 92 | git diff $sha 93 | elif [[ "$k" == 'ctrl-b' ]]; then 94 | git stash branch "stash-$sha" $sha 95 | break; 96 | else 97 | git stash show -p $sha 98 | fi 99 | done 100 | } 101 | 102 | # tm - create new tmux session, or switch to existing one. Works from within tmux too. (@bag-man) 103 | # `tm` will allow you to select your tmux session via fzf. 104 | # `tm irc` will attach to the irc session (if it exists), else it will create it. 105 | 106 | tm() { 107 | [[ -n "$TMUX" ]] && change="switch-client" || change="attach-session" 108 | if [ $1 ]; then 109 | tmux $change -t "$1" 2>/dev/null || (tmux new-session -d -s $1 && tmux $change -t "$1"); return 110 | fi 111 | session=$(tmux list-sessions -F "#{session_name}" 2>/dev/null | fzf --exit-0) && tmux $change -t "$session" || echo "No sessions found." 112 | } 113 | 114 | # fcs - get git commit sha 115 | # example usage: git rebase -i `fcs` 116 | fcs() { 117 | local commits commit 118 | commits=$(git log --color=always --pretty=oneline --abbrev-commit --reverse) && 119 | commit=$(echo "$commits" | fzf --tac +s +m -e --ansi --reverse) && 120 | echo -n $(echo "$commit" | sed "s/ .*//") 121 | } 122 | 123 | # fe [FUZZY PATTERN] - Open the selected file with the default editor 124 | # - Bypass fuzzy finder if there's only one match (--select-1) 125 | # - Exit if there's no match (--exit-0) 126 | fe() { 127 | IFS=$'\n' files=($(fzf-tmux --query="$1" --multi --select-1 --exit-0)) 128 | [[ -n "$files" ]] && ${EDITOR:-vim} "${files[@]}" 129 | } 130 | 131 | # Modified version where you can press 132 | # - CTRL-O to open with `open` command, 133 | # - CTRL-E or Enter key to open with the $EDITOR 134 | fo() { 135 | IFS=$'\n' out=("$(fzf-tmux --query="$1" --exit-0 --expect=ctrl-o,ctrl-e)") 136 | key=$(head -1 <<< "$out") 137 | file=$(head -2 <<< "$out" | tail -1) 138 | if [ -n "$file" ]; then 139 | [ "$key" = ctrl-o ] && open "$file" || ${EDITOR:-vim} "$file" 140 | fi 141 | } 142 | -------------------------------------------------------------------------------- /zsh/functions/gpo: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Function to commit, push, and create a new branch with the commit message 4 | function gpo() { 5 | if [ -z "$1" ]; then 6 | echo "Please provide a commit message." 7 | return 1 8 | fi 9 | 10 | commit_message="$1" 11 | open_web=false 12 | 13 | # Check if the -w flag is provided 14 | if [ "$2" = "-w" ]; then 15 | open_web=true 16 | fi 17 | 18 | # Commit changes 19 | git commit -m "$commit_message" 20 | 21 | # Push changes to the current branch 22 | git push 23 | 24 | # Create a pull request 25 | prc "$commit_message" 26 | 27 | # Open the pull request in the web browser if the -w flag is provided 28 | if $open_web; then 29 | gh pr view --web 30 | fi 31 | } 32 | -------------------------------------------------------------------------------- /zsh/functions/misc-functions: -------------------------------------------------------------------------------- 1 | # npmbin instead of global node modules https://github.com/Schniz/fnm/issues/109 2 | yg() { 3 | cd $HOME/dotfiles/npmbin 4 | yarn add $@ 5 | cd - 6 | } 7 | --------------------------------------------------------------------------------