├── .github └── workflows │ └── test.yml ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix ├── hammerspoon ├── Spoons │ └── WindowHalfsAndThirds.spoon │ │ ├── docs.json │ │ └── init.lua └── init.lua └── modules ├── home.nix └── macos.nix /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Test Home Manager configuration." 2 | on: [push] 3 | jobs: 4 | tests: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | - uses: DeterminateSystems/nix-installer-action@main 9 | - uses: DeterminateSystems/magic-nix-cache-action@main 10 | - name: Install home-manager and realise home.nix 11 | run: | 12 | # Create profile folders home-manager expects to exist. 13 | nix profile list 14 | # Move the nix.conf file created by magic-nix-cache rather than fail. 15 | nix run home-manager/master -- switch --flake .#apearwin-ci -b backup 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dotfiles 2 | 3 | This is my collection of [configuration files](http://dotfiles.github.io/). 4 | 5 | It uses [home-manager][home-manager]—a [Nix][nix]-based tool—to install 6 | programs and create their configuration files based off the 7 | [`home.nix`](home.nix) file in this repository. I [wrote more about it in a 8 | blog post][nix-post]. 9 | 10 | (I used to [use GNU Stow][stow-post]. The last Stow-based commit was 11 | [`4f1feee1e`][stow-commit].) 12 | 13 | ## Usage 14 | 15 | Install [Nix][nix] with Nix Flake support enabled, for example by using the [Determinate Systems installer][nix-installer]. 16 | You should be able to run the `nix flake` command in a shell. 17 | 18 | Next, clone this repository. We'll use `~/Code/dotfiles`. 19 | 20 | ```shell 21 | $ git clone git@github.com:alexpearce/dotfiles.git ~/Code/dotfiles 22 | ``` 23 | 24 | The home-manager profile can then be built and activated: 25 | 26 | ```shell 27 | $ nix run home-manager/master -- switch --flake ~/Code/dotfiles#apearwin 28 | ``` 29 | 30 | To update dependencies: 31 | 32 | ```shell 33 | $ nix flake update ~/Code/dotfiles 34 | ``` 35 | 36 | ### Fish 37 | 38 | I like to set [fish][fish] as my default shell. On macOS this means: 39 | 40 | 1. Editing `/etc/shells` to include an entry for the home-manager-managed 41 | `fish` binary at `~/.nix-profile/bin/fish`. 42 | 2. Setting the default shell with `chsh -s ~/.nix-profile/bin/fish`. 43 | 44 | 45 | ### nix-darwin 46 | 47 | As an alternative to using home-manager alone, the configuration supports using 48 | it with [nix-darwin][nix-darwin]. 49 | 50 | The initial setup requires moving the Nix configuration file created by the 51 | Determinate Systems installer out of the way, so that nix-darwin can manage it 52 | for us. 53 | 54 | ``` 55 | sudo mv /etc/nix/nix.conf{,.before-nix-darwin} 56 | nix --extra-experimental-features 'nix-command flakes' run nix-darwin -- switch --flake (pwd) 57 | ``` 58 | 59 | Subsequent rebuilds, after configuration changes, are simpler. 60 | 61 | ``` 62 | darwin-rebuild switch --flake (pwd) 63 | ``` 64 | 65 | ## License 66 | 67 | [MIT](http://opensource.org/licenses/MIT). 68 | 69 | [nix]: https://nixos.org/ 70 | [nix-installer]: https://github.com/DeterminateSystems/nix-installer 71 | [home-manager]: https://github.com/nix-community/home-manager 72 | [fish]: https://fishshell.com/ 73 | [nix-darwin]: https://github.com/LnL7/nix-darwin 74 | 75 | [nix-post]: https://alexpearce.me/2021/07/managing-dotfiles-with-nix/ 76 | [stow-post]: https://alexpearce.me/2016/02/managing-dotfiles-with-stow/ 77 | [stow-commit]: https://github.com/alexpearce/dotfiles/tree/4f1feee1e4bc71f2ba5774af44eed1da774510a0 78 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "home-manager": { 4 | "inputs": { 5 | "nixpkgs": [ 6 | "nixpkgs" 7 | ] 8 | }, 9 | "locked": { 10 | "lastModified": 1723015306, 11 | "narHash": "sha256-jQnFEtH20/OsDPpx71ntZzGdRlpXhUENSQCGTjn//NA=", 12 | "owner": "nix-community", 13 | "repo": "home-manager", 14 | "rev": "b3d5ea65d88d67d4ec578ed11d4d2d51e3de525e", 15 | "type": "github" 16 | }, 17 | "original": { 18 | "owner": "nix-community", 19 | "repo": "home-manager", 20 | "type": "github" 21 | } 22 | }, 23 | "nix-darwin": { 24 | "inputs": { 25 | "nixpkgs": [ 26 | "nixpkgs" 27 | ] 28 | }, 29 | "locked": { 30 | "lastModified": 1722924007, 31 | "narHash": "sha256-+CQDamNwqO33REJLft8c26NbUi2Td083hq6SvAm2xkU=", 32 | "owner": "LnL7", 33 | "repo": "nix-darwin", 34 | "rev": "91010a5613ffd7ee23ee9263213157a1c422b705", 35 | "type": "github" 36 | }, 37 | "original": { 38 | "owner": "LnL7", 39 | "repo": "nix-darwin", 40 | "type": "github" 41 | } 42 | }, 43 | "nixpkgs": { 44 | "locked": { 45 | "lastModified": 1723221148, 46 | "narHash": "sha256-7pjpeQlZUNQ4eeVntytU3jkw9dFK3k1Htgk2iuXjaD8=", 47 | "owner": "NixOS", 48 | "repo": "nixpkgs", 49 | "rev": "154bcb95ad51bc257c2ce4043a725de6ca700ef6", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "NixOS", 54 | "ref": "nixpkgs-unstable", 55 | "repo": "nixpkgs", 56 | "type": "github" 57 | } 58 | }, 59 | "root": { 60 | "inputs": { 61 | "home-manager": "home-manager", 62 | "nix-darwin": "nix-darwin", 63 | "nixpkgs": "nixpkgs" 64 | } 65 | } 66 | }, 67 | "root": "root", 68 | "version": 7 69 | } 70 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Home Manager configuration"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 6 | nix-darwin = { 7 | url = "github:LnL7/nix-darwin"; 8 | inputs.nixpkgs.follows = "nixpkgs"; 9 | }; 10 | home-manager = { 11 | url = "github:nix-community/home-manager"; 12 | inputs.nixpkgs.follows = "nixpkgs"; 13 | }; 14 | }; 15 | 16 | outputs = { 17 | self, 18 | nixpkgs, 19 | nix-darwin, 20 | home-manager, 21 | }: let 22 | makeHomeManagerConfiguration = { 23 | system, 24 | username, 25 | homeDirectory ? "/Users/${username}", 26 | }: let 27 | pkgs = nixpkgs.legacyPackages.${system}; 28 | in 29 | home-manager.lib.homeManagerConfiguration { 30 | inherit pkgs; 31 | 32 | modules = [ 33 | ./modules/home.nix 34 | { 35 | home = { 36 | inherit homeDirectory username; 37 | stateVersion = "23.11"; 38 | }; 39 | } 40 | ]; 41 | }; 42 | in { 43 | formatter.aarch64-darwin = nixpkgs.legacyPackages.aarch64-darwin.alejandra; 44 | formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.alejandra; 45 | homeConfigurations.apearwin = makeHomeManagerConfiguration { 46 | system = "aarch64-darwin"; 47 | username = "apearwin"; 48 | }; 49 | homeConfigurations.apearwin-ci = makeHomeManagerConfiguration { 50 | system = "x86_64-linux"; 51 | username = "runner"; 52 | homeDirectory = "/home/runner"; 53 | }; 54 | darwinConfigurations.pearwin-laptop = nix-darwin.lib.darwinSystem { 55 | system = "aarch64-darwin"; 56 | modules = [ 57 | home-manager.darwinModules.home-manager 58 | ./modules/macos.nix 59 | ]; 60 | }; 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /hammerspoon/Spoons/WindowHalfsAndThirds.spoon/docs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Command": [], 4 | "Constant": [], 5 | "Constructor": [], 6 | "Deprecated": [], 7 | "Field": [], 8 | "Function": [], 9 | "Method": [ 10 | { 11 | "def": "WindowHalfsAndThirds:bindHotkeys(mapping)", 12 | "desc": "Binds hotkeys for WindowHalfsAndThirds", 13 | "doc": "Binds hotkeys for WindowHalfsAndThirds\n\nParameters:\n * mapping - A table containing hotkey objifier/key details for the following items:\n * left_half, right_half, top_half, bottom_half - resize to the corresponding half of the screen\n * third_left, third_right - resize to one horizontal-third of the screen and move left/right\n * third_up, third_down - resize to one vertical-third of the screen and move up/down\n * max - maximize the window\n * max_toggle - toggle maximization\n * left_third, middle_third_h, right_third - resize and move the window to the corresponding horizontal third of the screen\n * top_third, middle_third_v, bottom_third - resize and move the window to the corresponding vertical third of the screen\n * top_left, top_right, bottom_left, bottom_right - resize and move the window to the corresponding quarter of the screen\n * undo - restore window to position before last move\n * center - move window to center of screen\n * larger - grow window larger than its current size\n * smaller - shrink window smaller than its current size\n\nReturns:\n * the WindowHalfsAndThirds object", 14 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 15 | "lineno": "416", 16 | "name": "bindHotkeys", 17 | "parameters": [ 18 | " * mapping - A table containing hotkey objifier/key details for the following items:", 19 | " * left_half, right_half, top_half, bottom_half - resize to the corresponding half of the screen", 20 | " * third_left, third_right - resize to one horizontal-third of the screen and move left/right", 21 | " * third_up, third_down - resize to one vertical-third of the screen and move up/down", 22 | " * max - maximize the window", 23 | " * max_toggle - toggle maximization", 24 | " * left_third, middle_third_h, right_third - resize and move the window to the corresponding horizontal third of the screen", 25 | " * top_third, middle_third_v, bottom_third - resize and move the window to the corresponding vertical third of the screen", 26 | " * top_left, top_right, bottom_left, bottom_right - resize and move the window to the corresponding quarter of the screen", 27 | " * undo - restore window to position before last move", 28 | " * center - move window to center of screen", 29 | " * larger - grow window larger than its current size", 30 | " * smaller - shrink window smaller than its current size" 31 | ], 32 | "returns": [ 33 | " * the WindowHalfsAndThirds object" 34 | ], 35 | "signature": "WindowHalfsAndThirds:bindHotkeys(mapping)", 36 | "stripped_doc": "", 37 | "type": "Method" 38 | }, 39 | { 40 | "def": "WindowHalfsAndThirds:center(win)", 41 | "desc": "Center window on screen", 42 | "doc": "Center window on screen\n\nParameters:\n * win - hs.window to use, defaults to hs.window.focusedWindow()\n\nReturns:\n * the WindowHalfsAndThirds object", 43 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 44 | "lineno": "350", 45 | "name": "center", 46 | "parameters": [ 47 | " * win - hs.window to use, defaults to hs.window.focusedWindow()" 48 | ], 49 | "returns": [ 50 | " * the WindowHalfsAndThirds object" 51 | ], 52 | "signature": "WindowHalfsAndThirds:center(win)", 53 | "stripped_doc": "", 54 | "type": "Method" 55 | }, 56 | { 57 | "def": "WindowHalfsAndThirds:larger(win)", 58 | "desc": "Make win larger than its current size", 59 | "doc": "Make win larger than its current size\n\nParameters:\n * win - hs.window to use, defaults to hs.window.focusedWindow()\n\nReturns:\n * the WindowHalfsAndThirds object", 60 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 61 | "lineno": "368", 62 | "name": "larger", 63 | "parameters": [ 64 | " * win - hs.window to use, defaults to hs.window.focusedWindow()" 65 | ], 66 | "returns": [ 67 | " * the WindowHalfsAndThirds object" 68 | ], 69 | "signature": "WindowHalfsAndThirds:larger(win)", 70 | "stripped_doc": "", 71 | "type": "Method" 72 | }, 73 | { 74 | "def": "WindowHalfsAndThirds:leftHalf(win)", 75 | "desc": "Resize to the left half of the screen.", 76 | "doc": "Resize to the left half of the screen.\n\nParameters:\n * win - hs.window to use, defaults to hs.window.focusedWindow()\n\nReturns:\n * the WindowHalfsAndThirds object\n\nNotes:\n * Variations of this method exist for other operations. See WindowHalfsAndThirds:bindHotkeys for details:\n * .leftHalf .rightHalf .topHalf .bottomHalf .thirdLeft .thirdRight .leftThird .middleThirdH .rightThird\n * .thirdUp .thirdDown .topThird .middleThirdV .bottomThird .topLeft .topRight .bottomLeft .bottomRight\n * .maximize", 77 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 78 | "lineno": "268", 79 | "name": "leftHalf", 80 | "notes": [ 81 | " * Variations of this method exist for other operations. See WindowHalfsAndThirds:bindHotkeys for details:", 82 | " * .leftHalf .rightHalf .topHalf .bottomHalf .thirdLeft .thirdRight .leftThird .middleThirdH .rightThird", 83 | " * .thirdUp .thirdDown .topThird .middleThirdV .bottomThird .topLeft .topRight .bottomLeft .bottomRight", 84 | " * .maximize" 85 | ], 86 | "parameters": [ 87 | " * win - hs.window to use, defaults to hs.window.focusedWindow()" 88 | ], 89 | "returns": [ 90 | " * the WindowHalfsAndThirds object" 91 | ], 92 | "signature": "WindowHalfsAndThirds:leftHalf(win)", 93 | "stripped_doc": "", 94 | "type": "Method" 95 | }, 96 | { 97 | "def": "WindowHalfsAndThirds:smaller(win)", 98 | "desc": "Make win smaller than its current size", 99 | "doc": "Make win smaller than its current size\n\nParameters:\n * win - hs.window to use, defaults to hs.window.focusedWindow()\n\nReturns:\n * the WindowHalfsAndThirds object", 100 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 101 | "lineno": "392", 102 | "name": "smaller", 103 | "parameters": [ 104 | " * win - hs.window to use, defaults to hs.window.focusedWindow()" 105 | ], 106 | "returns": [ 107 | " * the WindowHalfsAndThirds object" 108 | ], 109 | "signature": "WindowHalfsAndThirds:smaller(win)", 110 | "stripped_doc": "", 111 | "type": "Method" 112 | }, 113 | { 114 | "def": "WindowHalfsAndThirds:toggleMaximized(win)", 115 | "desc": "Toggle win between its normal size, and being maximized", 116 | "doc": "Toggle win between its normal size, and being maximized\n\nParameters:\n * win - hs.window to use, defaults to hs.window.focusedWindow()\n\nReturns:\n * the WindowHalfsAndThirds object", 117 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 118 | "lineno": "313", 119 | "name": "toggleMaximized", 120 | "parameters": [ 121 | " * win - hs.window to use, defaults to hs.window.focusedWindow()" 122 | ], 123 | "returns": [ 124 | " * the WindowHalfsAndThirds object" 125 | ], 126 | "signature": "WindowHalfsAndThirds:toggleMaximized(win)", 127 | "stripped_doc": "", 128 | "type": "Method" 129 | }, 130 | { 131 | "def": "WindowHalfsAndThirds:undo(win)", 132 | "desc": "Undo window size changes for win if there've been any in WindowHalfsAndThirds.clear_cache_after_seconds", 133 | "doc": "Undo window size changes for win if there've been any in WindowHalfsAndThirds.clear_cache_after_seconds\n\nParameters:\n * win - hs.window to use, defaults to hs.window.focusedWindow()\n\nReturns:\n * the WindowHalfsAndThirds object", 134 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 135 | "lineno": "336", 136 | "name": "undo", 137 | "parameters": [ 138 | " * win - hs.window to use, defaults to hs.window.focusedWindow()" 139 | ], 140 | "returns": [ 141 | " * the WindowHalfsAndThirds object" 142 | ], 143 | "signature": "WindowHalfsAndThirds:undo(win)", 144 | "stripped_doc": "", 145 | "type": "Method" 146 | } 147 | ], 148 | "Variable": [ 149 | { 150 | "def": "WindowHalfsAndThirds.clear_cache_after_seconds", 151 | "desc": "We don't want our undo frame cache filling all available memory. Let's clear it after it hasn't been used for a while.", 152 | "doc": "We don't want our undo frame cache filling all available memory. Let's clear it after it hasn't been used for a while.", 153 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 154 | "lineno": "77", 155 | "name": "clear_cache_after_seconds", 156 | "signature": "WindowHalfsAndThirds.clear_cache_after_seconds", 157 | "stripped_doc": "", 158 | "type": "Variable" 159 | }, 160 | { 161 | "def": "WindowHalfsAndThirds.defaultHotkeys", 162 | "desc": "Table containing a sample set of hotkeys that can be", 163 | "doc": "Table containing a sample set of hotkeys that can be\nassigned to the different operations. These are not bound\nby default - if you want to use them you have to call:\n`spoon.WindowHalfsAndThirds:bindHotkeys(spoon.WindowHalfsAndThirds.defaultHotkeys)`\nafter loading the spoon. Value:\n```\n {\n left_half = { {\"ctrl\", \"cmd\"}, \"Left\" },\n right_half = { {\"ctrl\", \"cmd\"}, \"Right\" },\n top_half = { {\"ctrl\", \"cmd\"}, \"Up\" },\n bottom_half = { {\"ctrl\", \"cmd\"}, \"Down\" },\n third_left = { {\"ctrl\", \"alt\" }, \"Left\" },\n third_right = { {\"ctrl\", \"alt\" }, \"Right\" },\n third_up = { {\"ctrl\", \"alt\" }, \"Up\" },\n third_down = { {\"ctrl\", \"alt\" }, \"Down\" },\n top_left = { {\"ctrl\", \"cmd\"}, \"1\" },\n top_right = { {\"ctrl\", \"cmd\"}, \"2\" },\n bottom_left = { {\"ctrl\", \"cmd\"}, \"3\" },\n bottom_right= { {\"ctrl\", \"cmd\"}, \"4\" },\n max_toggle = { {\"ctrl\", \"alt\", \"cmd\"}, \"f\" },\n max = { {\"ctrl\", \"alt\", \"cmd\"}, \"Up\" },\n undo = { { \"alt\", \"cmd\"}, \"z\" },\n center = { { \"alt\", \"cmd\"}, \"c\" },\n larger = { { \"alt\", \"cmd\", \"shift\"}, \"Right\" },\n smaller = { { \"alt\", \"cmd\", \"shift\"}, \"Left\" },\n }\n```", 164 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 165 | "lineno": "22", 166 | "name": "defaultHotkeys", 167 | "signature": "WindowHalfsAndThirds.defaultHotkeys", 168 | "stripped_doc": "assigned to the different operations. These are not bound\nby default - if you want to use them you have to call:\n`spoon.WindowHalfsAndThirds:bindHotkeys(spoon.WindowHalfsAndThirds.defaultHotkeys)`\nafter loading the spoon. Value:\n```\n {\n left_half = { {\"ctrl\", \"cmd\"}, \"Left\" },\n right_half = { {\"ctrl\", \"cmd\"}, \"Right\" },\n top_half = { {\"ctrl\", \"cmd\"}, \"Up\" },\n bottom_half = { {\"ctrl\", \"cmd\"}, \"Down\" },\n third_left = { {\"ctrl\", \"alt\" }, \"Left\" },\n third_right = { {\"ctrl\", \"alt\" }, \"Right\" },\n third_up = { {\"ctrl\", \"alt\" }, \"Up\" },\n third_down = { {\"ctrl\", \"alt\" }, \"Down\" },\n top_left = { {\"ctrl\", \"cmd\"}, \"1\" },\n top_right = { {\"ctrl\", \"cmd\"}, \"2\" },\n bottom_left = { {\"ctrl\", \"cmd\"}, \"3\" },\n bottom_right= { {\"ctrl\", \"cmd\"}, \"4\" },\n max_toggle = { {\"ctrl\", \"alt\", \"cmd\"}, \"f\" },\n max = { {\"ctrl\", \"alt\", \"cmd\"}, \"Up\" },\n undo = { { \"alt\", \"cmd\"}, \"z\" },\n center = { { \"alt\", \"cmd\"}, \"c\" },\n larger = { { \"alt\", \"cmd\", \"shift\"}, \"Right\" },\n smaller = { { \"alt\", \"cmd\", \"shift\"}, \"Left\" },\n }\n```", 169 | "type": "Variable" 170 | }, 171 | { 172 | "def": "WindowHalfsAndThirds.logger", 173 | "desc": "Logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.", 174 | "doc": "Logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.", 175 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 176 | "lineno": "17", 177 | "name": "logger", 178 | "signature": "WindowHalfsAndThirds.logger", 179 | "stripped_doc": "", 180 | "type": "Variable" 181 | }, 182 | { 183 | "def": "WindowHalfsAndThirds.use_frame_correctness", 184 | "desc": "If `true`, set [setFrameCorrectness](http://www.hammerspoon.org/docs/hs.window.html#setFrameCorrectness) for some resizing operations which fail when the window extends beyonds screen boundaries. This may cause some jerkiness in the resizing, so experiment and determine if you need it. Defaults to `false`", 185 | "doc": "If `true`, set [setFrameCorrectness](http://www.hammerspoon.org/docs/hs.window.html#setFrameCorrectness) for some resizing operations which fail when the window extends beyonds screen boundaries. This may cause some jerkiness in the resizing, so experiment and determine if you need it. Defaults to `false`", 186 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 187 | "lineno": "72", 188 | "name": "use_frame_correctness", 189 | "signature": "WindowHalfsAndThirds.use_frame_correctness", 190 | "stripped_doc": "", 191 | "type": "Variable" 192 | } 193 | ], 194 | "desc": "Simple window movement and resizing, focusing on half- and third-of-screen sizes", 195 | "doc": "Simple window movement and resizing, focusing on half- and third-of-screen sizes\n\nDownload: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/WindowHalfsAndThirds.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/WindowHalfsAndThirds.spoon.zip)", 196 | "items": [ 197 | { 198 | "def": "WindowHalfsAndThirds:bindHotkeys(mapping)", 199 | "desc": "Binds hotkeys for WindowHalfsAndThirds", 200 | "doc": "Binds hotkeys for WindowHalfsAndThirds\n\nParameters:\n * mapping - A table containing hotkey objifier/key details for the following items:\n * left_half, right_half, top_half, bottom_half - resize to the corresponding half of the screen\n * third_left, third_right - resize to one horizontal-third of the screen and move left/right\n * third_up, third_down - resize to one vertical-third of the screen and move up/down\n * max - maximize the window\n * max_toggle - toggle maximization\n * left_third, middle_third_h, right_third - resize and move the window to the corresponding horizontal third of the screen\n * top_third, middle_third_v, bottom_third - resize and move the window to the corresponding vertical third of the screen\n * top_left, top_right, bottom_left, bottom_right - resize and move the window to the corresponding quarter of the screen\n * undo - restore window to position before last move\n * center - move window to center of screen\n * larger - grow window larger than its current size\n * smaller - shrink window smaller than its current size\n\nReturns:\n * the WindowHalfsAndThirds object", 201 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 202 | "lineno": "416", 203 | "name": "bindHotkeys", 204 | "parameters": [ 205 | " * mapping - A table containing hotkey objifier/key details for the following items:", 206 | " * left_half, right_half, top_half, bottom_half - resize to the corresponding half of the screen", 207 | " * third_left, third_right - resize to one horizontal-third of the screen and move left/right", 208 | " * third_up, third_down - resize to one vertical-third of the screen and move up/down", 209 | " * max - maximize the window", 210 | " * max_toggle - toggle maximization", 211 | " * left_third, middle_third_h, right_third - resize and move the window to the corresponding horizontal third of the screen", 212 | " * top_third, middle_third_v, bottom_third - resize and move the window to the corresponding vertical third of the screen", 213 | " * top_left, top_right, bottom_left, bottom_right - resize and move the window to the corresponding quarter of the screen", 214 | " * undo - restore window to position before last move", 215 | " * center - move window to center of screen", 216 | " * larger - grow window larger than its current size", 217 | " * smaller - shrink window smaller than its current size" 218 | ], 219 | "returns": [ 220 | " * the WindowHalfsAndThirds object" 221 | ], 222 | "signature": "WindowHalfsAndThirds:bindHotkeys(mapping)", 223 | "stripped_doc": "", 224 | "type": "Method" 225 | }, 226 | { 227 | "def": "WindowHalfsAndThirds:center(win)", 228 | "desc": "Center window on screen", 229 | "doc": "Center window on screen\n\nParameters:\n * win - hs.window to use, defaults to hs.window.focusedWindow()\n\nReturns:\n * the WindowHalfsAndThirds object", 230 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 231 | "lineno": "350", 232 | "name": "center", 233 | "parameters": [ 234 | " * win - hs.window to use, defaults to hs.window.focusedWindow()" 235 | ], 236 | "returns": [ 237 | " * the WindowHalfsAndThirds object" 238 | ], 239 | "signature": "WindowHalfsAndThirds:center(win)", 240 | "stripped_doc": "", 241 | "type": "Method" 242 | }, 243 | { 244 | "def": "WindowHalfsAndThirds.clear_cache_after_seconds", 245 | "desc": "We don't want our undo frame cache filling all available memory. Let's clear it after it hasn't been used for a while.", 246 | "doc": "We don't want our undo frame cache filling all available memory. Let's clear it after it hasn't been used for a while.", 247 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 248 | "lineno": "77", 249 | "name": "clear_cache_after_seconds", 250 | "signature": "WindowHalfsAndThirds.clear_cache_after_seconds", 251 | "stripped_doc": "", 252 | "type": "Variable" 253 | }, 254 | { 255 | "def": "WindowHalfsAndThirds.defaultHotkeys", 256 | "desc": "Table containing a sample set of hotkeys that can be", 257 | "doc": "Table containing a sample set of hotkeys that can be\nassigned to the different operations. These are not bound\nby default - if you want to use them you have to call:\n`spoon.WindowHalfsAndThirds:bindHotkeys(spoon.WindowHalfsAndThirds.defaultHotkeys)`\nafter loading the spoon. Value:\n```\n {\n left_half = { {\"ctrl\", \"cmd\"}, \"Left\" },\n right_half = { {\"ctrl\", \"cmd\"}, \"Right\" },\n top_half = { {\"ctrl\", \"cmd\"}, \"Up\" },\n bottom_half = { {\"ctrl\", \"cmd\"}, \"Down\" },\n third_left = { {\"ctrl\", \"alt\" }, \"Left\" },\n third_right = { {\"ctrl\", \"alt\" }, \"Right\" },\n third_up = { {\"ctrl\", \"alt\" }, \"Up\" },\n third_down = { {\"ctrl\", \"alt\" }, \"Down\" },\n top_left = { {\"ctrl\", \"cmd\"}, \"1\" },\n top_right = { {\"ctrl\", \"cmd\"}, \"2\" },\n bottom_left = { {\"ctrl\", \"cmd\"}, \"3\" },\n bottom_right= { {\"ctrl\", \"cmd\"}, \"4\" },\n max_toggle = { {\"ctrl\", \"alt\", \"cmd\"}, \"f\" },\n max = { {\"ctrl\", \"alt\", \"cmd\"}, \"Up\" },\n undo = { { \"alt\", \"cmd\"}, \"z\" },\n center = { { \"alt\", \"cmd\"}, \"c\" },\n larger = { { \"alt\", \"cmd\", \"shift\"}, \"Right\" },\n smaller = { { \"alt\", \"cmd\", \"shift\"}, \"Left\" },\n }\n```", 258 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 259 | "lineno": "22", 260 | "name": "defaultHotkeys", 261 | "signature": "WindowHalfsAndThirds.defaultHotkeys", 262 | "stripped_doc": "assigned to the different operations. These are not bound\nby default - if you want to use them you have to call:\n`spoon.WindowHalfsAndThirds:bindHotkeys(spoon.WindowHalfsAndThirds.defaultHotkeys)`\nafter loading the spoon. Value:\n```\n {\n left_half = { {\"ctrl\", \"cmd\"}, \"Left\" },\n right_half = { {\"ctrl\", \"cmd\"}, \"Right\" },\n top_half = { {\"ctrl\", \"cmd\"}, \"Up\" },\n bottom_half = { {\"ctrl\", \"cmd\"}, \"Down\" },\n third_left = { {\"ctrl\", \"alt\" }, \"Left\" },\n third_right = { {\"ctrl\", \"alt\" }, \"Right\" },\n third_up = { {\"ctrl\", \"alt\" }, \"Up\" },\n third_down = { {\"ctrl\", \"alt\" }, \"Down\" },\n top_left = { {\"ctrl\", \"cmd\"}, \"1\" },\n top_right = { {\"ctrl\", \"cmd\"}, \"2\" },\n bottom_left = { {\"ctrl\", \"cmd\"}, \"3\" },\n bottom_right= { {\"ctrl\", \"cmd\"}, \"4\" },\n max_toggle = { {\"ctrl\", \"alt\", \"cmd\"}, \"f\" },\n max = { {\"ctrl\", \"alt\", \"cmd\"}, \"Up\" },\n undo = { { \"alt\", \"cmd\"}, \"z\" },\n center = { { \"alt\", \"cmd\"}, \"c\" },\n larger = { { \"alt\", \"cmd\", \"shift\"}, \"Right\" },\n smaller = { { \"alt\", \"cmd\", \"shift\"}, \"Left\" },\n }\n```", 263 | "type": "Variable" 264 | }, 265 | { 266 | "def": "WindowHalfsAndThirds:larger(win)", 267 | "desc": "Make win larger than its current size", 268 | "doc": "Make win larger than its current size\n\nParameters:\n * win - hs.window to use, defaults to hs.window.focusedWindow()\n\nReturns:\n * the WindowHalfsAndThirds object", 269 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 270 | "lineno": "368", 271 | "name": "larger", 272 | "parameters": [ 273 | " * win - hs.window to use, defaults to hs.window.focusedWindow()" 274 | ], 275 | "returns": [ 276 | " * the WindowHalfsAndThirds object" 277 | ], 278 | "signature": "WindowHalfsAndThirds:larger(win)", 279 | "stripped_doc": "", 280 | "type": "Method" 281 | }, 282 | { 283 | "def": "WindowHalfsAndThirds:leftHalf(win)", 284 | "desc": "Resize to the left half of the screen.", 285 | "doc": "Resize to the left half of the screen.\n\nParameters:\n * win - hs.window to use, defaults to hs.window.focusedWindow()\n\nReturns:\n * the WindowHalfsAndThirds object\n\nNotes:\n * Variations of this method exist for other operations. See WindowHalfsAndThirds:bindHotkeys for details:\n * .leftHalf .rightHalf .topHalf .bottomHalf .thirdLeft .thirdRight .leftThird .middleThirdH .rightThird\n * .thirdUp .thirdDown .topThird .middleThirdV .bottomThird .topLeft .topRight .bottomLeft .bottomRight\n * .maximize", 286 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 287 | "lineno": "268", 288 | "name": "leftHalf", 289 | "notes": [ 290 | " * Variations of this method exist for other operations. See WindowHalfsAndThirds:bindHotkeys for details:", 291 | " * .leftHalf .rightHalf .topHalf .bottomHalf .thirdLeft .thirdRight .leftThird .middleThirdH .rightThird", 292 | " * .thirdUp .thirdDown .topThird .middleThirdV .bottomThird .topLeft .topRight .bottomLeft .bottomRight", 293 | " * .maximize" 294 | ], 295 | "parameters": [ 296 | " * win - hs.window to use, defaults to hs.window.focusedWindow()" 297 | ], 298 | "returns": [ 299 | " * the WindowHalfsAndThirds object" 300 | ], 301 | "signature": "WindowHalfsAndThirds:leftHalf(win)", 302 | "stripped_doc": "", 303 | "type": "Method" 304 | }, 305 | { 306 | "def": "WindowHalfsAndThirds.logger", 307 | "desc": "Logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.", 308 | "doc": "Logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.", 309 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 310 | "lineno": "17", 311 | "name": "logger", 312 | "signature": "WindowHalfsAndThirds.logger", 313 | "stripped_doc": "", 314 | "type": "Variable" 315 | }, 316 | { 317 | "def": "WindowHalfsAndThirds:smaller(win)", 318 | "desc": "Make win smaller than its current size", 319 | "doc": "Make win smaller than its current size\n\nParameters:\n * win - hs.window to use, defaults to hs.window.focusedWindow()\n\nReturns:\n * the WindowHalfsAndThirds object", 320 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 321 | "lineno": "392", 322 | "name": "smaller", 323 | "parameters": [ 324 | " * win - hs.window to use, defaults to hs.window.focusedWindow()" 325 | ], 326 | "returns": [ 327 | " * the WindowHalfsAndThirds object" 328 | ], 329 | "signature": "WindowHalfsAndThirds:smaller(win)", 330 | "stripped_doc": "", 331 | "type": "Method" 332 | }, 333 | { 334 | "def": "WindowHalfsAndThirds:toggleMaximized(win)", 335 | "desc": "Toggle win between its normal size, and being maximized", 336 | "doc": "Toggle win between its normal size, and being maximized\n\nParameters:\n * win - hs.window to use, defaults to hs.window.focusedWindow()\n\nReturns:\n * the WindowHalfsAndThirds object", 337 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 338 | "lineno": "313", 339 | "name": "toggleMaximized", 340 | "parameters": [ 341 | " * win - hs.window to use, defaults to hs.window.focusedWindow()" 342 | ], 343 | "returns": [ 344 | " * the WindowHalfsAndThirds object" 345 | ], 346 | "signature": "WindowHalfsAndThirds:toggleMaximized(win)", 347 | "stripped_doc": "", 348 | "type": "Method" 349 | }, 350 | { 351 | "def": "WindowHalfsAndThirds:undo(win)", 352 | "desc": "Undo window size changes for win if there've been any in WindowHalfsAndThirds.clear_cache_after_seconds", 353 | "doc": "Undo window size changes for win if there've been any in WindowHalfsAndThirds.clear_cache_after_seconds\n\nParameters:\n * win - hs.window to use, defaults to hs.window.focusedWindow()\n\nReturns:\n * the WindowHalfsAndThirds object", 354 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 355 | "lineno": "336", 356 | "name": "undo", 357 | "parameters": [ 358 | " * win - hs.window to use, defaults to hs.window.focusedWindow()" 359 | ], 360 | "returns": [ 361 | " * the WindowHalfsAndThirds object" 362 | ], 363 | "signature": "WindowHalfsAndThirds:undo(win)", 364 | "stripped_doc": "", 365 | "type": "Method" 366 | }, 367 | { 368 | "def": "WindowHalfsAndThirds.use_frame_correctness", 369 | "desc": "If `true`, set [setFrameCorrectness](http://www.hammerspoon.org/docs/hs.window.html#setFrameCorrectness) for some resizing operations which fail when the window extends beyonds screen boundaries. This may cause some jerkiness in the resizing, so experiment and determine if you need it. Defaults to `false`", 370 | "doc": "If `true`, set [setFrameCorrectness](http://www.hammerspoon.org/docs/hs.window.html#setFrameCorrectness) for some resizing operations which fail when the window extends beyonds screen boundaries. This may cause some jerkiness in the resizing, so experiment and determine if you need it. Defaults to `false`", 371 | "file": "Source/WindowHalfsAndThirds.spoon//init.lua", 372 | "lineno": "72", 373 | "name": "use_frame_correctness", 374 | "signature": "WindowHalfsAndThirds.use_frame_correctness", 375 | "stripped_doc": "", 376 | "type": "Variable" 377 | } 378 | ], 379 | "name": "WindowHalfsAndThirds", 380 | "stripped_doc": "\nDownload: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/WindowHalfsAndThirds.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/WindowHalfsAndThirds.spoon.zip)", 381 | "submodules": [], 382 | "type": "Module" 383 | } 384 | ] -------------------------------------------------------------------------------- /hammerspoon/Spoons/WindowHalfsAndThirds.spoon/init.lua: -------------------------------------------------------------------------------- 1 | --- === WindowHalfsAndThirds === 2 | --- 3 | --- Simple window movement and resizing, focusing on half- and third-of-screen sizes 4 | --- 5 | --- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/WindowHalfsAndThirds.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/WindowHalfsAndThirds.spoon.zip) 6 | 7 | local obj={} 8 | obj.__index = obj 9 | 10 | -- Metadata 11 | obj.name = "WindowHalfsAndThirds" 12 | obj.version = "0.2" 13 | obj.author = "Diego Zamboni " 14 | obj.homepage = "https://github.com/Hammerspoon/Spoons" 15 | obj.license = "MIT - https://opensource.org/licenses/MIT" 16 | 17 | --- WindowHalfsAndThirds.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('WindowHalfsAndThirds') 21 | 22 | --- WindowHalfsAndThirds.defaultHotkeys 23 | --- Variable 24 | --- Table containing a sample set of hotkeys that can be 25 | --- assigned to the different operations. These are not bound 26 | --- by default - if you want to use them you have to call: 27 | --- `spoon.WindowHalfsAndThirds:bindHotkeys(spoon.WindowHalfsAndThirds.defaultHotkeys)` 28 | --- after loading the spoon. Value: 29 | --- ``` 30 | --- { 31 | --- left_half = { {"ctrl", "cmd"}, "Left" }, 32 | --- right_half = { {"ctrl", "cmd"}, "Right" }, 33 | --- top_half = { {"ctrl", "cmd"}, "Up" }, 34 | --- bottom_half = { {"ctrl", "cmd"}, "Down" }, 35 | --- third_left = { {"ctrl", "alt" }, "Left" }, 36 | --- third_right = { {"ctrl", "alt" }, "Right" }, 37 | --- third_up = { {"ctrl", "alt" }, "Up" }, 38 | --- third_down = { {"ctrl", "alt" }, "Down" }, 39 | --- top_left = { {"ctrl", "cmd"}, "1" }, 40 | --- top_right = { {"ctrl", "cmd"}, "2" }, 41 | --- bottom_left = { {"ctrl", "cmd"}, "3" }, 42 | --- bottom_right= { {"ctrl", "cmd"}, "4" }, 43 | --- max_toggle = { {"ctrl", "alt", "cmd"}, "f" }, 44 | --- max = { {"ctrl", "alt", "cmd"}, "Up" }, 45 | --- undo = { { "alt", "cmd"}, "z" }, 46 | --- center = { { "alt", "cmd"}, "c" }, 47 | --- larger = { { "alt", "cmd", "shift"}, "Right" }, 48 | --- smaller = { { "alt", "cmd", "shift"}, "Left" }, 49 | --- } 50 | --- ``` 51 | obj.defaultHotkeys = { 52 | left_half = { {"ctrl", "cmd"}, "Left" }, 53 | right_half = { {"ctrl", "cmd"}, "Right" }, 54 | top_half = { {"ctrl", "cmd"}, "Up" }, 55 | bottom_half = { {"ctrl", "cmd"}, "Down" }, 56 | third_left = { {"ctrl", "alt" }, "Left" }, 57 | third_right = { {"ctrl", "alt" }, "Right" }, 58 | third_up = { {"ctrl", "alt" }, "Up" }, 59 | third_down = { {"ctrl", "alt" }, "Down" }, 60 | top_left = { {"ctrl", "cmd"}, "1" }, 61 | top_right = { {"ctrl", "cmd"}, "2" }, 62 | bottom_left = { {"ctrl", "cmd"}, "3" }, 63 | bottom_right = { {"ctrl", "cmd"}, "4" }, 64 | max_toggle = { {"ctrl", "alt", "cmd"}, "f" }, 65 | max = { {"ctrl", "alt", "cmd"}, "Up" }, 66 | undo = { { "alt", "cmd"}, "z" }, 67 | center = { { "alt", "cmd"}, "c" }, 68 | larger = { { "alt", "cmd", "shift"}, "Right" }, 69 | smaller = { { "alt", "cmd", "shift"}, "Left" }, 70 | } 71 | 72 | --- WindowHalfsAndThirds.use_frame_correctness 73 | --- Variable 74 | --- If `true`, set [setFrameCorrectness](http://www.hammerspoon.org/docs/hs.window.html#setFrameCorrectness) for some resizing operations which fail when the window extends beyonds screen boundaries. This may cause some jerkiness in the resizing, so experiment and determine if you need it. Defaults to `false` 75 | obj.use_frame_correctness = false 76 | 77 | --- WindowHalfsAndThirds.clear_cache_after_seconds 78 | --- Variable 79 | --- We don't want our undo frame cache filling all available memory. Let's clear it after it hasn't been used for a while. 80 | obj.clear_cache_after_seconds = 60 81 | 82 | -- Internal terminology: 83 | -- `actions` are the things hotkeys are bound to and express a user desire (eg. `third_left`: move a third further left 84 | -- than the current `window_state`). See the keys of obj._window_moves or the keys of action_to_method_map in 85 | -- :bindHotkeys() for the available actions 86 | -- `window_states` are states a window may be currently in (eg. `left_third`: the leftmost horizontal third of the screen) 87 | -- sometimes `actions` and `window_states` share a name (eg. `left_half`) 88 | -- sometimes `actions` and `window_states` don't share a name (`third_left`: `left_third`, `middle_third_h`, `right_third`) 89 | -- 90 | -- `window_state_names` are states windows can be in (so since `third_left` implies a relative move there is no `third_left` 91 | -- `window_state_name`, only a `third_left` `action`) 92 | -- `window_state_rects` are `{x,y,w,l}` `hs.geometry.unitrect` tables defining those states 93 | obj._window_state_name_to_rect = { 94 | left_half = {0.00,0.00,0.50,1.00}, -- two decimal places required for `window_state_rect_strings` to match 95 | left_40 = {0.00,0.00,0.40,1.00}, 96 | left_60 = {0.00,0.00,0.60,1.00}, 97 | right_half = {0.50,0.00,0.50,1.00}, 98 | right_40 = {0.60,0.00,0.40,1.00}, 99 | right_60 = {0.40,0.00,0.60,1.00}, 100 | top_half = {0.00,0.00,1.00,0.50}, 101 | top_40 = {0.00,0.00,1.00,0.40}, 102 | top_60 = {0.00,0.00,1.00,0.60}, 103 | bottom_half = {0.00,0.50,1.00,0.50}, 104 | bottom_40 = {0.00,0.60,1.00,0.40}, 105 | bottom_60 = {0.00,0.40,1.00,0.60}, 106 | left_third = {0.00,0.00,0.33,1.00}, 107 | left_two_third = {0.00,0.00,0.67,1.00}, 108 | middle_third_h = {0.33,0.00,0.34,1.00}, 109 | right_third = {0.67,0.00,0.33,1.00}, 110 | right_two_third = {0.33,0.00,0.67,1.00}, 111 | top_third = {0.00,0.00,1.00,0.33}, 112 | top_two_third = {0.00,0.00,1.00,0.67}, 113 | middle_third_v = {0.00,0.33,1.00,0.34}, 114 | bottom_third = {0.00,0.67,1.00,0.33}, 115 | bottom_two_third = {0.00,0.33,1.00,0.67}, 116 | top_left = {0.00,0.00,0.50,0.50}, 117 | top_right = {0.50,0.00,0.50,0.50}, 118 | bottom_left = {0.00,0.50,0.50,0.50}, 119 | bottom_right = {0.50,0.50,0.50,0.50}, 120 | max = {0.00,0.00,1.00,1.00}, 121 | } 122 | 123 | -- `window_state_rect_strings` because Lua does table identity comparisons in table keys instead of table content 124 | -- comparisons; that is, table["0.00,0.00,0.50,1.00"] works where table[{0.00,0.00,0.50,1.00}] doesn't 125 | obj._window_state_rect_string_to_name = {} 126 | for state,rect in pairs(obj._window_state_name_to_rect) do 127 | obj._window_state_rect_string_to_name[table.concat(rect,",")] = state 128 | end 129 | 130 | -- `window_moves` are `action` to `window_state_name` pairs 131 | -- `action` = {[`window_state_name` default], [if current `window_state_name`] = [then new `window_state_name`], ...} 132 | -- so if a user takes `action` from `window_state_name` with a key, move to the paired value `window_state_name`, 133 | -- or the default `window_state_name` the current `window_state_name` isn't a key for that `action` 134 | -- (example below) 135 | obj._window_moves = { 136 | left_half = {"left_half", left_half = "left_40", left_40 = "left_60"}, 137 | half_left = {"left_half"}, 138 | -- if `action` `left_half` is requested without a match in this table, move to `left_half` 139 | -- if `action` `left_half` is requested from `window_state_name` `left_half`, move to `left_40` 140 | -- if `action` `left_half` is requested from `window_state_name` `left_40`, move to `left_60` 141 | -- rationale: if a user requests a move to `left_half` and they're already there they're expressing a user need 142 | -- and it's our job to work out what that need is. Let's give them some other `left_half`ish options. 143 | right_half = {"right_half", right_half = "right_40", right_40 = "right_60"}, 144 | half_right = {"right_half"}, 145 | top_half = {"top_half", top_half = "top_40", top_40 = "top_60"}, 146 | half_top = {"top_half"}, 147 | bottom_half = {"bottom_half", bottom_half = "bottom_40", bottom_40 = "bottom_60"}, 148 | half_bottom = {"bottom_half"}, 149 | third_left = {"left_third", left_third = "right_third", middle_third_h = "left_third", right_third = "middle_third_h", 150 | right_half = "middle_third_h"}, 151 | third_right = {"right_third", left_third = "middle_third_h", middle_third_h = "right_third", right_third = "left_third", 152 | left_half = "middle_third_h"}, 153 | left_third = {"left_third"}, -- `left_third` is a `window_state` specific `action`, not a relative action 154 | -- it is not part of the default hotkey mapping 155 | left_two_third = {"left_two_third"}, 156 | middle_third_h = {"middle_third_h"}, 157 | right_third = {"right_third"}, 158 | right_two_third = {"right_two_third"}, 159 | third_up = {"top_third", top_third = "bottom_third", middle_third_v = "top_third", bottom_third = "middle_third_v", 160 | bottom_half = "middle_third_v"}, 161 | third_down = {"bottom_third", top_third = "middle_third_v", middle_third_v = "bottom_third", bottom_third = "top_third", 162 | top_half = "middle_third_v"}, 163 | top_third = {"top_third"}, 164 | top_two_third = {"top_two_third"}, 165 | middle_third_v = {"middle_third_v"}, 166 | bottom_third = {"bottom_third"}, 167 | bottom_two_third = {"bottom_two_third"}, 168 | top_left = {"top_left"}, 169 | top_right = {"top_right"}, 170 | bottom_left = {"bottom_left"}, 171 | bottom_right = {"bottom_right"}, 172 | max = {"max"}, 173 | } 174 | 175 | -- Private utility functions 176 | 177 | local function round(x, places) 178 | local places = places or 0 179 | local x = x * 10^places 180 | return (x + 0.5 - (x + 0.5) % 1) / 10^places 181 | end 182 | 183 | local function current_window_rect(win) 184 | local win = win or hs.window.focusedWindow() 185 | local ur, r = win:screen():toUnitRect(win:frame()), round 186 | return {r(ur.x,2), r(ur.y,2), r(ur.w,2), r(ur.h,2)} -- an hs.geometry.unitrect table 187 | end 188 | 189 | local function current_window_state_name(win) 190 | local win = win or hs.window.focusedWindow() 191 | return obj._window_state_rect_string_to_name[table.concat(current_window_rect(win),",")] 192 | end 193 | 194 | local function cacheWindow(win, move_to) 195 | local win = win or hs.window.focusedWindow() 196 | if (not win) or (not win:id()) then return end 197 | obj._frameCache[win:id()] = win:frame() 198 | obj._frameCacheClearTimer:start() 199 | obj._lastMoveCache[win:id()] = move_to 200 | return win 201 | end 202 | 203 | local function restoreWindowFromCache(win) 204 | local win = win or hs.window.focusedWindow() 205 | if (not win) or (not win:id()) or (not obj._frameCache[win:id()]) then return end 206 | local current_window_frame = win:frame() -- enable undoing an undo action 207 | win:setFrame(obj._frameCache[win:id()]) 208 | obj._frameCache[win:id()] = current_window_frame -- enable undoing an undo action 209 | return win 210 | end 211 | 212 | function obj.script_path_raw(n) 213 | return (debug.getinfo(n or 2, "S").source) 214 | end 215 | function obj.script_path(n) 216 | local str = obj.script_path_raw(n or 2):sub(2) 217 | return str:match("(.*/)") 218 | end 219 | function obj.generate_docs_json() 220 | io.open(obj.script_path().."docs.json","w"):write(hs.doc.builder.genJSON(obj.script_path())):close() 221 | end 222 | 223 | -- Internal functions to store/restore the current value of setFrameCorrectness. 224 | local function _setFrameCorrectness() 225 | obj._savedFrameCorrectness = hs.window.setFrameCorrectness 226 | hs.window.setFrameCorrectness = obj.use_frame_correctness 227 | end 228 | local function _restoreFrameCorrectness() 229 | hs.window.setFrameCorrectness = obj._savedFrameCorrectness 230 | end 231 | 232 | 233 | -- -------------------------------------------------------------------- 234 | -- Base window resizing and moving functions 235 | -- -------------------------------------------------------------------- 236 | 237 | 238 | -- Resize current window to different parts of the screen 239 | -- If use_frame_correctness_preference is true, then use setFrameCorrectness according to the 240 | -- configured value of `WindowHalfsAndThirds.use_frame_correctness` 241 | function obj.resizeCurrentWindow(how, use_frame_correctness_preference) 242 | local win = hs.window.focusedWindow() 243 | if not win then return end 244 | 245 | local move_to = obj._lastMoveCache[win:id()] and obj._window_moves[how][obj._lastMoveCache[win:id()]] or 246 | obj._window_moves[how][current_window_state_name(win)] or obj._window_moves[how][1] 247 | if not move_to then 248 | obj.logger.e("I don't know how to move ".. how .." from ".. (obj._lastMoveCache[win:id()] or 249 | current_window_state_name(win))) 250 | end 251 | if current_window_state_name(win) == move_to then return end 252 | local move_to_rect = obj._window_state_name_to_rect[move_to] 253 | if not move_to_rect then 254 | obj.logger.e("I don't know how to move to ".. move_to) 255 | return 256 | end 257 | 258 | if use_frame_correctness_preference then _setFrameCorrectness() end 259 | cacheWindow(win, move_to) 260 | win:move(move_to_rect) 261 | if use_frame_correctness_preference then _restoreFrameCorrectness() end 262 | end 263 | 264 | -- -------------------------------------------------------------------- 265 | -- Action functions for obj.resizeCurrentWindow, for the hotkeys 266 | -- -------------------------------------------------------------------- 267 | 268 | --- WindowHalfsAndThirds:leftHalf(win) 269 | --- Method 270 | --- Resize to the left half of the screen. 271 | --- 272 | --- Parameters: 273 | --- * win - hs.window to use, defaults to hs.window.focusedWindow() 274 | --- 275 | --- Returns: 276 | --- * the WindowHalfsAndThirds object 277 | --- 278 | --- Notes: 279 | --- * Variations of this method exist for other operations. See WindowHalfsAndThirds:bindHotkeys for details: 280 | --- * .leftHalf .rightHalf .topHalf .bottomHalf .thirdLeft .thirdRight .leftThird .middleThirdH .rightThird 281 | --- * .thirdUp .thirdDown .topThird .middleThirdV .bottomThird .topLeft .topRight .bottomLeft .bottomRight 282 | --- * .maximize 283 | 284 | obj.leftHalf = hs.fnutils.partial(obj.resizeCurrentWindow, "left_half") 285 | obj.halfLeft = hs.fnutils.partial(obj.resizeCurrentWindow, "half_left") 286 | obj.rightHalf = hs.fnutils.partial(obj.resizeCurrentWindow, "right_half") 287 | obj.halfRight = hs.fnutils.partial(obj.resizeCurrentWindow, "half_right") 288 | obj.topHalf = hs.fnutils.partial(obj.resizeCurrentWindow, "top_half") 289 | obj.halfTop = hs.fnutils.partial(obj.resizeCurrentWindow, "half_top") 290 | obj.bottomHalf = hs.fnutils.partial(obj.resizeCurrentWindow, "bottom_half") 291 | obj.halfBottom = hs.fnutils.partial(obj.resizeCurrentWindow, "half_bottom") 292 | obj.thirdLeft = hs.fnutils.partial(obj.resizeCurrentWindow, "third_left") 293 | obj.thirdRight = hs.fnutils.partial(obj.resizeCurrentWindow, "third_right") 294 | obj.leftThird = hs.fnutils.partial(obj.resizeCurrentWindow, "left_third") 295 | obj.leftTwoThird = hs.fnutils.partial(obj.resizeCurrentWindow, "left_two_third") 296 | obj.middleThirdH = hs.fnutils.partial(obj.resizeCurrentWindow, "middle_third_h") 297 | obj.rightThird = hs.fnutils.partial(obj.resizeCurrentWindow, "right_third") 298 | obj.rightTwoThird = hs.fnutils.partial(obj.resizeCurrentWindow, "right_two_third") 299 | obj.thirdUp = hs.fnutils.partial(obj.resizeCurrentWindow, "third_up") 300 | obj.thirdDown = hs.fnutils.partial(obj.resizeCurrentWindow, "third_down") 301 | obj.topThird = hs.fnutils.partial(obj.resizeCurrentWindow, "top_third") 302 | obj.topTwoThird = hs.fnutils.partial(obj.resizeCurrentWindow, "top_two_third") 303 | obj.middleThirdV = hs.fnutils.partial(obj.resizeCurrentWindow, "middle_third_v") 304 | obj.bottomThird = hs.fnutils.partial(obj.resizeCurrentWindow, "bottom_third") 305 | obj.bottomTwoThird = hs.fnutils.partial(obj.resizeCurrentWindow, "bottom_two_third") 306 | obj.topLeft = hs.fnutils.partial(obj.resizeCurrentWindow, "top_left") 307 | obj.topRight = hs.fnutils.partial(obj.resizeCurrentWindow, "top_right") 308 | obj.bottomLeft = hs.fnutils.partial(obj.resizeCurrentWindow, "bottom_left") 309 | obj.bottomRight = hs.fnutils.partial(obj.resizeCurrentWindow, "bottom_right") 310 | obj.maximize = hs.fnutils.partial(obj.resizeCurrentWindow, "max", true) 311 | 312 | 313 | --- WindowHalfsAndThirds:toggleMaximized(win) 314 | --- Method 315 | --- Toggle win between its normal size, and being maximized 316 | --- 317 | --- Parameters: 318 | --- * win - hs.window to use, defaults to hs.window.focusedWindow() 319 | --- 320 | --- Returns: 321 | --- * the WindowHalfsAndThirds object 322 | function obj.toggleMaximized(win) 323 | local win = win or hs.window.focusedWindow() 324 | if (not win) or (not win:id()) then 325 | return 326 | end 327 | if current_window_state_name() == "max" then 328 | restoreWindowFromCache(win) 329 | else 330 | cacheWindow(win, "max") 331 | win:maximize() 332 | end 333 | return obj 334 | end 335 | 336 | --- WindowHalfsAndThirds:undo(win) 337 | --- Method 338 | --- Undo window size changes for win if there've been any in WindowHalfsAndThirds.clear_cache_after_seconds 339 | --- 340 | --- Parameters: 341 | --- * win - hs.window to use, defaults to hs.window.focusedWindow() 342 | --- 343 | --- Returns: 344 | --- * the WindowHalfsAndThirds object 345 | function obj.undo(win) 346 | restoreWindowFromCache(win) 347 | return obj 348 | end 349 | 350 | --- WindowHalfsAndThirds:center(win) 351 | --- Method 352 | --- Center window on screen 353 | --- 354 | --- Parameters: 355 | --- * win - hs.window to use, defaults to hs.window.focusedWindow() 356 | --- 357 | --- Returns: 358 | --- * the WindowHalfsAndThirds object 359 | function obj.center(win) 360 | local win = win or hs.window.focusedWindow() 361 | if win then 362 | cacheWindow(win, "center") 363 | win:centerOnScreen() 364 | end 365 | return obj 366 | end 367 | 368 | --- WindowHalfsAndThirds:larger(win) 369 | --- Method 370 | --- Make win larger than its current size 371 | --- 372 | --- Parameters: 373 | --- * win - hs.window to use, defaults to hs.window.focusedWindow() 374 | --- 375 | --- Returns: 376 | --- * the WindowHalfsAndThirds object 377 | function obj.larger(win) 378 | local win = win or hs.window.focusedWindow() 379 | if win then 380 | cacheWindow(win, nil) 381 | local cw = current_window_rect(win) 382 | local move_to_rect = {} 383 | move_to_rect[1] = math.max(cw[1]-0.02,0) 384 | move_to_rect[2] = math.max(cw[2]-0.02,0) 385 | move_to_rect[3] = math.min(cw[3]+0.04,1 - move_to_rect[1]) 386 | move_to_rect[4] = math.min(cw[4]+0.04,1 - move_to_rect[2]) 387 | win:move(move_to_rect) 388 | end 389 | return obj 390 | end 391 | 392 | --- WindowHalfsAndThirds:smaller(win) 393 | --- Method 394 | --- Make win smaller than its current size 395 | --- 396 | --- Parameters: 397 | --- * win - hs.window to use, defaults to hs.window.focusedWindow() 398 | --- 399 | --- Returns: 400 | --- * the WindowHalfsAndThirds object 401 | function obj.smaller(win) 402 | local win = win or hs.window.focusedWindow() 403 | if win then 404 | cacheWindow(win, nil) 405 | local cw = current_window_rect(win) 406 | local move_to_rect = {} 407 | move_to_rect[3] = math.max(cw[3]-0.04,0.1) 408 | move_to_rect[4] = cw[4] > 0.95 and 1 or math.max(cw[4]-0.04,0.1) -- some windows (MacVim) don't size to 1 409 | move_to_rect[1] = math.min(cw[1]+0.02,1 - move_to_rect[3]) 410 | move_to_rect[2] = cw[2] == 0 and 0 or math.min(cw[2]+0.02,1 - move_to_rect[4]) 411 | win:move(move_to_rect) 412 | end 413 | return obj 414 | end 415 | 416 | --- WindowHalfsAndThirds:bindHotkeys(mapping) 417 | --- Method 418 | --- Binds hotkeys for WindowHalfsAndThirds 419 | --- 420 | --- Parameters: 421 | --- * mapping - A table containing hotkey objifier/key details for the following items: 422 | --- * left_half, right_half, top_half, bottom_half - resize to the corresponding half of the screen 423 | --- * third_left, third_right - resize to one horizontal-third of the screen and move left/right 424 | --- * third_up, third_down - resize to one vertical-third of the screen and move up/down 425 | --- * max - maximize the window 426 | --- * max_toggle - toggle maximization 427 | --- * left_third, middle_third_h, right_third - resize and move the window to the corresponding horizontal third of the screen 428 | --- * top_third, middle_third_v, bottom_third - resize and move the window to the corresponding vertical third of the screen 429 | --- * top_left, top_right, bottom_left, bottom_right - resize and move the window to the corresponding quarter of the screen 430 | --- * undo - restore window to position before last move 431 | --- * center - move window to center of screen 432 | --- * larger - grow window larger than its current size 433 | --- * smaller - shrink window smaller than its current size 434 | --- 435 | --- Returns: 436 | --- * the WindowHalfsAndThirds object 437 | function obj:bindHotkeys(mapping) 438 | local action_to_method_map = { 439 | left_half = self.leftHalf, 440 | half_left = self.halfLeft, 441 | right_half = self.rightHalf, 442 | half_right = self.halfRight, 443 | top_half = self.topHalf, 444 | half_top = self.halfTop, 445 | bottom_half = self.bottomHalf, 446 | half_bottom = self.halfBottom, 447 | third_left = self.thirdLeft, 448 | third_right = self.thirdRight, 449 | third_up = self.thirdUp, 450 | third_down = self.thirdDown, 451 | max = self.maximize, 452 | max_toggle = self.toggleMaximized, 453 | left_third = self.leftThird, 454 | left_two_third = self.leftTwoThird, 455 | middle_third_h = self.middleThirdH, 456 | right_third = self.rightThird, 457 | right_two_third = self.rightTwoThird, 458 | top_third = self.topThird, 459 | top_two_third = self.topTwoThird, 460 | middle_third_v = self.middleThirdV, 461 | bottom_third = self.bottomThird, 462 | bottom_two_third = self.bottomTwoThird, 463 | top_left = self.topLeft, 464 | top_right = self.topRight, 465 | bottom_left = self.bottomLeft, 466 | bottom_right = self.bottomRight, 467 | undo = self.undo, 468 | center = self.center, 469 | larger = self.larger, 470 | smaller = self.smaller, 471 | -- Legacy (`action` names changed for internal consistency, old names preserved) 472 | left = self.leftHalf, 473 | right = self.rightHalf, 474 | top = self.topHalf, 475 | bottom = self.bottomHalf, 476 | } 477 | hs.spoons.bindHotkeysToSpec(action_to_method_map, mapping) 478 | return self 479 | end 480 | 481 | function obj:init() 482 | self._frameCache = {} 483 | obj._lastMoveCache = {} 484 | self._frameCacheClearTimer = hs.timer.delayed.new(obj.clear_cache_after_seconds, 485 | function() obj._frameCache = {}; obj._lastMoveCache = {} end) 486 | end 487 | 488 | 489 | -- Legacy (names changed for internal consistency, old names preserved) 490 | function obj.oneThirdLeft() obj.thirdLeft() end 491 | function obj.oneThirdRight() obj.thirdRight() end 492 | function obj.oneThirdUp() obj.thirdUp() end 493 | function obj.onethirdDown() obj.thirdDown() end 494 | 495 | 496 | return obj 497 | 498 | -------------------------------------------------------------------------------- /hammerspoon/init.lua: -------------------------------------------------------------------------------- 1 | -- Reload Hammerspoon configuration. 2 | hs.hotkey.bind({"⌘", "⌥", "⌃"}, "h", function() 3 | hs.reload() 4 | hs.console.clearConsole() 5 | end) 6 | 7 | -- Enter hotkey mode with option-space. 8 | leader = hs.hotkey.modal.new({"⌥"}, "space") 9 | 10 | -- Exit hotkey mode with escape. 11 | leader:bind("", "escape", nil, function() leader:exit() end, nil) 12 | 13 | -- Hide focused application. 14 | leader:bind("", "h", function() hs.application.frontmostApplication():hide() leader:exit() end, nil) 15 | 16 | -- Launch applications. 17 | leader:bind("", "f", function() hs.application.launchOrFocus("Firefox") leader:exit() end, nil) 18 | leader:bind("", "o", function() hs.application.launchOrFocus("Obsidian") leader:exit() end, nil) 19 | leader:bind("", "s", function() hs.application.launchOrFocus("Slack") leader:exit() end, nil) 20 | leader:bind("", "t", function() hs.application.launchOrFocus("WezTerm") leader:exit() end, nil) 21 | 22 | -- Window management. 23 | hs.loadSpoon("WindowHalfsAndThirds") 24 | wm = spoon.WindowHalfsAndThirds 25 | 26 | -- Move focused window to left half. 27 | leader:bind("", "left", function() wm.leftHalf() leader:exit() end, nil) 28 | -- Move focused window to right half. 29 | leader:bind("", "right", function() wm.rightHalf() leader:exit() end, nil) 30 | -- Move focused window to top half. 31 | leader:bind("", "up", function() wm.topHalf() leader:exit() end, nil) 32 | -- Move focused window to bottom half. 33 | leader:bind("", "down", function() wm.bottomHalf() leader:exit() end, nil) 34 | -- Center focused window. 35 | leader:bind("", "c", function() wm.center() leader:exit() end, nil) 36 | -- Undo previous window movement. 37 | leader:bind("", "delete", function() wm.undo() leader:exit() end, nil) 38 | 39 | -- Move focused window to screen to the left. 40 | leader:bind("⌥", "left", function() 41 | local window = hs.application.frontmostApplication():focusedWindow() 42 | window:moveToScreen(window:screen():toWest()) 43 | leader:exit() 44 | end, nil) 45 | 46 | -- Move focused window to screen to the right. 47 | leader:bind("⌥", "right", function() 48 | local window = hs.application.frontmostApplication():focusedWindow() 49 | window:moveToScreen(window:screen():toEast()) 50 | leader:exit() 51 | end, nil) 52 | -------------------------------------------------------------------------------- /modules/home.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | ... 5 | }: { 6 | home = { 7 | # Specify packages not explicitly configured below 8 | packages = with pkgs; [ 9 | colima 10 | docker 11 | entr 12 | fd 13 | httpie 14 | hyperfine 15 | imagemagick 16 | jq 17 | nodejs 18 | pandoc 19 | ripgrep 20 | tree 21 | tree-sitter 22 | ]; 23 | sessionVariables = { 24 | EDITOR = "hx"; 25 | }; 26 | }; 27 | 28 | programs = { 29 | bat = { 30 | enable = true; 31 | config = { 32 | theme = "GitHub"; 33 | italic-text = "always"; 34 | }; 35 | }; 36 | 37 | direnv = { 38 | enable = true; 39 | nix-direnv = { 40 | enable = true; 41 | }; 42 | }; 43 | 44 | eza = { 45 | enable = true; 46 | }; 47 | 48 | fish = { 49 | enable = true; 50 | plugins = [ 51 | { 52 | name = "fzf"; 53 | src = pkgs.fetchFromGitHub { 54 | owner = "PatrickF1"; 55 | repo = "fzf.fish"; 56 | rev = "6d8e962f3ed84e42583cec1ec4861d4f0e6c4eb3"; 57 | sha256 = "sha256-0rnd8oJzLw8x/U7OLqoOMQpK81gRc7DTxZRSHxN9YlM"; 58 | }; 59 | } 60 | # Need this when using Fish as a default macOS shell in order to pick 61 | # up ~/.nix-profile/bin 62 | { 63 | name = "nix-env"; 64 | src = pkgs.fetchFromGitHub { 65 | owner = "lilyball"; 66 | repo = "nix-env.fish"; 67 | rev = "00c6cc762427efe08ac0bd0d1b1d12048d3ca727"; 68 | sha256 = "1hrl22dd0aaszdanhvddvqz3aq40jp9zi2zn0v1hjnf7fx4bgpma"; 69 | }; 70 | } 71 | ]; 72 | shellInit = '' 73 | # Set syntax highlighting colours; var names defined here: 74 | # http://fishshell.com/docs/current/index.html#variables-color 75 | set fish_color_normal normal 76 | set fish_color_command white 77 | set fish_color_quote brgreen 78 | set fish_color_redirection brblue 79 | set fish_color_end white 80 | set fish_color_error -o brred 81 | set fish_color_param brpurple 82 | set fish_color_comment --italics brblack 83 | set fish_color_match cyan 84 | set fish_color_search_match --background=brblack 85 | set fish_color_operator cyan 86 | set fish_color_escape white 87 | set fish_color_autosuggestion brblack 88 | ''; 89 | # Send OSC 133 escape sequences to signal prompt and ouput start and end. 90 | interactiveShellInit = '' 91 | function terminal_integration_preprompt --on-event fish_prompt 92 | printf "\033]133;A;\007" 93 | end 94 | 95 | # TODO not used yet. 96 | function terminal_integration_postprompt 97 | printf "\033]133;B;\007" 98 | end 99 | 100 | function terminal_integration_preexec --on-event fish_preexec 101 | printf "\033]133;C;\007" 102 | end 103 | 104 | function terminal_integration_postexec --on-event fish_postexec 105 | printf "\033]133;D;\007" 106 | end 107 | ''; 108 | shellAliases = { 109 | ipython = "ipython --no-banner"; 110 | rm = "rm -i"; 111 | cp = "cp -i"; 112 | mv = "mv -i"; 113 | mkdir = "mkdir -p"; 114 | du = "du -hs"; 115 | }; 116 | # Abbreviate commonly used functions 117 | # An abbreviation will expand after or is hit 118 | shellAbbrs = { 119 | b = "bat"; 120 | ip = "ipython"; 121 | g = "git"; 122 | ga = "git add"; 123 | gap = "git add -p"; 124 | gb = "git branch"; 125 | gc = "git commit"; 126 | gca = "git commit --amend"; 127 | gcan = "git commit --amend --no-edit"; 128 | gcm = "git commit -m"; 129 | gcl = "git clone"; 130 | gd = "git diff"; 131 | gds = "git diff --staged"; 132 | gl = "git prettylog"; 133 | gp = "git push"; 134 | gpf = "git push --force-with-lease"; 135 | gpl = "git pull"; 136 | gplp = "git pull --prune"; 137 | gr = "git restore"; 138 | grs = "git restore --staged"; 139 | grb = "git rebase"; 140 | grba = "git rebase --abort"; 141 | grbc = "git rebase --continue"; 142 | grbi = "git rebase -i"; 143 | gs = "git status -s -b"; 144 | gst = "git stash"; 145 | gstp = "git stash pop"; 146 | gsts = "git stash show -p"; 147 | gstx = "git stash drop"; 148 | gsw = "git switch"; 149 | gswc = "git switch -c"; 150 | gswm = "git switch main"; 151 | h = "http"; 152 | hme = "home-manager --flake ~/Code/dotfiles edit"; 153 | hms = "home-manager --flake ~/Code/dotfiles switch"; 154 | iexm = "iex -S mix"; 155 | m = "make"; 156 | o = "open"; 157 | p = "python3"; 158 | }; 159 | functions = { 160 | ctrlp = { 161 | description = "Launch Helix file finder from the shell"; 162 | argumentNames = "hidden"; 163 | body = '' 164 | if test -n "$hidden" 165 | # TODO can't find a way to toggle hidden files in Helix yet 166 | echo 'Hidden searching not yet supported.' 167 | exit 1 168 | else 169 | hx . 170 | end 171 | ''; 172 | }; 173 | fish_greeting = { 174 | description = "Greeting to show when starting a fish shell"; 175 | body = ""; 176 | }; 177 | fish_user_key_bindings = { 178 | description = "Set custom key bindings"; 179 | body = '' 180 | bind \cp ctrlp 181 | bind \cl 'ctrlp --hidden' 182 | ''; 183 | }; 184 | mkdcd = { 185 | description = "Make a directory tree and enter it"; 186 | body = "mkdir -p $argv[1]; and cd $argv[1]"; 187 | }; 188 | }; 189 | }; 190 | 191 | fzf = { 192 | enable = true; 193 | enableFishIntegration = false; 194 | }; 195 | 196 | gh = { 197 | enable = true; 198 | settings = { 199 | aliases = { 200 | co = "pr checkout"; 201 | pv = "pr view"; 202 | }; 203 | git_protocol = "ssh"; 204 | }; 205 | }; 206 | 207 | git = { 208 | enable = true; 209 | userName = "Alex Pearwin"; 210 | userEmail = "alex@pearwin.com"; 211 | aliases = { 212 | prettylog = "log --graph --abbrev-commit --decorate --date=relative --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all"; 213 | }; 214 | delta = { 215 | enable = true; 216 | # Custom `delta` executable whichs sets light/dark feature 217 | # depending on OS appearance. 218 | package = pkgs.writeShellApplication { 219 | name = "delta"; 220 | runtimeInputs = [pkgs.delta]; 221 | text = '' 222 | features=$(defaults read -globalDomain AppleInterfaceStyle > /dev/null 2>&1 && printf dark-mode || printf light-mode) 223 | env DELTA_FEATURES="$features" ${pkgs.delta}/bin/delta "$@" 224 | ''; 225 | }; 226 | options = { 227 | light-mode = { 228 | light = true; 229 | syntax-theme = "GitHub"; 230 | }; 231 | dark-mode = { 232 | dark = true; 233 | syntax-theme = "OneHalfDark"; 234 | }; 235 | features = "light-mode"; 236 | navigate = true; 237 | line-numbers = true; 238 | }; 239 | }; 240 | extraConfig = { 241 | core = { 242 | # If git uses `ssh` from Nix the macOS-specific configuration in 243 | # `~/.ssh/config` won't be seen as valid 244 | # https://github.com/NixOS/nixpkgs/issues/15686#issuecomment-865928923 245 | sshCommand = "/usr/bin/ssh"; 246 | }; 247 | color = { 248 | ui = true; 249 | }; 250 | diff = { 251 | colorMoved = "default"; 252 | }; 253 | merge = { 254 | conflictstyle = "zdiff3"; 255 | }; 256 | push = { 257 | default = "current"; 258 | }; 259 | pull = { 260 | ff = "only"; 261 | }; 262 | init = { 263 | defaultBranch = "main"; 264 | }; 265 | # Clone git repos with URLs like "gh:alexpearce/dotfiles" 266 | url."git@github.com:" = { 267 | insteadOf = "gh:"; 268 | pushInsteadOf = "gh:"; 269 | }; 270 | }; 271 | ignores = [ 272 | ".*.swp" 273 | ".bundle" 274 | "vendor/bundle" 275 | ".DS_Store" 276 | "Icon" 277 | "*.pyc" 278 | ".direnv" 279 | ]; 280 | }; 281 | 282 | helix = { 283 | enable = true; 284 | settings = { 285 | theme = "onelight"; 286 | editor = { 287 | bufferline = "multiple"; 288 | color-modes = true; 289 | line-number = "relative"; 290 | lsp.display-messages = true; 291 | }; 292 | keys.normal = { 293 | space.w = ":w"; 294 | space.q = ":q"; 295 | space.x = ":x"; 296 | }; 297 | }; 298 | languages.language = [ 299 | { 300 | name = "elixir"; 301 | auto-format = true; 302 | } 303 | ]; 304 | }; 305 | 306 | home-manager.enable = true; 307 | 308 | starship = { 309 | enable = true; 310 | enableFishIntegration = true; 311 | settings = { 312 | elixir.disabled = true; 313 | nodejs.disabled = true; 314 | nix_shell.disabled = true; 315 | package.disabled = true; 316 | docker_context.disabled = true; 317 | git_branch.format = "[$branch(:$remote_branch)]($style) "; 318 | git_branch.style = "bold black"; 319 | git_status.style = "bold black"; 320 | character.success_symbol = "[❯](bold black)"; 321 | }; 322 | }; 323 | 324 | wezterm = { 325 | enable = true; 326 | extraConfig = '' 327 | -- https://wezfurlong.org/wezterm/config/lua/window/get_appearance.html 328 | function color_scheme_for_appearance(appearance) 329 | if appearance:find 'Dark' then 330 | return 'OneDark (base16)' 331 | else 332 | return 'One Light (base16)' 333 | end 334 | end 335 | 336 | wezterm.on('window-config-reloaded', function(window, pane) 337 | local overrides = window:get_config_overrides() or {} 338 | local appearance = window:get_appearance() 339 | local color_scheme = color_scheme_for_appearance(appearance) 340 | if overrides.color_scheme ~= color_scheme then 341 | overrides.color_scheme = color_scheme 342 | window:set_config_overrides(overrides) 343 | end 344 | end) 345 | 346 | return { 347 | bold_brightens_ansi_colors = "BrightAndBold", 348 | color_scheme = "One Light (base16)", 349 | default_prog = { "${config.home.profileDirectory}/bin/fish" }, 350 | font = wezterm.font("JetBrains Mono"), 351 | font_size = 14.0, 352 | keys = { 353 | -- Close panes with shift+w. 354 | { 355 | key = "w", 356 | mods = "CMD|SHIFT", 357 | action = wezterm.action.CloseCurrentPane { confirm = true }, 358 | }, 359 | -- Vim-style hjkl navigation between panes. 360 | { 361 | key = "h", 362 | mods = "CMD|OPT", 363 | action = wezterm.action.ActivatePaneDirection("Left"), 364 | }, 365 | { 366 | key = "j", 367 | mods = "CMD|OPT", 368 | action = wezterm.action.ActivatePaneDirection("Down"), 369 | }, 370 | { 371 | key = "k", 372 | mods = "CMD|OPT", 373 | action = wezterm.action.ActivatePaneDirection("Up"), 374 | }, 375 | { 376 | key = "l", 377 | mods = "CMD|OPT", 378 | action = wezterm.action.ActivatePaneDirection("Right"), 379 | }, 380 | -- iTerm-style cmd-d/cmd-shift-d pane splitting. 381 | { 382 | key = "d", 383 | mods = "CMD", 384 | action = wezterm.action.SplitPane({direction = "Right"}) 385 | }, 386 | { 387 | key = "d", 388 | mods = "CMD|SHIFT", 389 | action = wezterm.action.SplitPane({direction = "Down"}) 390 | }, 391 | -- iTerm-style cmd-shift-enter pane zoom toggle. 392 | { 393 | key = "Enter", 394 | mods = "CMD|SHIFT", 395 | action = wezterm.action.TogglePaneZoomState, 396 | }, 397 | -- Scroll between prompts. 398 | { 399 | key = "UpArrow", 400 | mods = "CMD|SHIFT", 401 | action = wezterm.action.ScrollToPrompt(-1), 402 | }, 403 | { 404 | key = "DownArrow", 405 | mods = "CMD|SHIFT", 406 | action = wezterm.action.ScrollToPrompt(1), 407 | }, 408 | -- Type a hash symbol. 409 | { 410 | key = "3", 411 | mods = "OPT", 412 | action = wezterm.action.SendString("#"), 413 | }, 414 | }, 415 | hide_tab_bar_if_only_one_tab = true, 416 | mouse_bindings = { 417 | { 418 | event = { Down = { streak = 3, button = 'Left' } }, 419 | action = wezterm.action.SelectTextAtMouseCursor 'SemanticZone', 420 | mods = 'NONE', 421 | }, 422 | }, 423 | scrollback_lines = 100000, 424 | use_fancy_tab_bar = true, 425 | } 426 | ''; 427 | }; 428 | 429 | zoxide = { 430 | enable = true; 431 | enableFishIntegration = true; 432 | }; 433 | }; 434 | 435 | # Requires telling Hammerspoon to look for configuration in the right place: 436 | # defaults write org.hammerspoon.Hammerspoon MJConfigFile "${XDG_CONFIG_HOME:-$HOME/.config}/hammerspoon/init.lua" 437 | xdg.configFile.hammerspoon = { 438 | source = ../hammerspoon; 439 | recursive = true; 440 | }; 441 | } 442 | -------------------------------------------------------------------------------- /modules/macos.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: let 2 | username = "apearwin"; 3 | homeDirectory = "/Users/${username}"; 4 | in { 5 | users.users."${username}" = { 6 | home = homeDirectory; 7 | }; 8 | 9 | # Make nix available to default macOS shell. 10 | programs.zsh.enable = true; 11 | # Expose nix-provided paths to fish. 12 | programs.fish.enable = true; 13 | 14 | home-manager = { 15 | useGlobalPkgs = true; 16 | useUserPackages = true; 17 | users."${username}" = { 18 | imports = [./home.nix]; 19 | home = { 20 | inherit homeDirectory username; 21 | stateVersion = "23.11"; 22 | }; 23 | }; 24 | }; 25 | 26 | fonts.packages = [ 27 | (pkgs.nerdfonts.override { 28 | fonts = [ 29 | "JetBrainsMono" 30 | ]; 31 | }) 32 | ]; 33 | 34 | nix.extraOptions = '' 35 | # Settings copied from those written by 36 | # https://github.com/DeterminateSystems/nix-installer, version 0.11.0. 37 | extra-nix-path = nixpkgs=flake:nixpkgs 38 | bash-prompt-prefix = (nix:$name)\040 39 | experimental-features = nix-command flakes auto-allocate-uids 40 | build-users-group = nixbld 41 | ''; 42 | services.nix-daemon.enable = true; 43 | 44 | system.stateVersion = 4; 45 | } 46 | --------------------------------------------------------------------------------