├── .gitignore ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix ├── git ├── default.nix ├── gitconfig └── gitignore ├── homies.nix ├── homies.png ├── kitty ├── Info.plist ├── default.nix └── kitty.conf ├── neovim ├── default.nix ├── init.lua └── lua │ ├── README.md │ └── fzf.lua ├── nix ├── default.nix └── nix.conf └── zshrc ├── default.nix ├── fzf-key-bindings.zsh └── zshrc /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | *.swp 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Nicolas Mattia 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Homies 2 | 3 | homies 4 | 5 | Reproducible set of dotfiles and packages for Linux and macOS 6 | 7 | --- 8 | 9 | Install with `nix profile install`. Update your `~/.zshrc`: 10 | 11 | ``` zsh 12 | if [ -f $HOME/.nix-profile/share/zshrc/zshrc ]; then source $HOME/.nix-profile/share/zshrc/zshrc; fi 13 | ``` 14 | 15 | The homies will be available in all subsequent shells, including the 16 | customizations (vim with my favorite plugins, tmux with my customized 17 | configuration, etc). See the [introduction blog post][post] for an overview. 18 | 19 | [post]: https://nmattia.com/posts/2018-03-21-nix-reproducible-setup-linux-macos.html 20 | 21 | ## How-To 22 | 23 | Installing the package set: 24 | 25 | ``` shell 26 | $ nix profile install 27 | ``` 28 | 29 | Updating the packages: 30 | 31 | ```shell 32 | $ nix flake update # alternative: nix flake lock --update-input 33 | ``` 34 | 35 | Try out the new packages: 36 | 37 | ```shell 38 | $ nix develop 39 | ``` 40 | 41 | Upgrading to the new profile: 42 | 43 | ``` shell 44 | $ nix profile upgrade homies # or list more with "nix profile list" 45 | ``` 46 | 47 | Syncing apps for Spotlight indexing: 48 | 49 | ``` 50 | $ rsync --archive --checksum --delete --chmod=-w ~/.nix-profile/Applications/ ~/Applications/homies-apps/ && chmod -R +w ~/Applications/homies-apps && codesign --remove-signature ~/Applications/homies-apps/kitty.app && codesign --force --deep --sign - ~/Applications/homies-apps/kitty.app 51 | ``` 52 | 53 | > **Note** 54 | > We copy the app to make sure Spotlight picks it up. Creating a (Finder) alias does work too, but 55 | > the alias is given much lower priority in Spotlight search and the app appears way below e.g. 56 | > online searches, files, etc. 57 | 58 | Listing the previous and current configurations: 59 | 60 | ``` shell 61 | $ nix profile history 62 | ``` 63 | 64 | Deleting old configurations: 65 | 66 | ``` shell 67 | $ nix profile wipe-history 68 | ``` 69 | 70 | Ensure build is sandboxed: 71 | ``` 72 | # /etc/nix/nix.conf 73 | build-users-group = nixbld 74 | # /Library: cc is installed in /Library/Developer (and used from /usr/bin 75 | /cc and others) 76 | # /System/Library: needed for system-wide Perl 77 | sandbox-paths = /bin/bash /bin /usr/bin /usr/sbin /Library /System/Library 78 | sandbox = true 79 | ``` 80 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "bufdelete-nvim": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1708814161, 7 | "narHash": "sha256-ljUNfmpImtxFCS19HC9kFlaLlqaPDltKtnx1+/6Y33U=", 8 | "owner": "famiu", 9 | "repo": "bufdelete.nvim", 10 | "rev": "f6bcea78afb3060b198125256f897040538bcb81", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "famiu", 15 | "repo": "bufdelete.nvim", 16 | "type": "github" 17 | } 18 | }, 19 | "bufferline-nvim": { 20 | "flake": false, 21 | "locked": { 22 | "lastModified": 1736870559, 23 | "narHash": "sha256-ae4MB6+6v3awvfSUWlau9ASJ147ZpwuX1fvJdfMwo1Q=", 24 | "owner": "akinsho", 25 | "repo": "bufferline.nvim", 26 | "rev": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3", 27 | "type": "github" 28 | }, 29 | "original": { 30 | "owner": "akinsho", 31 | "repo": "bufferline.nvim", 32 | "type": "github" 33 | } 34 | }, 35 | "flake-compat": { 36 | "flake": false, 37 | "locked": { 38 | "lastModified": 1733328505, 39 | "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", 40 | "owner": "edolstra", 41 | "repo": "flake-compat", 42 | "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", 43 | "type": "github" 44 | }, 45 | "original": { 46 | "owner": "edolstra", 47 | "repo": "flake-compat", 48 | "type": "github" 49 | } 50 | }, 51 | "flake-utils": { 52 | "inputs": { 53 | "systems": "systems" 54 | }, 55 | "locked": { 56 | "lastModified": 1731533236, 57 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 58 | "owner": "numtide", 59 | "repo": "flake-utils", 60 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 61 | "type": "github" 62 | }, 63 | "original": { 64 | "owner": "numtide", 65 | "repo": "flake-utils", 66 | "type": "github" 67 | } 68 | }, 69 | "fugitive": { 70 | "flake": false, 71 | "locked": { 72 | "lastModified": 1740005044, 73 | "narHash": "sha256-1AteNwnc7lCHLIwM8Ejm2T9VTIDM+CeAfvAUeSQRFKE=", 74 | "owner": "tpope", 75 | "repo": "vim-fugitive", 76 | "rev": "4a745ea72fa93bb15dd077109afbb3d1809383f2", 77 | "type": "github" 78 | }, 79 | "original": { 80 | "owner": "tpope", 81 | "repo": "vim-fugitive", 82 | "type": "github" 83 | } 84 | }, 85 | "kitty-icon": { 86 | "flake": false, 87 | "locked": { 88 | "lastModified": 1696432667, 89 | "narHash": "sha256-AXU1KOXaEiAMTkgkR+yVc8g4FZq8TqXj9imswCHhNKc=", 90 | "owner": "k0nserv", 91 | "repo": "kitty-icon", 92 | "rev": "7f631a61bcbdfb268cdf1c97992a5c077beec9d6", 93 | "type": "github" 94 | }, 95 | "original": { 96 | "owner": "k0nserv", 97 | "repo": "kitty-icon", 98 | "type": "github" 99 | } 100 | }, 101 | "luafun": { 102 | "flake": false, 103 | "locked": { 104 | "lastModified": 1728490544, 105 | "narHash": "sha256-sSutkX0cssRoPPG1vtn1HbYVqKaVMIkUolDafMpHJ7M=", 106 | "owner": "luafun", 107 | "repo": "luafun", 108 | "rev": "f1a57f25bf8554bf430faba5237dec7f04b2979a", 109 | "type": "github" 110 | }, 111 | "original": { 112 | "owner": "luafun", 113 | "repo": "luafun", 114 | "type": "github" 115 | } 116 | }, 117 | "multicursor-nvim": { 118 | "flake": false, 119 | "locked": { 120 | "lastModified": 1742854584, 121 | "narHash": "sha256-Fe22VBABH7O8Ga+uXnByoz4rG+PVVf2lmyiaTb3ra3Y=", 122 | "owner": "jake-stewart", 123 | "repo": "multicursor.nvim", 124 | "rev": "7d3b16fbd86d0de77f7dc25bf2b923796eb37537", 125 | "type": "github" 126 | }, 127 | "original": { 128 | "owner": "jake-stewart", 129 | "repo": "multicursor.nvim", 130 | "type": "github" 131 | } 132 | }, 133 | "nixpkgs": { 134 | "locked": { 135 | "lastModified": 1743365457, 136 | "narHash": "sha256-knTQhVK5xUC6ie2yfmwuONn5V08B8ggkD9Ern28uC84=", 137 | "owner": "NixOS", 138 | "repo": "nixpkgs", 139 | "rev": "fd9f17ef491ffacc24fee30e94491b5b46ba27f0", 140 | "type": "github" 141 | }, 142 | "original": { 143 | "owner": "NixOS", 144 | "repo": "nixpkgs", 145 | "type": "github" 146 | } 147 | }, 148 | "nvim-tree": { 149 | "flake": false, 150 | "locked": { 151 | "lastModified": 1742694377, 152 | "narHash": "sha256-YKt5yYkgNvCWbnyaDHsF/vnLBEuLTE29LHjl5n0iyj8=", 153 | "owner": "kyazdani42", 154 | "repo": "nvim-tree.lua", 155 | "rev": "44d9b58f11d5a426c297aafd0be1c9d45617a849", 156 | "type": "github" 157 | }, 158 | "original": { 159 | "owner": "kyazdani42", 160 | "repo": "nvim-tree.lua", 161 | "type": "github" 162 | } 163 | }, 164 | "nvim-web-devicons": { 165 | "flake": false, 166 | "locked": { 167 | "lastModified": 1742215722, 168 | "narHash": "sha256-JKOvXJr1s2lpP5aeRE7OC3IeOrF5uJxg/Tal3eScd6g=", 169 | "owner": "nvim-tree", 170 | "repo": "nvim-web-devicons", 171 | "rev": "4c3a5848ee0b09ecdea73adcd2a689190aeb728c", 172 | "type": "github" 173 | }, 174 | "original": { 175 | "owner": "nvim-tree", 176 | "repo": "nvim-web-devicons", 177 | "type": "github" 178 | } 179 | }, 180 | "root": { 181 | "inputs": { 182 | "bufdelete-nvim": "bufdelete-nvim", 183 | "bufferline-nvim": "bufferline-nvim", 184 | "flake-compat": "flake-compat", 185 | "flake-utils": "flake-utils", 186 | "fugitive": "fugitive", 187 | "kitty-icon": "kitty-icon", 188 | "luafun": "luafun", 189 | "multicursor-nvim": "multicursor-nvim", 190 | "nixpkgs": "nixpkgs", 191 | "nvim-tree": "nvim-tree", 192 | "nvim-web-devicons": "nvim-web-devicons", 193 | "vim-astro": "vim-astro", 194 | "vim-glsl": "vim-glsl", 195 | "vim-nix": "vim-nix", 196 | "vim-submode": "vim-submode", 197 | "vim-surround": "vim-surround", 198 | "vim-svelte": "vim-svelte", 199 | "vim-terraform": "vim-terraform" 200 | } 201 | }, 202 | "systems": { 203 | "locked": { 204 | "lastModified": 1681028828, 205 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 206 | "owner": "nix-systems", 207 | "repo": "default", 208 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 209 | "type": "github" 210 | }, 211 | "original": { 212 | "owner": "nix-systems", 213 | "repo": "default", 214 | "type": "github" 215 | } 216 | }, 217 | "vim-astro": { 218 | "flake": false, 219 | "locked": { 220 | "lastModified": 1697864536, 221 | "narHash": "sha256-vwQ3hwSpJBCdUp5dlHuP6GPaW7EJ4Rb5kXOJ9qtrpf8=", 222 | "owner": "wuelnerdotexe", 223 | "repo": "vim-astro", 224 | "rev": "9b4674ecfe1dd84b5fb9b4de1653975de6e8e2e1", 225 | "type": "github" 226 | }, 227 | "original": { 228 | "owner": "wuelnerdotexe", 229 | "repo": "vim-astro", 230 | "type": "github" 231 | } 232 | }, 233 | "vim-glsl": { 234 | "flake": false, 235 | "locked": { 236 | "lastModified": 1718439280, 237 | "narHash": "sha256-d5lh5S1YQ1OzlsKmj+cB9UAdbX7haAqo3eR/4s4H7FQ=", 238 | "owner": "tikhomirov", 239 | "repo": "vim-glsl", 240 | "rev": "40dd0b143ef93f3930a8a409f60c1bb85e28b727", 241 | "type": "github" 242 | }, 243 | "original": { 244 | "owner": "tikhomirov", 245 | "repo": "vim-glsl", 246 | "type": "github" 247 | } 248 | }, 249 | "vim-nix": { 250 | "flake": false, 251 | "locked": { 252 | "lastModified": 1738443453, 253 | "narHash": "sha256-Hmn8EVlvMQnQF8COeb89cgl5+A83kagOjGsmvm5WNoE=", 254 | "owner": "LnL7", 255 | "repo": "vim-nix", 256 | "rev": "7235c7ce2cea530cb6b59bc3e46d4bfe917d15c8", 257 | "type": "github" 258 | }, 259 | "original": { 260 | "owner": "LnL7", 261 | "repo": "vim-nix", 262 | "type": "github" 263 | } 264 | }, 265 | "vim-submode": { 266 | "flake": false, 267 | "locked": { 268 | "lastModified": 1499687652, 269 | "narHash": "sha256-5zLztAk3HHi/UIZYPpcWb+q78ad422ZC4n7sJb3PwOE=", 270 | "owner": "kana", 271 | "repo": "vim-submode", 272 | "rev": "d29de4f55c40a7a03af1d8134453a703d6affbd2", 273 | "type": "github" 274 | }, 275 | "original": { 276 | "owner": "kana", 277 | "repo": "vim-submode", 278 | "type": "github" 279 | } 280 | }, 281 | "vim-surround": { 282 | "flake": false, 283 | "locked": { 284 | "lastModified": 1666730476, 285 | "narHash": "sha256-DZE5tkmnT+lAvx/RQHaDEgEJXRKsy56KJY919xiH1lE=", 286 | "owner": "tpope", 287 | "repo": "vim-surround", 288 | "rev": "3d188ed2113431cf8dac77be61b842acb64433d9", 289 | "type": "github" 290 | }, 291 | "original": { 292 | "owner": "tpope", 293 | "repo": "vim-surround", 294 | "type": "github" 295 | } 296 | }, 297 | "vim-svelte": { 298 | "flake": false, 299 | "locked": { 300 | "lastModified": 1666888764, 301 | "narHash": "sha256-sZcHLBCGvCk8px1FlIU+JwDbHS1e7neeXMMQLPoCYe8=", 302 | "owner": "evanleck", 303 | "repo": "vim-svelte", 304 | "rev": "0e93ec53c3667753237282926fec626785622c1c", 305 | "type": "github" 306 | }, 307 | "original": { 308 | "owner": "evanleck", 309 | "repo": "vim-svelte", 310 | "type": "github" 311 | } 312 | }, 313 | "vim-terraform": { 314 | "flake": false, 315 | "locked": { 316 | "lastModified": 1737360319, 317 | "narHash": "sha256-I2L37E2TBE3ZuK5I3xwqxmGdrwbzEMI3Keqz8k0fBdk=", 318 | "owner": "hashivim", 319 | "repo": "vim-terraform", 320 | "rev": "8912ca1be3025a1c9fab193618f3b99517e01973", 321 | "type": "github" 322 | }, 323 | "original": { 324 | "owner": "hashivim", 325 | "repo": "vim-terraform", 326 | "type": "github" 327 | } 328 | } 329 | }, 330 | "root": "root", 331 | "version": 7 332 | } 333 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "my project description"; 3 | 4 | inputs.flake-compat = { 5 | url = github:edolstra/flake-compat; 6 | flake = false; 7 | }; 8 | 9 | inputs.nixpkgs.url = "github:NixOS/nixpkgs"; 10 | 11 | inputs.flake-utils.url = "github:numtide/flake-utils"; 12 | 13 | inputs.vim-nix.url = "github:LnL7/vim-nix"; 14 | inputs.vim-nix.flake = false; 15 | 16 | inputs.kitty-icon.url = "github:k0nserv/kitty-icon"; 17 | inputs.kitty-icon.flake = false; 18 | 19 | inputs.vim-svelte.url = "github:evanleck/vim-svelte"; 20 | inputs.vim-svelte.flake = false; 21 | 22 | inputs.vim-terraform.url = "github:hashivim/vim-terraform"; 23 | inputs.vim-terraform.flake = false; 24 | 25 | inputs.vim-glsl.url = "github:tikhomirov/vim-glsl"; 26 | inputs.vim-glsl.flake = false; 27 | 28 | inputs.nvim-tree.url = "github:kyazdani42/nvim-tree.lua"; 29 | inputs.nvim-tree.flake = false; 30 | 31 | inputs.fugitive.url = "github:tpope/vim-fugitive"; 32 | inputs.fugitive.flake = false; 33 | 34 | inputs.vim-astro.url = "github:wuelnerdotexe/vim-astro"; 35 | inputs.vim-astro.flake = false; 36 | 37 | inputs.vim-surround.url = github:tpope/vim-surround; 38 | inputs.vim-surround.flake = false; 39 | 40 | inputs.multicursor-nvim.url = github:jake-stewart/multicursor.nvim; 41 | inputs.multicursor-nvim.flake = false; 42 | 43 | # dependency of bufferline-nvim 44 | inputs.nvim-web-devicons.url = github:nvim-tree/nvim-web-devicons; 45 | inputs.nvim-web-devicons.flake = false; 46 | 47 | inputs.bufferline-nvim.url = github:akinsho/bufferline.nvim; 48 | inputs.bufferline-nvim.flake = false; 49 | 50 | inputs.bufdelete-nvim.url = github:famiu/bufdelete.nvim; 51 | inputs.bufdelete-nvim.flake = false; 52 | 53 | inputs.vim-submode.url = github:kana/vim-submode; 54 | inputs.vim-submode.flake = false; 55 | 56 | inputs.luafun.url = github:luafun/luafun; 57 | inputs.luafun.flake = false; 58 | 59 | outputs = 60 | inputs@{ self 61 | , nixpkgs 62 | , flake-compat 63 | , flake-utils 64 | , ... 65 | }: 66 | flake-utils.lib.eachSystem [ "x86_64-darwin" "aarch64-darwin" ] (system: 67 | let 68 | pkgs = nixpkgs.legacyPackages.${system}; 69 | homies = import ./homies.nix { 70 | nixpkgs-src = nixpkgs; 71 | inherit 72 | pkgs 73 | inputs; 74 | }; 75 | in 76 | { 77 | packages.default = homies; 78 | } 79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /git/default.nix: -------------------------------------------------------------------------------- 1 | # Git, with a git config baked in (see ./config) 2 | { runCommand, git, symlinkJoin, writeTextFile }: 3 | let 4 | 5 | gitconfig = writeTextFile 6 | { 7 | name = "git-config"; 8 | text = 9 | builtins.replaceStrings 10 | [ "SUBSTITUTE_GITIGNORE" ] [ "${./gitignore}" ] 11 | (builtins.readFile ./gitconfig); 12 | }; 13 | in 14 | 15 | git.overrideAttrs (old: { 16 | 17 | # git has three levels of config: system, global, and local. The nixpkgs build 18 | # of git already writes some stuff to the system config, so we just append our 19 | # own config. The big drawback is that we need to build git from scratch. 20 | postInstall = old.postInstall + '' 21 | cat ${gitconfig} >> $out/etc/gitconfig 22 | ''; 23 | }) 24 | -------------------------------------------------------------------------------- /git/gitconfig: -------------------------------------------------------------------------------- 1 | # vi: ft=gitconfig 2 | [core] 3 | excludesfile = SUBSTITUTE_GITIGNORE 4 | editor = nvim 5 | [user] 6 | name = Nicolas Mattia 7 | [alias] 8 | co = checkout 9 | br = branch 10 | ci = commit 11 | st = status 12 | # [r]e[b]ase: Fetch and rebase on default branch 13 | rb = !git fetch && git rebase $(git rev-parse --abbrev-ref origin/HEAD) 14 | # [d]i[f]f: Diff against last common ancestor with default branch 15 | df = !git diff $(git merge-base HEAD $(git rev-parse --abbrev-ref origin/HEAD)) 16 | # Add everything and amend 17 | amen = !git add -A && git commit --amend 18 | 19 | # Fast config setup 20 | dfn = config user.email nicolas.mattia@dfinity.org 21 | me = config user.email nicolas@nmattia.com 22 | 23 | alias = config --get-regexp alias 24 | head = rev-parse HEAD 25 | [push] 26 | default = simple 27 | [url "git@github.com:"] 28 | insteadOf = https://github.com/ 29 | [init] 30 | defaultBranch = "main" 31 | -------------------------------------------------------------------------------- /git/gitignore: -------------------------------------------------------------------------------- 1 | result 2 | result-* 3 | *.swp 4 | tags 5 | -------------------------------------------------------------------------------- /homies.nix: -------------------------------------------------------------------------------- 1 | { pkgs, nixpkgs-src, inputs }: 2 | # The main homies file, where homies are defined. See the README.md for 3 | # instructions. 4 | let 5 | 6 | nix = pkgs.callPackage ./nix { }; 7 | 8 | neovim = pkgs.callPackage ./neovim { inherit inputs; }; 9 | 10 | kitty = pkgs.callPackage ./kitty { inherit inputs; }; 11 | 12 | # A custom '.zshrc' (see zshrc/default.nix for details) 13 | zshrc = pkgs.callPackage ./zshrc { inherit nixpkgs-src; }; 14 | 15 | # Git with config baked in 16 | git = pkgs.callPackage ./git { }; 17 | in 18 | 19 | # The "homies", which is a buildEnv where bin/ contains all the executables. 20 | # The manpages are in share/man, which are auto-discovered by man (because 21 | # it's close to bin/ which is on the PATH). 22 | pkgs.buildEnv { 23 | name = "homies"; 24 | paths = 25 | [ 26 | zshrc 27 | git 28 | kitty.wrapper 29 | kitty.bundle 30 | nix 31 | neovim 32 | 33 | pkgs.curl 34 | pkgs.direnv 35 | pkgs.entr 36 | pkgs.git-lfs 37 | pkgs.gnupg 38 | pkgs.nixpkgs-fmt 39 | pkgs.niv 40 | pkgs.fzf 41 | pkgs.htop 42 | pkgs.jq 43 | pkgs.less 44 | pkgs.mpremote 45 | pkgs.scc 46 | pkgs.shellcheck 47 | pkgs.shfmt 48 | pkgs.tree 49 | ]; 50 | } 51 | -------------------------------------------------------------------------------- /homies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmattia/homies/b0eff88b9cd0cc3ed9c432d9e632c1185b2b056b/homies.png -------------------------------------------------------------------------------- /kitty/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleExecutable 6 | kitty 7 | CFBundlePackageType 8 | APPL 9 | CFBundleVersion 10 | 1.0.0 11 | CFBundleIdentifier 12 | com.nmattia.kitty 13 | CFBundleDisplayName 14 | kitty 15 | CFBundleName 16 | Kitty 17 | CFBundleIconFile 18 | kitty.icns 19 | 20 | 21 | -------------------------------------------------------------------------------- /kitty/default.nix: -------------------------------------------------------------------------------- 1 | { inputs, runCommand, writeText, writeScriptBin, _7zz }: 2 | 3 | # Wrapped kitty terminal emulator, different from stock: 4 | # * We create a dummy macos bundle with a custom icon. The bundle points to 5 | # ~/.nix-profile and should only rarely need to be updated. 6 | # * The actual `kitty` executable in PATH is a shell wrapper that sets custom 7 | # configuration (kitty.conf, startup args) 8 | 9 | let 10 | 11 | # NOTE: we use the official kitty build because it is signed & notarized by the author. Unless signed, 12 | # kitty can't trigger notifications on macOS. 13 | version = "0.41.1"; 14 | sha256 = "sha256:10j445iif392l0srhlqjm1vg8vkmqmcpl7cpwm10j7dy9wlvbv4j"; 15 | kittyDmg = builtins.fetchurl { 16 | url = "https://github.com/kovidgoyal/kitty/releases/download/v${version}/kitty-${version}.dmg"; 17 | inherit sha256; 18 | }; 19 | kitty = runCommand "kitty" { nativeBuildInputs = [ _7zz ]; } '' 20 | mkdir -p "$out/Applications" 21 | cd $out/Applications 22 | 7zz x ${kittyDmg} 23 | ''; 24 | 25 | kittyConfDir = runCommand "kitty-conf" { nativeBuildInputs = [ _7zz ]; } '' 26 | mkdir -p $out 27 | cp ${./kitty.conf} $out/kitty.conf 28 | ''; 29 | 30 | # kitty uses $0/_NSGetExecutablePath to figure out the path of its 31 | # bundle so (on macOS) kitty should find the correct set of resources (except 32 | # for the icon, but the icon is only relevant to macOS when reading the bundle) 33 | wrapper = writeScriptBin "kitty" '' 34 | #!/usr/bin/env bash 35 | 36 | export KITTY_CONFIG_DIRECTORY=${kittyConfDir} 37 | exec ${kitty}/Applications/kitty.app/Contents/MacOS/kitty --start-as=fullscreen "$@" 38 | ''; 39 | 40 | 41 | # Actual runner injected in the bundle 42 | # NOTE: KITTY_LAUNCHED_BY_LAUNCH_SERVICES=1 tells kitty to change dir to HOME 43 | kittyProfileRunner = writeText "kittyexe" '' 44 | #!/usr/bin/env bash 45 | kitty_exe="$HOME/.nix-profile/bin/kitty" 46 | 47 | if ! [ -e "$kitty_exe" ]; then echo "kitty not found"; exit 1; fi 48 | exec -a kitty env KITTY_LAUNCHED_BY_LAUNCH_SERVICES=1 $kitty_exe 49 | ''; 50 | 51 | bundle = runCommand "kitty-0" { } '' 52 | bundle=$out/Applications/kitty.app 53 | mkdir -p $bundle 54 | 55 | mkdir -p $bundle/Contents 56 | cat <${./Info.plist} > $bundle/Contents/Info.plist 57 | 58 | mkdir -p $bundle/Contents/MacOS 59 | cat <${kittyProfileRunner} > $bundle/Contents/MacOS/kitty 60 | chmod +x $bundle/Contents/MacOS/kitty 61 | 62 | mkdir -p $bundle/Contents/Resources/ 63 | cat <${inputs.kitty-icon}/build/neue_toxic.icns > $bundle/Contents/Resources/kitty.icns 64 | 65 | ''; 66 | 67 | in 68 | { inherit bundle wrapper; } 69 | -------------------------------------------------------------------------------- /kitty/kitty.conf: -------------------------------------------------------------------------------- 1 | # Disable sound (backspace on empty, etc) 2 | enable_audio_bell no 3 | 4 | # Make sure RIGHT option can be used as alt (or esp. as ESC 5 | # in vim) and LEFT option can still be used for unicode input 6 | macos_option_as_alt right 7 | 8 | # Have kitty quit when all the top-level windows are closed on macOS. 9 | macos_quit_when_last_window_closed yes 10 | 11 | # Very long scrollback history 12 | scrollback_lines 100000 13 | 14 | # Disable automated version checks by kitty 15 | update_check_interval 0 16 | 17 | # Font size slightly bigger than default (11) 18 | font_size 13.0 19 | 20 | # https://sw.kovidgoyal.net/kitty/shell-integration/#shell-integration 21 | shell_integration enabled 22 | 23 | # Only only a few layouts and make 'tall' the first one (i.e. default one) 24 | # (with first window 66% of width, assuming more room is needed for the 25 | # editor) 26 | enabled_layouts tall:bias=66,grid,fat,stack 27 | 28 | # open new windows in the current CWD 29 | map cmd+enter launch --cwd=last_reported 30 | 31 | # kitty sometimes inherits bash as SHELL for unknown reasons 32 | shell /bin/zsh 33 | -------------------------------------------------------------------------------- /neovim/default.nix: -------------------------------------------------------------------------------- 1 | { runCommand, lib, makeWrapper, coreutils, neovim-unwrapped, symlinkJoin, fzf, inputs, ripgrep }: 2 | let 3 | plugins = { 4 | inherit (inputs) 5 | fugitive 6 | bufdelete-nvim 7 | bufferline-nvim 8 | multicursor-nvim 9 | nvim-tree 10 | nvim-web-devicons 11 | vim-astro 12 | vim-glsl 13 | vim-nix 14 | vim-submode 15 | vim-surround 16 | vim-svelte 17 | vim-terraform; 18 | }; 19 | 20 | plugins' = lib.mapAttrsToList (k: v: "${k} ${v}") plugins; 21 | plugins'' = lib.concatStringsSep "\n" plugins'; 22 | 23 | pluginsDir = runCommand "mk-plugins" { nativeBuildInputs = [ neovim-unwrapped ]; } 24 | '' 25 | plugins_dir="$out/pack/nix-is-an-addiction/start" 26 | 27 | mkdir -p "$plugins_dir" 28 | 29 | # loop over plugins, using FD 10 to avoid polluting stdin 30 | # (trips nvim otherwise) 31 | while read -u 10 plug_name plugin 32 | do 33 | plug_dest="$plugins_dir/$plug_name" 34 | 35 | echo installing plugin 36 | echo " " name "'$plug_name'" 37 | echo " " source "'$plugin'" 38 | echo " " destination "'$plug_dest'" 39 | 40 | cp -a "$plugin/." "$plug_dest" 41 | 42 | # build doc/helptags if necessary 43 | pushd "$plug_dest" >/dev/null 44 | if [ -d doc ] 45 | then 46 | echo installing doc 47 | chmod -R +w . 48 | # Set home & al so that nvim can create swapfiles 49 | XDG_DATA_HOME=$PWD HOME=$PWD nvim -u NONE -c ":helptags doc" -c q 50 | fi 51 | popd >/dev/null 52 | done 10<<< $(echo -n "${plugins''}") 53 | ''; 54 | 55 | luafun = runCommand "luafun" { } '' 56 | mkdir -p $out 57 | cp ${inputs.luafun}/fun.lua $out/fun.lua 58 | ''; 59 | 60 | extraBins = [ 61 | ripgrep # used by fzf.lua for rg 62 | coreutils 63 | ]; 64 | in 65 | 66 | symlinkJoin { 67 | name = "neovim"; 68 | buildInputs = [ makeWrapper ]; 69 | postBuild = '' 70 | wrapProgram "$out/bin/nvim" \ 71 | --add-flags "-u ${./init.lua}" \ 72 | --set NEOVIM_PLUGINS_PATH '${pluginsDir}' \ 73 | --set NEOVIM_LUA_PATH '${luafun}/?.lua;${./lua}/?.lua' \ 74 | --prefix PATH ':' '${lib.makeBinPath extraBins}' 75 | ''; 76 | paths = [ neovim-unwrapped ]; 77 | } 78 | -------------------------------------------------------------------------------- /neovim/init.lua: -------------------------------------------------------------------------------- 1 | -- First, make sure the Lua modules can be "require"d 2 | package.path = package.path .. ";" .. vim.env.NEOVIM_LUA_PATH 3 | 4 | -- Initialize functional lua 5 | require'fun'() 6 | 7 | -- Set the mapleader (space) 8 | -- note: we remap space to ensure there's no existing mapping: https://stackoverflow.com/a/446293 9 | vim.keymap.set('n', ' ', '', { silent = true, remap = false }) 10 | vim.g.mapleader = ' ' 11 | 12 | local opts = { 13 | 14 | -- Open new splits on the right/below 15 | "splitright", 16 | "splitbelow", 17 | 18 | -- Display tabs and trailing spaces 19 | list = true, 20 | listchars = { tab = "▷⋅", trail = "⋅", nbsp = "⋅" }, 21 | 22 | -- Wrap lines 23 | wrap = true, 24 | 25 | -- Default indent settings 26 | shiftwidth = 4, 27 | softtabstop = 4, 28 | 29 | -- Set the width of \t to 4. It's still a TAB, but displayed as wide as 4 30 | -- chars. 31 | tabstop = 4, 32 | 33 | -- In insert mode, hitting TAB will insert N spaces instead. 34 | expandtab = true, 35 | 36 | -- NEOVIM_PLUGINS_PATH should be set to a dir containing plugins 37 | packpath = vim.opt.packpath + { vim.env.NEOVIM_PLUGINS_PATH }, 38 | 39 | -- Large scrollback in terminal (default: 10_000) 40 | scrollback = 100000, 41 | 42 | -- Enables 24-bit RGB color 43 | termguicolors = true, 44 | } 45 | 46 | -- This reads all the "opts" and sets the corresponding vim opt. This takes 47 | -- advantage of the fact that Lua treats { "foo" } as an associative array with 48 | -- { 1 = "foo" }, thus, if a key is a "number", then we set the opt to "true". 49 | for opt_key in pairs(opts) do 50 | if(type(opt_key) == "number") then 51 | vim.opt[opts[opt_key]] = true 52 | elseif (type(opt_key) == "string") then 53 | vim.opt[opt_key] = opts[opt_key] 54 | end 55 | end 56 | 57 | -- Show line numbers 58 | vim.opt.number = true 59 | -- ... except in terminal 60 | vim.api.nvim_create_autocmd('TermOpen', { 61 | callback = function () 62 | vim.wo.number = false 63 | vim.wo.relativenumber = false 64 | end 65 | }) 66 | 67 | -- git = ignore = false: make sure nvim-tree shows gitignored files 68 | require'nvim-tree'.setup({ git = { ignore = false }}) 69 | 70 | -- Toggle filetree on ,o 71 | vim.keymap.set('n', 'o', vim.cmd.NvimTreeToggle, { noremap = true }) 72 | 73 | -- Remove trailing whitespaces 74 | vim.api.nvim_command([[ 75 | fun! TrimWhitespace() 76 | " by saving/restoring the view we make sure the cursor doesn't appear to 77 | " have moved 78 | let l:save = winsaveview() 79 | keeppatterns %s/\s\+$//e 80 | call winrestview(l:save) 81 | endfun 82 | ]]) 83 | vim.keymap.set('n', 'w', vim.fn.TrimWhitespace, { noremap = true }) 84 | 85 | -- search 86 | 87 | -- Stop highlighting search on C-/ 88 | vim.keymap.set('n', '', vim.cmd.noh, { noremap = true }) 89 | 90 | -- Case insensitive search with ,/ 91 | vim.keymap.set('n', '/', '/\\c', { noremap = true }) 92 | 93 | -- E[x]it with ,x 94 | vim.keymap.set('n', 'x', vim.cmd.x, { noremap = true }) 95 | 96 | -- [d]elete buffer with ,d 97 | -- note: this uses 'bufdelete' which ensures that nvim-tree doesn't end up as fullscreen 98 | -- when it's the last buffer 99 | vim.keymap.set('n', 'd', require'bufdelete'.bufdelete, { noremap = true }) 100 | 101 | -- Navigation across windows 102 | 103 | -- Simplify navigator across windows; C-{hjkl} moves to other window 104 | for _,key in pairs{ 'H', 'J', 'K', 'L' } do 105 | vim.keymap.set('n', '', '', { noremap = true }) 106 | end 107 | 108 | -- Same, in insert mode (uses C-O which runs a command and then re-enters 109 | -- insert mode) 110 | for _,key in pairs{ 'H', 'J', 'K', 'L' } do 111 | vim.keymap.set('i', '', '', { noremap = true }) 112 | end 113 | 114 | -- FZF + ripgrep on ,fr 115 | 116 | local fzf = require'fzf' 117 | vim.keymap.set('n', 'fr', fzf.rg, { noremap = true }) 118 | 119 | -- Misc 120 | 121 | -- Wrap selected lines with Q 122 | vim.keymap.set('n', 'Q', 'gq', { noremap = true }) 123 | 124 | -- Yank til end of line (consistent with C and D) 125 | vim.keymap.set('n', 'Y', 'y$', { noremap = true }) 126 | 127 | -- Select the whole file with 128 | vim.keymap.set('n', '', 'ggVG', { noremap = true }) 129 | 130 | -- Start a git command with ,g 131 | vim.keymap.set('n', 'g', ':G ', { noremap = true }) 132 | 133 | -- [r]efresh buffers 134 | vim.keymap.set('n', 'r', ':checktime ', { noremap = true }) 135 | 136 | -- In Visual, sort with 137 | vim.keymap.set('v', '', ':sort', { noremap = true }) 138 | 139 | -- TERMINAL 140 | 141 | -- Open a terminal in the current window 142 | vim.keymap.set('n', 't', vim.cmd.terminal, { noremap = true }) 143 | 144 | -- Exit terminal through Ctrl+hjkl 145 | for _,key in pairs{ 'H', 'J', 'K', 'L' } do 146 | vim.keymap.set('t', '', '', { noremap = true }) 147 | end 148 | 149 | -- Close the terminal buffer if the terminal exits with 0 150 | vim.api.nvim_create_autocmd('TermClose', { 151 | command = "if !v:event.status | exe 'bdelete! '..expand('') | endif" 152 | }) 153 | 154 | -- Multi-cursor edit (enabled only in normal mode to avoid clash with 155 | -- in visual mode) 156 | local mc = require("multicursor-nvim") 157 | mc.setup() 158 | -- Add a cursor and jump to the next word under cursor. 159 | vim.keymap.set({'n'}, '', function() mc.addCursor("*") end) 160 | -- Skip word under cursor 161 | vim.keymap.set({'n'}, '', function() mc.skipCursor("*") end) 162 | 163 | -- Clear all cursors 164 | vim.keymap.set({'n', 'v'}, '', function() 165 | if mc.hasCursors() then 166 | mc.clearCursors() 167 | end 168 | end) 169 | 170 | -- Set up bufferline: https://github.com/akinsho/bufferline.nvim?tab=readme-ov-file#usage 171 | require("bufferline").setup{} 172 | 173 | -- switch to previous/next buffer (and enter submode for quick repeat with h/l) 174 | vim.api.nvim_command([[ 175 | call submode#enter_with('switchbuf', 'n', '', 'h', ':bprevious') 176 | call submode#enter_with('switchbuf', 'n', '', 'l', ':bnext') 177 | call submode#leave_with('switchbuf', 'n', '', '') 178 | call submode#map('switchbuf', 'n', '', 'h', ':bprevious') 179 | call submode#map('switchbuf', 'n', '', 'l', ':bnext') 180 | ]]) 181 | 182 | -- Make vim sees the leaving key when exiting a submode (similar to 183 | -- e.g. opt+key when emulating Esc) 184 | vim.g.submode_keep_leaving_key = true 185 | -------------------------------------------------------------------------------- /neovim/lua/README.md: -------------------------------------------------------------------------------- 1 | # Lua Functions 2 | 3 | The Lua interpreter will look for packages on the `package.path`. During development the functions can be imported as follows: 4 | 5 | ```vim 6 | :so " loads the file, then just call the function you need 7 | ``` 8 | 9 | in file, or alternatively reload package: 10 | 11 | ``` vim 12 | " it's important to prepend since package.path already loads those packages from the baked-in config 13 | :lua package.path = "./neovim/lua/foo.lua;" .. package.path 14 | :lua package.loaded.foo = nil; require'foo'.foo_func() 15 | ``` 16 | 17 | For more info, see the [Neovim Lua documentation](https://neovim.io/doc/user/lua.html) 18 | -------------------------------------------------------------------------------- /neovim/lua/fzf.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | 3 | local function read_file(filename) 4 | local f = assert(io.open(filename, "rb")) 5 | local content = f:read("*all") 6 | f:close() 7 | return content 8 | end 9 | 10 | local rg_filename_and_lineno = function(line) 11 | local idx = string.find(line, ":") 12 | local filename = string.sub(line, 1, idx-1) 13 | line = string.sub(line, idx+1, -1) 14 | 15 | idx = string.find(line, ":") 16 | local lineno = string.sub(line, 1, idx-1) 17 | 18 | return filename, tonumber(lineno) 19 | end 20 | 21 | local mk_fzf_bindings = function(bindings) 22 | local kvs = iter(bindings):map(function(k,v) return k..":"..v end):totable() 23 | return table.concat(kvs, ",") 24 | end 25 | 26 | local rg = function() 27 | 28 | vim.cmd("new") 29 | local new_buf_nr = api.nvim_get_current_buf() 30 | local stdout_filename = os.tmpname() 31 | 32 | -- --line-number: show line numbers, useful when picking exact line 33 | -- --no-heading: don't cluster by file name (file name is printed alone above matches) 34 | -- '\S': print all lines that contain at least one non-space character 35 | -- --hidden --glob '!.git': show hidden files like `.github/` but ignore `.git/` 36 | local rg_opts = '--line-number --no-heading --color=always --hidden --glob "!.git"' 37 | local rg = 'rg '..rg_opts..' -- "\\S"' 38 | 39 | -- num[b]er all lines, and format is [l]eft justified 40 | local add_linenos = 'nl -b all -n ln' 41 | 42 | -- Match on 'start of line' + line number of selected line 43 | -- Capture rest of line in \1 44 | -- Replace everything with "line number" + + \1 + 45 | local highlight_line = [[sed "s/^"{2}" \(.*\)""/"{2}" "`tput bold`"\1"`tput sgr0`"/"]] 46 | 47 | -- fzf's preview command, that shows the file (with the selected line highlighted) 48 | local preview_cmd = 'cat {1} | '..add_linenos..' | '..highlight_line 49 | 50 | local fzf_bindings = mk_fzf_bindings{ 51 | ['ctrl-p'] = 'toggle-preview', 52 | ['ctrl-c'] = 'cancel', 53 | ['ctrl-u'] = 'preview-half-page-up', 54 | ['ctrl-d'] = 'preview-half-page-down', 55 | } 56 | 57 | local fzf_opts = { 58 | "--ansi", -- works with --color=always in rg to show colors properly 59 | "--delimiter=':'", -- used by fzf to split "FIELD INDEX EXPRESSIONS" (':' is the default rg delimited) 60 | [[--preview ']]..preview_cmd..[[']], -- specify how the currently selected file should be previewed 61 | "--preview-window=hidden", -- don't open preview unless asked to 62 | [[--bind ']]..fzf_bindings..[[']], 63 | } 64 | 65 | -- Make the actual command 66 | local fzf = 'fzf '..table.concat(fzf_opts, ' ')..' >'..stdout_filename 67 | local term_cmd = rg..' | '..fzf 68 | 69 | vim.fn.termopen(term_cmd, { 70 | on_exit = function(job_id, exit_code) 71 | 72 | 73 | -- Avoid "Process exited with ..." 74 | -- (buffer is still valid iff it wasn't somehow deleted already) 75 | if api.nvim_buf_is_valid(new_buf_nr) then 76 | api.nvim_buf_delete(new_buf_nr, {}) 77 | end 78 | 79 | if(exit_code ~= 0) then 80 | -- most likely ^C 81 | return 82 | end 83 | 84 | local content = read_file(stdout_filename) 85 | local filename, lineno = rg_filename_and_lineno(content) 86 | 87 | -- open file at line 88 | vim.cmd('e '..'+'..lineno..' '..filename) 89 | 90 | -- center 91 | vim.api.nvim_feedkeys("zz", "n", false) 92 | 93 | os.remove(stdout_filename) 94 | end 95 | 96 | }) 97 | vim.cmd'startinsert' 98 | end 99 | 100 | 101 | return { rg = rg } 102 | -------------------------------------------------------------------------------- /nix/default.nix: -------------------------------------------------------------------------------- 1 | # Nix, with a nix.conf baked in 2 | { nix, symlinkJoin, makeWrapper }: 3 | 4 | symlinkJoin { 5 | name = "nix"; 6 | nativeBuildInputs = [ makeWrapper ]; 7 | paths = [ nix ]; 8 | postBuild = '' 9 | wrapProgram $out/bin/nix \ 10 | --set NIX_USER_CONF_FILES ${./nix.conf} 11 | ''; 12 | } 13 | -------------------------------------------------------------------------------- /nix/nix.conf: -------------------------------------------------------------------------------- 1 | experimental-features = nix-command flakes 2 | -------------------------------------------------------------------------------- /zshrc/default.nix: -------------------------------------------------------------------------------- 1 | # Because the path of the zshrc changes upon rebuild, we cannot source it 2 | # directly from the (vanilla) ~/.zshrc. 3 | # Instead we read it from ~/.nix-profile. 4 | { lib, runCommand, writeText, writeScriptBin, fzf, nix, cacert, nixpkgs-src }: 5 | let 6 | 7 | # official git completion, better & faster than the zsh defaults 8 | git-completion-zsh-src = builtins.fetchurl { 9 | url = "https://raw.githubusercontent.com/git/git/3c20acdf465ba211978108ca8507d41e62a016fd/contrib/completion/git-completion.zsh"; 10 | sha256 = "sha256:0cifmyc0rsf1pn0lr4qpkgwcb2l7dxk8nqbd7xdc9ij3jq34ijnf"; 11 | }; 12 | git-completion-zsh = runCommand "git-completion-zsh" { } '' 13 | mkdir -p $out/git-completion.zsh/ 14 | cat <${git-completion-zsh-src} > $out/git-completion.zsh/_git 15 | ''; 16 | 17 | # Write .zshrc to share/zshrc/zshrc 18 | zshrc = writeText "zshrc" 19 | (lib.concatStringsSep "\n" 20 | [ 21 | # Set up nix autocomplete 22 | '' 23 | fpath+=(${nix}/share/zsh/site-functions) 24 | fpath+=(${git-completion-zsh}/git-completion.zsh) 25 | '' 26 | (builtins.readFile ./zshrc) 27 | # Set up fzf terminal bindings 28 | '' 29 | source ${fzf}/share/fzf/completion.zsh 30 | source ${./fzf-key-bindings.zsh} 31 | '' 32 | # Set up useful env vars (NIX_PATH to have a set nixpkgs, 33 | # and SSL_CERT_FILE make sure https works) 34 | '' 35 | export NIX_PATH=nixpkgs=${nixpkgs-src} 36 | export SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt 37 | '' 38 | ] 39 | ); 40 | in 41 | 42 | runCommand "zshrc" { } '' 43 | mkdir -p $out/share/zshrc 44 | cp ${zshrc} $out/share/zshrc/zshrc 45 | '' 46 | -------------------------------------------------------------------------------- /zshrc/fzf-key-bindings.zsh: -------------------------------------------------------------------------------- 1 | # FZF keybindings, adapted from upstream 2 | # https://github.com/junegunn/fzf/blob/30a8ef28cdc1d23cf6956f57ca05bd28db515014/shell/key-bindings.zsh 3 | # ____ ____ 4 | # / __/___ / __/ 5 | # / /_/_ / / /_ 6 | # / __/ / /_/ __/ 7 | # /_/ /___/_/ key-bindings.zsh 8 | # 9 | # - $FZF_TMUX_OPTS 10 | # - $FZF_CTRL_T_COMMAND 11 | # - $FZF_CTRL_T_OPTS 12 | # - $FZF_CTRL_R_OPTS 13 | # - $FZF_ALT_C_COMMAND 14 | # - $FZF_ALT_C_OPTS 15 | 16 | 17 | # Key bindings 18 | # ------------ 19 | 20 | # The code at the top and the bottom of this file is the same as in completion.zsh. 21 | # Refer to that file for explanation. 22 | if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then 23 | __fzf_key_bindings_options="options=(${(j: :)${(kv)options[@]}})" 24 | else 25 | () { 26 | __fzf_key_bindings_options="setopt" 27 | 'local' '__fzf_opt' 28 | for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do 29 | if [[ -o "$__fzf_opt" ]]; then 30 | __fzf_key_bindings_options+=" -o $__fzf_opt" 31 | else 32 | __fzf_key_bindings_options+=" +o $__fzf_opt" 33 | fi 34 | done 35 | } 36 | fi 37 | 38 | 'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases' 39 | 40 | { 41 | if [[ -o interactive ]]; then 42 | 43 | __fzf_defaults() { 44 | # $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS 45 | # $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS 46 | echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1" 47 | command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null 48 | echo "${FZF_DEFAULT_OPTS-} $2" 49 | } 50 | 51 | # CTRL-T - Paste the selected file path(s) into the command line 52 | __fzf_select() { 53 | setopt localoptions pipefail no_aliases 2> /dev/null 54 | local item 55 | FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \ 56 | FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \ 57 | FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) "$@" < /dev/tty | while read -r item; do 58 | echo -n -E "${(q)item} " 59 | done 60 | local ret=$? 61 | echo 62 | return $ret 63 | } 64 | 65 | __fzfcmd() { 66 | [ -n "${TMUX_PANE-}" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "${FZF_TMUX_OPTS-}" ]; } && 67 | echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf" 68 | } 69 | 70 | fzf-file-widget() { 71 | LBUFFER="${LBUFFER}$(__fzf_select)" 72 | local ret=$? 73 | zle reset-prompt 74 | return $ret 75 | } 76 | if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then 77 | zle -N fzf-file-widget 78 | bindkey -M emacs '^T' fzf-file-widget 79 | bindkey -M vicmd '^T' fzf-file-widget 80 | bindkey -M viins '^T' fzf-file-widget 81 | fi 82 | 83 | # ALT-C - cd into the selected directory 84 | fzf-cd-widget() { 85 | setopt localoptions pipefail no_aliases 2> /dev/null 86 | local dir="$( 87 | FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} \ 88 | FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path" "${FZF_ALT_C_OPTS-} +m") \ 89 | FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) < /dev/tty)" 90 | if [[ -z "$dir" ]]; then 91 | zle redisplay 92 | return 0 93 | fi 94 | zle push-line # Clear buffer. Auto-restored on next prompt. 95 | BUFFER="builtin cd -- ${(q)dir:a}" 96 | zle accept-line 97 | local ret=$? 98 | unset dir # ensure this doesn't end up appearing in prompt expansion 99 | zle reset-prompt 100 | return $ret 101 | } 102 | if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then 103 | zle -N fzf-cd-widget 104 | bindkey -M emacs '\ec' fzf-cd-widget 105 | bindkey -M vicmd '\ec' fzf-cd-widget 106 | bindkey -M viins '\ec' fzf-cd-widget 107 | fi 108 | 109 | # CTRL-R - Paste the selected command from history into the command line 110 | fzf-history-widget() { 111 | local selected 112 | setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null 113 | # Ensure the associative history array, which maps event numbers to the full 114 | # history lines, is loaded, and that Perl is installed for multi-line output. 115 | if zmodload -F zsh/parameter p:history 2>/dev/null && (( ${#commands[perl]} )); then 116 | selected="$(printf '%s\t%s\000' "${(kv)history[@]}" | 117 | perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' | 118 | FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \ 119 | FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))" 120 | else 121 | selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' | 122 | FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \ 123 | FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))" 124 | fi 125 | local ret=$? 126 | if [ -n "$selected" ]; then 127 | if [[ $(awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then 128 | zle vi-fetch-history -n $MATCH 129 | else # selected is a custom query, not from history 130 | LBUFFER="$selected" 131 | fi 132 | fi 133 | zle reset-prompt 134 | return $ret 135 | } 136 | zle -N fzf-history-widget 137 | bindkey -M emacs '^R' fzf-history-widget 138 | bindkey -M vicmd '^R' fzf-history-widget 139 | bindkey -M viins '^R' fzf-history-widget 140 | fi 141 | 142 | ### start of custom homies key bindings 143 | # extra opts: 144 | # 145 | # - $FZF_ALT_G_OPTS 146 | 147 | # ALT-G - Paste the selected git branch(es) into the command line. If the LBUFFER 148 | # is empty, run a git checkout of the branch. 149 | fzf-git-br-widget() { 150 | setopt localoptions pipefail no_aliases 2> /dev/null 151 | local cmd="git for-each-ref --format='%(refname:short)' refs/heads/" 152 | local fzf_opts="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_G_OPTS" 153 | 154 | if [ -n "${LBUFFER}" ]; then 155 | fzf_opts="${fzf_opts} --multi --header='insert branch(es)' --prompt='$LBUFFER'" 156 | else 157 | fzf_opts="${fzf_opts} --header='change branch' --prompt='git switch '" 158 | fi 159 | 160 | local brs=() 161 | eval "$cmd" | FZF_DEFAULT_OPTS="$fzf_opts" $(__fzfcmd) | while read -r br; do 162 | brs+=("$br") 163 | done 164 | 165 | if [ -z "$brs" ]; then 166 | zle redisplay 167 | return 0 168 | fi 169 | 170 | local res="${(j[ ])brs}" 171 | local ret="0" 172 | if [ -n "${LBUFFER}" ]; then 173 | # if a command exists, just append 174 | LBUFFER="${LBUFFER}${res}" 175 | else 176 | # if there's no command, run git switch 177 | BUFFER="git switch ${res}" 178 | zle accept-line 179 | ret=$? 180 | fi 181 | zle reset-prompt 182 | return $ret 183 | } 184 | 185 | zle -N fzf-git-br-widget 186 | bindkey -M emacs '^[g' fzf-git-br-widget 187 | bindkey -M vicmd '^[g' fzf-git-br-widget 188 | bindkey -M viins '^[g' fzf-git-br-widget 189 | 190 | ### end of custom homies key bindings 191 | 192 | } always { 193 | eval $__fzf_key_bindings_options 194 | 'unset' '__fzf_key_bindings_options' 195 | } 196 | -------------------------------------------------------------------------------- /zshrc/zshrc: -------------------------------------------------------------------------------- 1 | # Add e.g. timestamps to zsh history 2 | setopt EXTENDED_HISTORY 3 | 4 | # Save command history 5 | HISTFILE=${HOME}/.zsh_history 6 | HISTSIZE=5000 7 | SAVEHIST=$HISTSIZE 8 | 9 | # Allow #... comments on prompt (and not just in scripts) 10 | setopt interactivecomments 11 | 12 | # share history across multiple zsh sessions 13 | setopt SHARE_HISTORY 14 | # append to history 15 | setopt APPEND_HISTORY 16 | # adds commands as they are typed, not at shell exit 17 | setopt INC_APPEND_HISTORY 18 | 19 | # we keep all duplicates (incl. timestamps) in case we need 20 | # to look them up, but we don't show them when using Ctrl+R 21 | 22 | # expire duplicates first 23 | setopt HIST_EXPIRE_DUPS_FIRST 24 | # ignore duplicates when searching 25 | setopt HIST_FIND_NO_DUPS 26 | # removes blank lines from history 27 | setopt HIST_REDUCE_BLANKS 28 | 29 | # Enable vi-mode command editing 30 | bindkey -v 31 | 32 | # The commands below set some key bindings. To figure out the code for a particular 33 | # key binding, use 'cat': 34 | # % cat 35 | # ^A^C 36 | 37 | # Restore Ctrl+A & Ctrl+E, which don't otherwise work in vi-mode 38 | bindkey "^A" vi-beginning-of-line 39 | bindkey "^E" vi-end-of-line 40 | 41 | # Ensure "del" key deletes the next char 42 | # (needed if terminal doesn't handle it directly) 43 | bindkey "^[[3~" delete-char 44 | bindkey -M vicmd "^[[3~" vi-delete-char 45 | 46 | alias gti="git" 47 | alias :e="nvim" 48 | alias gd="git diff" 49 | alias gdw="git diff --word-diff" 50 | alias tmp='cd $(mktemp -d)' 51 | alias ll='ls -alF' 52 | alias la='ls -A' 53 | alias l='ls -CF' 54 | 55 | # Add default FZF options to move up/down with jk 56 | export FZF_DEFAULT_OPTS='--bind alt-k:up,alt-j:down' 57 | 58 | # Open nvim, do different things depending on the dest 59 | function n { 60 | if [ "$#" -eq 0 ] 61 | then 62 | nvim 63 | elif [ "$#" -eq 1 ] 64 | then 65 | local dest="$1" 66 | if [ -d "$dest" ] 67 | then 68 | pushd "$dest" 69 | nvim 70 | popd 71 | fi 72 | else 73 | echo "expected 0 or 1 arguments, got $#" 74 | return 75 | fi 76 | } 77 | 78 | # Send a notification when the command has completed 79 | function ntfy { 80 | if eval "$@"; then 81 | kitten notify "Success 🟩" "$(basename $PWD): $@" 82 | else 83 | ret="$?" 84 | kitten notify "Failure 🟥 ($ret)" "$(basename $PWD): $@" 85 | return "$ret" 86 | fi 87 | } 88 | 89 | # Partial completion (/fo/bar -> /foo/bar) 90 | zstyle ':completion:*' list-suffixes 91 | zstyle ':completion:*' expand prefix suffix 92 | 93 | # Prompt that shows current dir and red on error. 94 | # %B %b: bold 95 | # %F{...} %f: color 96 | # %#: A ‘#’ if the shell is running with privileges, a ‘%’ if not. 97 | # %(n?..): ternary operator on %? (n is optional return code) 98 | # ref: https://zsh.sourceforge.io/Doc/Release/Prompt-Expansion.html#Conditional-Substrings-in-Prompts 99 | PS1="%B%F{blue}%~%f%b%(?.%#.%F{red}%#%f%B%b) " 100 | 101 | # Direnv 102 | eval "$(direnv hook zsh)" 103 | 104 | # Initialize completion 105 | autoload -Uz compinit && compinit 106 | --------------------------------------------------------------------------------