Compare commits

..

533 Commits

Author SHA1 Message Date
Jim Myhrberg
919c5d48de feat(bin): add ollama-for-gitbutler wrapper script
Run a dedicated ollama instance for GitButler on a separate port
(11435) with permissive origins and long keep-alive, avoiding
conflicts with any primary ollama server.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:13:21 +00:00
d3ef296e84 feat(mise/tools): add convex CLI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:29:33 +00:00
39f2d3094c feat(hammerspoon/noct): add ChatGPT toggle on Cmd+Ctrl+4
Move ChatGPT binding inline with other numbered hotkeys for
consistent ordering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:28:54 +00:00
e2d2e4ec13 feat(install): add agentic repo clone command
Clone jimeh/agentic repo to ~/.config/agentic, following the same
pattern as the private dotfiles clone.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:28:12 +00:00
Jim Myhrberg
9bf0ffd5d7 chore(gitignore): add .claude/worktrees/ pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 09:43:54 +00:00
Jim Myhrberg
ae207e49bd feat(zshenv): add ToolHive bin to PATH 2026-02-11 15:28:47 +00:00
a76c67542f chore(gitignore): add .vscode/settings.local.json pattern
Ignore VS Code local settings files across all projects, consistent
with the existing settings.json ignore pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 09:25:44 +00:00
6900c31b3a feat(hammerspoon/noct): add Conductor app toggle on Cmd+Ctrl+3
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 22:42:54 +00:00
Jim Myhrberg
83d043f186 feat(hammerspoon/hosts): update app toggle keybindings for improved access
Added keybinding for Codex and updated bindings for Conductor and ChatGPT Atlas. This enhances the app toggle functionality for better user experience.
2026-02-09 20:34:58 +00:00
eb3ecdce75 feat(hammerspoon/noct): prioritize VS Code over Insiders for Cmd+Ctrl+W
Switched to using regular VS Code as the primary app, with Insiders as
the fallback. Reflects shift to stable VS Code as primary editor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 19:34:57 +00:00
9e5784d741 feat(mise/tools): add skills CLI tool
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 17:48:14 +00:00
051731d110 feat(zsh): add iTerm2 shell integration support
Source iTerm2 shell integration script when available, enabling
features like marks, badges, and status bar integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 17:47:44 +00:00
c83cd5e62b chore(hammerspoon): remove window management margins
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 13:40:24 +00:00
54fac673e0 feat(hammerspoon/noct): add Codex app toggle keybinding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 13:39:58 +00:00
e542560a67 feat(mise/tools): add oxfmt, oxlint, and opencode
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 13:38:49 +00:00
ca42166e11 chore(mise/ruby): remove ruby-install backend setting
No longer needed; default ruby-build backend is sufficient.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 13:38:24 +00:00
9c2e3b7343 feat(mise/tools): rename 1password-cli to 1password, add lua-language-server
The 1password-cli tool was renamed to 1password in mise registry.
Also add lua-language-server for Lua LSP support.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:46:37 +00:00
Jim Myhrberg
f5245da82a chore(gitignore): add ignore patterns for review directories in .agents and .claude 2026-01-30 17:47:01 +00:00
Jim Myhrberg
cc3dd3fe0e chore(gitignore): expand ignore patterns to include additional draft and plan directories 2026-01-30 15:15:05 +00:00
Jim Myhrberg
1c0f58ef55 feat(mise/tools): add 'riffdiff' dependency and rename '1password-cli' to '1password' 2026-01-27 16:58:07 +00:00
Jim Myhrberg
220461bd9a feat(zsh/rust): add alias for 'batdiff' as 'biff' if available 2026-01-27 16:57:38 +00:00
Jim Myhrberg
e34efe233e chore(hammerspoon/hati): move 'Cursor' key binding for app toggle definition 2026-01-27 16:57:05 +00:00
Jim Myhrberg
3a14e2302f chore(gitignore): update ignore patterns to exclude additional configuration and log files
Added new entries to .gitignore for various configuration files and log formats to prevent unnecessary files from being tracked.
2026-01-27 09:20:26 +00:00
7911beee79 feat(mise/tools): add 'zig' and 'zls' dependencies 2026-01-26 09:35:20 +00:00
c4924a8cfc feat(docs): add CLAUDE.md for repository guidance and configuration details 2026-01-26 09:34:55 +00:00
e38a4f6b73 chore(gitignore): add .claude/plans/ to .gitignore to exclude plan files from version control 2026-01-26 09:34:47 +00:00
Jim Myhrberg
f3bb188938 fix(zoxide): guard cd/cdi definitions with function existence checks
Prevent errors if zoxide fails to initialize by checking that
__zoxide_z and __zoxide_zi functions exist before defining wrappers.
2026-01-21 19:48:43 +00:00
Jim Myhrberg
f1901b81ad fix(zshrc): clean stale paths from PATH before loading zinit and mise
Remove any stale zinit plugin paths and mise install paths from PATH
before loading/activating them. This prevents issues when shell sessions
inherit cached environment variables from tools like direnv in VSCode.
2026-01-21 19:48:39 +00:00
Jim Myhrberg
ab9042fbae feat(zshrc): skip interactive shell setup for Claude Code sessions
Add CLAUDECODE=1 environment variable check to bail early from full
interactive shell setup, matching behavior for Cursor and VSCode.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 16:21:30 +00:00
Jim Myhrberg
c0694b0494 fix(hammerspoon/hati): swap key bindings for Claude and Open WebUI 2026-01-19 19:26:54 +00:00
cb72a6e190 feat(markdownlint): update line length settings for code blocks in markdownlint configuration 2026-01-18 22:02:30 +00:00
b38ab73624 chore(mise/config): remove 'claude' dependency from configuration 2026-01-18 22:02:18 +00:00
6dc701a4f7 feat(mise/config): add 'claude-plugins' dependency to configuration 2026-01-15 21:19:51 +00:00
3ef428c5ea fix(hammerspoon/noct): swap Claude and ChatGPT key bindings 2026-01-15 21:19:51 +00:00
a6af299d8d feat(nix): add initial Nix flake configuration and lock file for package management 2026-01-15 21:19:50 +00:00
Jim Myhrberg
a954f2b588 feat(zshenv): add pnpm setup to environment configuration 2026-01-15 21:11:31 +00:00
Jim Myhrberg
4f831ccd2e feat(ruby): add 'steep' to global Ruby packages installation 2026-01-15 21:10:47 +00:00
Jim Myhrberg
b72042cd1f feat(mise/config): add 'gemini' dependency to configuration 2026-01-15 21:10:34 +00:00
Jim Myhrberg
75116ac623 chore(gitignore): add settings.local.json to ignore list 2026-01-15 21:09:45 +00:00
f92e4b9646 feat(mise/config): add new dependencies for miniserve, http-server, svgo, toml-sort, and cloudflared 2026-01-13 19:30:37 +00:00
Jim Myhrberg
f7dd4b41fd fix(mise/config): rename 'claude-code' to 'claude' in configuration 2026-01-13 17:40:53 +00:00
4909c6a779 feat(userscripts): add DuckDuckGo Old School Blue userscript
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 10:29:13 +00:00
eed33bf54c feat(userstyles): remove old DuckDuckGo custom styles and add new Old School Blue theme 2026-01-06 09:10:12 +00:00
711b8f7678 feat(userstyles): add custom DuckDuckGo styles for enhanced dark theme support 2026-01-01 22:06:07 +00:00
1822f82401 chore(gitignore): add .envrc to ignore list 2025-12-30 13:54:06 +00:00
7c04e49858 fix(zshrc): improve installation prompts for mise and starship 2025-12-30 13:53:42 +00:00
3bb36f8b32 feat(zshenv): add Antigravity setup to zsh environment 2025-12-30 13:53:20 +00:00
4756212c32 feat(zsh/cursor): set cursor as default editor and update alias for cursor command 2025-12-30 13:52:47 +00:00
de6bb30eae chore(mise/config): clean up npm dependencies and update package versions to latest 2025-12-30 13:51:43 +00:00
079ec5177f feat(mise/config): enhance Ruby installation settings and clean up tool dependencies 2025-12-26 19:19:19 +00:00
666c60b89d feat(starship): update configuration to version 0.5.0
Adds config for a few new modules from recent starship updates.
2025-12-26 19:17:57 +00:00
e86ff34b2e chore(zsh/ruby): remove deprecated and redundant gems 2025-12-19 15:30:42 +00:00
fcbcce79f6 fix(mise/tools): update ansible installation instructions to specify compatibility with bcrypt version 2025-12-09 22:13:01 +00:00
13b2b58b67 feat(gitconfig): update AI model configuration for Git Butler 2025-12-09 22:04:11 +00:00
dee407ffc7 feat(mise/tools): remove deprecated npm package and add new package for Claude Code 2025-12-09 22:00:56 +00:00
192e6d6a21 feat(mise/tools): update ansible installation configuration and remove deprecated npm package 2025-12-09 22:00:32 +00:00
c2ab0a9362 feat(zsh): set cursor as default editor if available 2025-12-09 21:58:34 +00:00
Jim Myhrberg
17b96034b6 feat(hammerspoon/hati): update Gmail hotkey to use Mail+ for Gmail 2025-12-02 10:17:32 +00:00
Jim Myhrberg
7248d531b8 fix(zsh/kubernetes): improve password handling in docker-config-json function 2025-11-20 13:58:53 +00:00
e60b35ecb4 style(zsh): standardize fzf options formatting in zshrc 2025-11-13 01:08:13 +00:00
f0e903f102 fix(zsh/atuin): remove ctrl+e keybinding 2025-11-12 02:49:38 +00:00
6cba7534cf feat(zsh): add atuin+fzf for enhanced command history 2025-11-10 09:36:53 +00:00
1808ccde8b feat(mise/tools): add various disk usage inspection CLI tools 2025-11-10 09:27:24 +00:00
0adfe2a389 feat(mise/tools): add 1password-cli to the configuration 2025-11-08 02:48:59 +00:00
4c17e33786 chore(dictionary): add new terms to the Harper dictionary 2025-11-06 17:30:01 +00:00
Jim Myhrberg
f862c93970 chore(rspec): don't set default format to documentation anymore
This is honestly mostly an effort to reduce context size for LLMs when they run rspec tests. But also something I had been considering to do for years anyway as the output is just a wall of text unless running specific subsets of test suites.
2025-11-06 13:42:24 +00:00
f2be6869d2 style(hammerspoon/window_management): improve comment formatting for better readability 2025-11-05 20:50:41 +00:00
74c4df5974 feat(mise/tools): add markdownlint-cli2 2025-10-29 09:58:01 +00:00
78c2706301 feat(mise/config): re-enable bun 2025-10-29 09:57:44 +00:00
1b2c9351cc feat(hammerspoon/hosts/noct): update ChatGPT keybinding to include ChatGPT Atlas 2025-10-29 09:44:49 +00:00
0211c75b1b chore(markdownlint): add line length rule allowing up to 80 characters and disable table line length enforcement 2025-10-29 09:43:16 +00:00
76bbdfb25f fix(hammerspoon/hosts/noct): reorder app toggle keybindings and add Visual Studio Code options 2025-10-25 12:10:49 +01:00
d79d05b551 feat(hammerspoon/window_management): update window resize dimensions for cmd+ctrl+alt+f 2025-10-25 12:08:50 +01:00
Jim Myhrberg
699b4a05d4 fix(hammerspoon/hosts/hati): reorder app toggle keybindings for Visual Studio Code 2025-10-22 10:20:47 +01:00
b7aef0bb99 feat(bin/pngs2collage): add script for creating grid collages from multiple PNG images with customizable options 2025-10-19 00:09:13 +01:00
aa7b647a7f feat(bin/png2icns): add script to convert PNG images to ICNS format with multiple sizes 2025-10-19 00:09:03 +01:00
Jim Myhrberg
9cd09904a4 fix(zshrc): update condition to prevent execution in specific environments 2025-10-13 23:06:30 +01:00
Jim Myhrberg
ebf9c4455c feat(zsh/ruby): add setup for completions of rv command in zsh 2025-10-10 19:39:45 +01:00
Jim Myhrberg
4007b32303 feat(zsh/kubernetes): add docker-config-json function for generating registry auth payload 2025-10-09 12:02:53 +01:00
Jim Myhrberg
1dd9b6664d chore(mise/tools): add toolhive 2025-10-01 17:53:01 +01:00
Jim Myhrberg
62b252f570 chore(hosts/hati): comment out Ghostty and Warp terminal configurations 2025-10-01 17:53:00 +01:00
9e1fd19992 fix(hammerspoon/app_toggle): enhance app name matching to include sanitized names 2025-09-28 16:47:45 +01:00
c939a13be8 feat(hammerspoon/app_toggle): have showAppInfo print to console as well 2025-09-27 05:42:44 +01:00
6fcc5013e9 feat(hosts/noct): add WhatsApp app toggle keybinding 2025-09-27 05:34:49 +01:00
86c5a45570 fix(hammerspoon/app_toggle): sanitize app names to handle non-printable characters 2025-09-27 05:34:15 +01:00
9fd9b794d5 chore(gitconfig): update credential helpers to use op + gh CLIs 2025-09-24 03:54:15 +01:00
ead236947f fix(mise/ansible): resolve issues with ansible installed via mise 2025-09-24 03:49:43 +01:00
Jim Myhrberg
93eb2af279 feat(zsh): add delta completions setup for enhanced git integration 2025-09-23 17:55:04 +01:00
Jim Myhrberg
68a5b5dfb9 feat(zsh): add cursor setup script for enhanced shell integration 2025-09-23 17:54:38 +01:00
Jim Myhrberg
204d274cee chore(ghostty): increase background opacity 2025-09-23 17:54:03 +01:00
Jim Myhrberg
086d80d6bd feat(hosts/hati): tweak keybinding method for activating Ghostty terminal 2025-09-23 17:53:36 +01:00
Jim Myhrberg
4baed4f299 feat(hosts/hati): use Ghostty as main terminal application 2025-09-22 14:50:47 +01:00
447fcd04a8 chore(xbar/brew-updates): update Xbar::Printer class 2025-09-22 09:35:13 +01:00
83c7fb10a2 chore(xbar/brew-services): update Xbar::Printer class 2025-09-22 09:35:13 +01:00
509b2e89e6 feat(xbar/mise): improve display of updates to mise itself 2025-09-22 09:35:13 +01:00
Jim Myhrberg
50004fcb74 fix(shell): improve conditions for skipping interactive shell setup 2025-09-16 18:03:46 +01:00
d04be47100 fix(xbar/mise): force-check latest mise version 2025-09-08 23:25:10 +01:00
e3377c7dbe fix(hammerspoon/app_toggle): fix exception on certain spps 2025-08-30 03:59:13 +01:00
6507f123cb chore(shell): move batman alias 2025-08-27 21:54:43 +01:00
0c46b38c5f feat(mise/tools): add new rv tool for Ruby 2025-08-27 21:47:20 +01:00
Jim Myhrberg
1f558c9f95 fix(shell/vscode): make alt+left/right work in VSCode terminal 2025-08-25 16:37:13 +01:00
0224bd608c feat(xbar): update plugin images and bump versions 2025-08-24 22:44:54 +01:00
70e616332e chore(xbar/img): update preview images 2025-08-24 22:38:59 +01:00
fedb2a78ba chore(xbar/img): add plugin preview images 2025-08-24 22:20:01 +01:00
e47b73d645 style(xbar): fix rubocop linting complaints 2025-08-24 17:21:52 +01:00
bf79e3aa4c feat(swiftbar): add plugins directory with symlinks to xbar plugins dir 2025-08-24 17:06:47 +01:00
add5fe566c feat(xbar/mise-updates): add support for SwiftBar in addition to Xbar 2025-08-24 17:03:06 +01:00
e63ea227f9 feat(xbar/brew-services): add support for SwiftBar in addition to Xbar 2025-08-24 17:01:39 +01:00
eb7f92740c feat(xbar/brew-updates): add support for SwiftBar in addition to Xbar 2025-08-24 16:58:57 +01:00
e835afbd05 feat(xbar/mise-updates): add mise self version check 2025-08-22 13:20:10 +01:00
94887fe5f1 chore(zshenv): add path for LM Studio bins 2025-08-19 09:47:30 +01:00
7e3b5ae4f9 feat(shell/aliases): use batman for man if available 2025-08-19 09:46:19 +01:00
1c2feb9b8b chore(mise/tools): add bat-extras 2025-08-19 09:45:42 +01:00
4e7a48fb2c fix(hammerspoon/app_toggle): find only legit GUI apps
- Fixes an issue with `* Web Content` processes raising an error when checking their path.
- Restrict list of running apps to those who's path ends with `.app`, effectively filtering out background processes and services.
2025-08-19 09:44:59 +01:00
fb2da8181b feat(mise/tools): add pastel 2025-08-17 01:11:32 +01:00
734947c0e7 feat(mise/tools): add fd 2025-08-17 01:11:19 +01:00
124fcd921f feat(mise/tools): add vibe-kanban 2025-08-17 01:10:58 +01:00
Jim Myhrberg
753b4cec41 feat(git/https): add new gh-git-credential-helper script 2025-07-22 17:47:56 +01:00
Jim Myhrberg
31e79a0d64 feat(hati/hotkeys): make Notion Calendar primary calendar app 2025-07-21 18:10:05 +01:00
9eb9dd21b0 feat(mise/tools): add pnpm 2025-07-14 21:26:41 +01:00
Jim Myhrberg
4292fc70e8 chore(markdownlint): add caddyfile to list of tab-indented codeblock languages 2025-07-14 15:57:30 +01:00
Jim Myhrberg
c5e6591d7f feat(gitconfig): enable rerere 2025-07-14 11:55:45 +01:00
Jim Myhrberg
93e46d8edc feat(mise/tools): add mergiraf 2025-07-14 11:15:56 +01:00
9fc5722fa7 feat(mise/tools): add memory and sequential-thinking mcp servers 2025-07-07 23:14:02 +01:00
36195046d4 chore(zsh/funcs): tidy up comments 2025-07-07 23:13:39 +01:00
d2b5cd53b0 chore(zshrc): improve cached-eval usage 2025-07-07 23:10:25 +01:00
ed14e9a73d chore(zsh): make comments prettier :P 2025-07-07 10:26:06 +01:00
b572a346fa fix(zsh/direnv): correctly perform cache eval of setup 2025-07-07 10:24:47 +01:00
60cf0d1e33 feat(git/config): add mergiraf 2025-07-07 10:24:08 +01:00
525fe77e62 chore(hammerspoon/app_toggle): minor lint warning fix 2025-07-07 10:16:46 +01:00
9315ca98bb chore(vscode/settings): add more dictionary words 2025-07-07 10:16:01 +01:00
5bd7292a5e chore(markdownlint): add schema comment for yaml-language-server 2025-06-28 16:30:40 +01:00
456547a278 chore(hammerspoon/noct): add commented out setup for Wrap as main terminal app 2025-06-28 16:19:59 +01:00
fcf092041a chore(vscode/settings.shared): add more custom spelled words 2025-06-28 16:19:22 +01:00
c134beccea feat(markdownlint): add default config for markdownlint 2025-06-28 16:12:57 +01:00
6eb38e87f7 fix(mise): work around issue with GitHub rate limits
Since v2025.4.3 Mise's MISE_LIST_ALL_VERSIONS env var option seems to make some GitHub API requests without your GITHUB_TOKEN, which leads to rate limit errors very quickly.

But the MISE_LIST_ALL_VERSIONS is typically not needed, so disabling it is an acceptable workaround for now.
2025-06-28 16:12:05 +01:00
c2ae58077a fix(vscode): avoid interactive shell setup during VSCode shell resolving
When VSCode is started from normal non-terminal/CLI methods, it will start a interactive shell session in the background to dump the the env and get PATH.

Latest versions of VSCode do no play nice with Mise's dynamic PATH updating stuff, and understandably so.

Hence we now check VSCODE_RESOLVING_ENVIRONMENT and avoid all interactive shell setup when it is set.
2025-06-28 16:10:19 +01:00
cf1c7de954 chore(1password-cli): install with Homebrew instead of Mise
Mise's latest version of 1password-cli is out of date and has been for some time.
2025-06-28 16:05:17 +01:00
1ebbcaf386 fix(mise/tools): lock restish to last working version
Later versions fail to build, so the github release does not contain any binary assets to download. Hence let's use the last release that has assets.
2025-06-28 16:03:45 +01:00
8dc375887c chore(mise/tools): remove kubetail
Stern fills the same need more than well enough, and kubetail's approach to publishing the CLI tool makes UBI not always play nice and find the correct release.
2025-06-28 16:02:27 +01:00
d87d60445e chore(vscode): add shared vscode settings 2025-06-26 13:24:06 +01:00
Jim Myhrberg
12bee562c3 feat(mise/tools): add rails-new 2025-06-11 16:54:39 +01:00
b585142dca chore(mise/tools): add context7 MCP server 2025-06-11 00:32:11 +01:00
Jim Myhrberg
8b7de2514e chore(zshenv): add Windsurf bin to PATH 2025-05-29 12:47:39 +01:00
Jim Myhrberg
f80885f663 chore(ruby/gems): add ruby-lsp-rspec to global package list 2025-05-29 12:47:38 +01:00
Jim Myhrberg
899828ad0c feat(shell/k8s): add more aliases 2025-05-29 12:47:38 +01:00
Jim Myhrberg
db5efc926c chore(hammerspoon/hati): make Code primary VSCode 2025-05-29 12:47:38 +01:00
68f828b356 chore(hammerspoon/noct): make stable VSCode default 2025-05-26 01:16:15 +01:00
1e536231f1 chore(mise): switch to default binary install path
This resolves issues with VSCode sometimes not finding the mise executable within the custom path.
2025-05-26 01:15:28 +01:00
6ca847f9bc feat(fonts): update Menlo Nerd Fonts with FontPatcher 3.4.0 2025-05-25 23:31:45 +01:00
02311a6b3e fix(shell/1password): fix typo in comment of script 2025-05-25 20:48:40 +01:00
4d3b0e5c79 chore(mise/tools): add codex CLI 2025-05-25 20:48:04 +01:00
Jim Myhrberg
da24c81f93 chore(hati/keybindings): bind GitButler to cmd+ctrl+f 2025-05-05 13:49:44 +01:00
Jim Myhrberg
0494075eb0 chore(hati/keybindings): use Warp as primary terminal application 2025-05-05 13:49:04 +01:00
Jim Myhrberg
56c3b49bef fix(mise/ruby): move global gems back to shell helper
Mise is only suitable for gems which expose self-contained CLI tools that don't interact with other Ruby-based tools.

Because it sets GEM_HOME to the specific gem's install path, things like `foreman` cannot launch Rails apps, cause any Ruby-based child processes will not find any gems they expect.
2025-05-05 13:45:57 +01:00
Jim Myhrberg
d2ee3df9ed chore(mise/tools): add slsa-verifier
This allows mise to verify some tools that support it when they're downloaded.
2025-05-05 10:10:53 +01:00
Jim Myhrberg
0084d78885 fix(mise/tools): lock hwatch to known good version
Version 0.3.19 immediately crashes on start for me.
2025-05-05 10:10:05 +01:00
Jim Myhrberg
4a7c880a3a chore(mise): enable specific idiomatic version files 2025-05-05 10:09:25 +01:00
9760c4a2aa feat(mise/tools): install Ruby gems with mise
Also remove various old defunct install_*_global_packages helper shell commands.
2025-05-04 10:22:59 +01:00
1f61ddd8e4 chore(hammerspoon/noct): set cmd+ctrl+f global keybinding to GitButler 2025-05-03 13:34:38 +01:00
52f4d43ddf feat(mise/tools): add stern and kubetail 2025-05-03 13:33:52 +01:00
0d02455a73 chore(mise/tools): switch hexyl to registry shorthand 2025-05-03 13:30:31 +01:00
4d76bb94b9 chore(mise/ruby): use ruby-install instead of ruby-build
Compile time for Ruby is about the same since ruby-install started auto-detecting number of CPU cores like ruby-build does.

So I'm curious to try ruby-install for a while.
2025-05-03 13:29:52 +01:00
708917f58f fix(mise): stop using bun until freezing issue is fixed 2025-05-03 13:28:27 +01:00
4551336767 chore(gitconfig): update Kaleidoscope related settings 2025-04-13 13:54:12 +01:00
6e6755231c chore(zshenv): tweak comment for ~/.local/bin PATH entry 2025-04-13 13:53:38 +01:00
69581a3529 chore(cached-eval): improve caching mechanism
- Include mtime of source file in cache key calculation.
- Use SHA1 instead of MD5 to generate cache key.
- Add flush-cached-eval helper function to clear all caches.
2025-04-13 13:52:57 +01:00
f4563e03fd chore: add .cursorignore 2025-04-09 00:47:40 +01:00
327905fb05 chore(mise/tools): add k3d and dmgbuild 2025-04-09 00:47:11 +01:00
Jim Myhrberg
531bf0bd0f chore(ruby): add ruby-lsp-rails to global package list 2025-04-01 10:48:05 +01:00
Jim Myhrberg
f4f9b055bb chore(hammerspoon/hati): change email client global keybinding 2025-04-01 10:47:36 +01:00
Jim Myhrberg
e67bf554a2 chore(go/tools): install mockgen via go instead of mise 2025-04-01 10:46:42 +01:00
8a77fc622c chore(gitconfig): add gitbutler global settings 2025-04-01 09:51:28 +01:00
4e8a756bec chore(mise/tools): add huge-extended 2025-04-01 09:49:00 +01:00
9fbed8d2ba chore(shell/ruby): remove rubocop-daemon from global package list 2025-04-01 09:48:00 +01:00
63a7285d05 feat(cursor): add windsurf to karabiner vscode tweaks 2025-03-09 00:19:43 +00:00
28eea46178 chore(cursor/ext): update lock files 2025-03-09 00:19:11 +00:00
b1ec71d45c chore(cursor): minor tweaks to settings and keybindings 2025-03-09 00:16:54 +00:00
4bd7fbcc19 feat(cursor/setup): refactor setup script and add support for Windsurf 2025-03-09 00:16:18 +00:00
Jim Myhrberg
27a3193c01 chore(cursor): remove trailing comma 2025-03-07 18:28:52 +00:00
Jim Myhrberg
6a7cca4597 chore(cursor): minor tweaks to setup.sh helper script 2025-03-07 11:21:51 +00:00
Jim Myhrberg
7f937b2777 chore(mise/tools): add copilot-language-server npm package 2025-03-07 11:20:10 +00:00
Jim Myhrberg
c9ef1aa07c feat(cursor/vsci): update vscode-insiders extentions lock file 2025-03-07 11:18:32 +00:00
Jim Myhrberg
12c3e4e2c0 chore(cursor): minor settings tweak 2025-03-07 11:17:44 +00:00
Jim Myhrberg
8153faae39 chore(warp): update keybindings config 2025-03-06 20:00:27 +00:00
Jim Myhrberg
9d0dc840dd fix(cursor): add workaround keybinding for accept next suggested word 2025-03-06 20:00:01 +00:00
Jim Myhrberg
1227c603af chore(mise/tools): update aws-cli to latest version 2025-03-06 10:21:40 +00:00
a95fe29e8e feat(warp): add basic config and theme 2025-03-06 02:56:50 +00:00
842fe398d7 fix(cursor): add alt keybinds for ctrl+f/b/n/p keys 2025-03-06 02:42:13 +00:00
cebbd21e9b chore(cursor/karabiner): add ctrl+p/f/b as well as ctrl+n 2025-03-05 19:54:41 +00:00
a6d649dbb5 chore(cursor/ext): update lock file 2025-03-05 19:53:54 +00:00
Jim Myhrberg
53effb7185 fix(cursor): enable alt+delete to work in terminals 2025-03-05 19:46:12 +00:00
Jim Myhrberg
5a33d6e345 feat(cursor): numerious improvements to alt/ctrl keybindings via karabiner 2025-03-05 19:33:26 +00:00
Jim Myhrberg
79b82d6ce9 feat(cursor/keybindings): lots of tweaks around window tabs and editor group tab navigation 2025-03-05 19:29:25 +00:00
Jim Myhrberg
0fdc9854fe chore(cursor): tweak window title to focus on root/project name 2025-03-05 18:39:24 +00:00
55d6c9ccad feat(cursor): enable native tabs and setup keybindings 2025-03-05 09:29:42 +00:00
d8d77c5f50 feat(cursor): various tweaks and improvements 2025-03-05 02:45:51 +00:00
Jim Myhrberg
b550696164 feat(cursor): update explorer sort order and improved sort lines extension 2025-03-04 18:38:13 +00:00
Jim Myhrberg
71192d2555 feat(cursor): add open in github extension and custom keybinding 2025-03-03 18:16:10 +00:00
Jim Myhrberg
1547265931 chore(cursor): add minimap heading markings to config files 2025-03-03 18:15:04 +00:00
Jim Myhrberg
6cece38916 feat(cursor/theme): enable vivid mode in One Dark Pro 2025-03-03 18:10:53 +00:00
Jim Myhrberg
f71b6376e8 chore(cursor/ext): update lockfile 2025-03-03 12:33:58 +00:00
1f977a79ff chore(cursor/extensions): update lock file 2025-03-02 16:33:05 +00:00
7cbdf277ff feat(cursor/settings): various tweaks and minor re-organization 2025-03-02 15:14:22 +00:00
d0dc7b9068 feat(cursor/keybindings): enable half-page scrolling with ctrl+v and alt+v 2025-03-02 15:11:58 +00:00
989b90822f feat(cursor): add keybindings to toggle maximize and expand editor groups 2025-03-01 23:47:38 +00:00
043ab9af6a chore(cursor): various settings tweaks 2025-03-01 23:46:35 +00:00
Jim Myhrberg
78bf38cc7d feat(cursor): always show minimap slider 2025-03-01 22:55:34 +00:00
f1243b09a8 chore(cursor): update generated at time for ext lock file 2025-02-28 09:07:17 +00:00
f3df7e2253 fix(golang): install Go dev tools with go install and general CLI tools with mise 2025-02-28 09:06:49 +00:00
37a7552518 feat(cursor): add mise extension 2025-02-28 09:05:13 +00:00
Jim Myhrberg
3dd0e620fd chore(mise/tools): add grpcurl and evans gRPC CLI tools 2025-02-27 22:04:20 +00:00
Jim Myhrberg
1e87166ea5 chore(cursor): update settings, keybindings, and remove extensions causing issues
Specifically, the Linter extension was causing the whole extension stack in VSCode to crash and restart every 5-10 seconds when working on large Rails projects. End result was VSCode becoming completely unresponsible for 5-ish seconds as all extensions reloaded.
2025-02-27 22:02:20 +00:00
Jim Myhrberg
c35363ac8f feat(mise/tools): add yarn 2025-02-24 22:26:35 +00:00
Jim Myhrberg
497fe4c9b3 chore(cursor/testing): reset back to default settings 2025-02-24 22:21:33 +00:00
Jim Myhrberg
9ca2e7ead6 chore(zshrc/prompt): offer to install starship with mise use -g instead of mise install 2025-02-24 10:52:42 +00:00
Jim Myhrberg
379f28175a chore(mise/tools): remove duplicate gofumpt definition 2025-02-24 10:51:56 +00:00
Jim Myhrberg
fed65960f2 chore(cursor): update extensions lock file 2025-02-24 10:51:22 +00:00
Jim Myhrberg
f421ba578b fix(cursor/setup): fix typo in setup script 2025-02-24 10:51:03 +00:00
f9af2de78f chore(cursor): add cache directory to root .gitignore 2025-02-23 23:00:43 +00:00
cd497851bd feat(mise/tools): install Go-based tools via mise again
The issue that was preventing mise from installing Go tools a few weeks
ago seems to have been resolved.
2025-02-23 23:00:00 +00:00
035a43d32e feat(mise/tools): install npm packages with bun 2025-02-23 22:58:25 +00:00
32d6e8ebba chore(cursor/github): don't periodically fetch remote repos 2025-02-23 22:53:04 +00:00
84486c881c chore(cursor): minor tweak to window setup 2025-02-23 22:52:46 +00:00
f2f195d15f fix(cursor): allow most keybindings to work like normal in termainl 2025-02-23 22:49:39 +00:00
36ccaa89e2 feat(cursor): add my emacs-style indent/outdent keybindings 2025-02-23 22:49:14 +00:00
f6dc89641a chore(cursor): minor fix to setup.sh 2025-02-23 22:48:51 +00:00
537ca9f9dd fix(cursor): allow tab to work like emacs again
Latest cursor update seems to have broken awesome-emacs-keymap's
"emacsLikeTab" feature. This manually adds the same keybinding with one
extra conditional check which Cursor uses when certain types of inline
suggestions are used.
2025-02-23 22:47:20 +00:00
b6c291c44c feat(cursor/setup): add support for vscode in setup script 2025-02-23 21:58:08 +00:00
1ef2937eb4 feat(cursor): automatically mark script files as execuable on save 2025-02-23 16:37:15 +00:00
d76e2f771e chore(cursor/extensions): update lock file 2025-02-21 23:12:21 +00:00
edd534d50e feat(cursor): add prev/next paragrapn alt+a/e keybindings 2025-02-21 23:11:42 +00:00
89aeb4a960 feat(cursor/shellscript): default to 2-space indentation 2025-02-21 22:46:38 +00:00
Jim Myhrberg
144de73855 chore(cursor): minor tweaks to settings and keybindings 2025-02-21 19:02:26 +00:00
Jim Myhrberg
06f91b1d79 feat(cursor): improve setup script and fix issue with installing extensions 2025-02-21 19:01:34 +00:00
65bee1192e chore(cursor): improve setup script 2025-02-19 19:29:34 +00:00
03eb561f1f feat(cursor): add setup script to symlink config and manage extensions 2025-02-19 19:10:51 +00:00
Jim Myhrberg
7064604079 feat(cursor): add go and ruby snippets 2025-02-19 18:15:55 +00:00
Jim Myhrberg
b8ca8c0268 feat(cursor): add keybindings to run tests 2025-02-19 18:15:52 +00:00
9680f81955 fix(karabiner/ctrl-g): send ESC instead of ctrl+g to Cursor 2025-02-14 22:44:15 +00:00
0be2643aa4 chore(cursor): customize lua setup 2025-02-14 22:41:23 +00:00
Jim Myhrberg
e6d81a0903 chore(cursor): further tweaks to keybindings and settings 2025-02-14 21:38:52 +00:00
Jim Myhrberg
62f5251774 feat(golang/packages): add gofumpt to global package list 2025-02-14 21:38:17 +00:00
Jim Myhrberg
2be6239e18 feat(cli): add ansi CLI tool 2025-02-14 21:36:41 +00:00
Jim Myhrberg
7766d46bd3 chore(cursor): further tweaks to keybindings and settings 2025-02-11 22:40:02 +00:00
e42f9794a5 fix(shell/kubectx): fix typo in completion setup function 2025-02-11 08:29:48 +00:00
Jim Myhrberg
b6ac9ce2ac chore(cursor): update keybindings and settings 2025-02-10 18:57:33 +00:00
Jim Myhrberg
0cb2d3ab27 chore(cursor/settings): remove unused settings 2025-02-10 11:27:38 +00:00
Jim Myhrberg
a1a078c976 fix(karabiner/custom): use correct identifier for Warp 2025-02-10 02:28:38 +00:00
4616736544 feat(hammerspoon/noct): swap Emacs and Cursor keybindings 2025-02-10 01:55:10 +00:00
70fe417553 chore(cursor): update files 2025-02-10 01:54:48 +00:00
Jim Myhrberg
726de4a6d7 feat(hammerspoon/hati): tweak global app keybindings 2025-02-10 01:50:34 +00:00
2b54755e91 feat(cursor): add settings, keybindings and karabiner modifications for Cursor 2025-02-10 01:47:42 +00:00
3ec3884108 feat(install): update ghostty and kitty symlink paths 2025-01-13 19:59:02 +00:00
ca8c913eb3 feat(kitty): add missing theme file 2025-01-13 19:58:22 +00:00
ffe5933b17 feat(hammerspoon): add app_hider.lua which allows hiding apps on loss of focus 2025-01-13 19:57:57 +00:00
10144dfa16 fix(zsh/mise): switch back to normal interactive init
Previously the interactive init would not update PATH immediately,
causing the rest of the zshrc file to fail to find any tools installed
with mise. This longer seems to be an issue.
2025-01-13 19:56:30 +00:00
6eee7b019a chore(alacritty): minor config tweak 2025-01-13 19:54:37 +00:00
fed6251aa7 chore(ghostty): minor config tweaks 2025-01-13 19:37:02 +00:00
33bcfd70be feat(zshenv): add support for using aqua 2025-01-13 19:36:03 +00:00
00d6815373 chore(kitty): wip updates to simplify kitty config 2025-01-13 19:36:03 +00:00
Jim Myhrberg
24aab0403e feat(shell/alias): add jy alias for "yj -jy | yq" 2025-01-06 13:27:00 +00:00
Jim Myhrberg
d560688c58 feat(mise/tools): add crane to global tools list 2025-01-06 13:25:03 +00:00
Jim Myhrberg
9e200139cc feat(mise/tools): install kubelogin 2025-01-06 11:04:24 +00:00
Jim Myhrberg
8ecb6a1d84 chore(mise/tools): improve globally installed ansible setup 2025-01-06 11:04:09 +00:00
Jim Myhrberg
1ab8503b1c fix(irbrc): make irb play nicer within emacs compilation buffers 2025-01-06 10:30:08 +00:00
Jim Myhrberg
0f0efd5917 feat(hammerspoon/hosts/hati): tweak global keybindings 2025-01-06 10:30:08 +00:00
Jim Myhrberg
bb0518a166 fix(zshrc): only attempt to load zinit module if .so file exists 2025-01-06 10:30:07 +00:00
bb538ee33b feat(ghostty): add config and theme 2024-12-28 02:38:24 +00:00
9e9818456d chore(mise/tools): manage global python packages with mise 2024-12-26 23:56:32 +00:00
151620c15b feat(fonts): update Menlo Nerd Fonts with FontPatcher 3.3.0 2024-12-26 23:54:57 +00:00
3d008b827d chore(rust): only install components not included with rust itself 2024-12-18 08:52:06 +00:00
8b22da930e chore(mise/tools): remove prettier plugins I dont use 2024-12-18 08:51:50 +00:00
eea10a67e0 chore(bin/crc32checker): update for newer ruby versions 2024-12-10 01:03:59 +00:00
eadb0fb6e4 chore(brewfile): switch mosh from HEAD to latest stable release 2024-12-10 01:03:28 +00:00
Jim Myhrberg
9a501b438b chore(mise/tools): use shorthand or aqua instead of go/cargo backends 2024-12-09 10:56:40 +00:00
Jim Myhrberg
e31301d0ff chore(zshrc): minor re-orders of a few sections 2024-12-09 10:55:08 +00:00
Jim Myhrberg
d400b87228 feat(mise/tools): add staticcheck 2024-11-28 19:38:08 +00:00
Jim Myhrberg
cc7d4a10d2 feat(shell): alias watch to hwatch if installed 2024-11-28 19:37:47 +00:00
Jim Myhrberg
2242bd9580 fix(shell): don't use cached-eval for setup scripts that dynamically update PATH 2024-11-28 19:37:15 +00:00
Jim Myhrberg
fdc7fcafc6 chore(mise): update shell setup and mise config for latest mise 2024-11-28 19:36:46 +00:00
0c419db04e chore(zshenv/mise): set MISE_LIST_ALL_VERSIONS to 1 2024-11-28 19:17:45 +00:00
9d29136ae6 chore(zshenv/emacs): set LSP_USE_PLIST to 1 2024-11-28 19:17:45 +00:00
Jim Myhrberg
b56b651790 fix(userstyles/github-fixed-width-textarea): only apply to editable textareas 2024-11-25 10:47:00 +00:00
a86cedbcb2 feat(nix): add nix.conf 2024-11-20 14:46:04 +00:00
5b300e7501 fix(userstyles/github-fixed-width-textarea): match GitHub's fixed-width style 2024-11-13 18:11:53 +00:00
Jim Myhrberg
714938a289 feat(userstyle/github): add fixed-width textarea 2024-11-13 18:03:24 +00:00
2ab4549060 chore(fzf): tweak tmux integration behavior 2024-11-12 01:25:45 +00:00
938fd1be31 chore(shell/ruby): tidy up global gem list 2024-11-10 21:54:26 +00:00
e3cd54b9da chore(mise): remove default gems and go package files
For go packages that's managed directly by mise now, and ruby gems is
the old trust `install_ruby_global_packages` shell function.
2024-11-10 21:48:42 +00:00
2f4385a9c6 chore(mise/tools): shift thing arounds 2024-11-02 00:09:26 +00:00
f89466da66 chore(mise/tools): switch from ubi to shorthands for tools which default to ubi backend 2024-10-27 11:23:29 +00:00
916b3967e8 feat(env/PATH): add ~/.local/bin used by pipx 2024-10-27 01:34:25 +01:00
d2d324d1f0 feat(mise/tools): add cargo-show 2024-10-27 01:34:09 +01:00
2c051577b7 fix(mise/tools): install sccache via cargo backend to fix build issues
When mise's cargo backend needs to compile from source, it seemingly
fails to find the `sccache` binary if it was installed via ubi or
asdf backends. Via the cargo backend, or if manually installed via
cargo, it does find it however.
2024-10-27 01:33:00 +01:00
93688e3f2b chore(mise/tools): install bat via ubi backend 2024-10-27 01:32:39 +01:00
f4a409071e chore(mise/tools): install eza via cargo backend
Because eza does not provide binaries for macOS, we can't use the ubi
backend. But via the cargo backend and cargo-binstall, we can install it
from a pre-built binary easily enough.

This removes the need for using the eza asdf plugin.
2024-10-27 01:31:11 +01:00
b753d6af61 chore(mise/tools): install fzf via ubi now that fzf-tmux is no longer needed 2024-10-27 01:30:11 +01:00
5cafa5c9d4 feat(mise/tools): install and manage rust with mise 2024-10-27 01:29:54 +01:00
fcbed7dc82 chore(shell/fzf): use newer --tmux option for fzf
This means the fzf-tmux helper script is no longer needed.
2024-10-27 01:28:17 +01:00
79d1049d93 chore(mise/tools): switch tools which default to ubi back to their shortnames
These tools still install via UBI, as that is now the default method for
their shortnames in mise's registry.
2024-10-26 16:40:46 +01:00
6139832568 chore(mise/tools): switch gitu from cargo to ubi 2024-10-26 16:40:11 +01:00
494578b1ad feat(mise/tools): add taplo for working with toml files 2024-10-24 10:44:14 +01:00
Jim Myhrberg
831e4854af fix(mise): switch shfmt from ubi to asdf plugin
Release assets includes dots in the file names in the form of the
version string, which confuses ubi's asset parser at the moment.
2024-10-21 10:07:13 +01:00
Jim Myhrberg
cf161df584 feat(shell/ruby): add erb_lint to global package list 2024-10-21 10:03:31 +01:00
30c8ab45af feat(hammerspoon/noct): update global keybindings 2024-10-21 00:03:20 +01:00
cb7d47965d chore(zsh/fzf): set height to 100% when tmux is not available 2024-10-20 23:52:06 +01:00
ff3fab8c39 feat(Brewfile): add upscayl 2024-10-19 12:33:01 +01:00
Jim Myhrberg
13ea7e419b fix(mise/tools): switch kustomize back to asdf plugin
Due to the way releases are named in the repo, UBI can't always find the
correct release.
2024-10-18 12:50:34 +01:00
Jim Myhrberg
5990bc5975 feat(shell/python): add pipx to global package list 2024-10-18 12:23:39 +01:00
Jim Myhrberg
4f99ba5001 chore(hammerspoon/hati): tweak global keybindings 2024-10-18 12:23:39 +01:00
Jim Myhrberg
ce00fc9793 feat(Brewfile): add pv formula 2024-10-18 12:23:39 +01:00
0ab34484d0 chore(shell/kubernetes): add completion setup for helm and helmfile 2024-10-18 12:14:46 +01:00
743f99db4f chore(shell/kubernetes): improve kubectx completion setup 2024-10-18 12:14:28 +01:00
d3b5a0c2ab fix(mise/tools): switch kubectx back to asdf plugin
The UBI method excludes supporting files like shell completions and
more.
2024-10-18 12:13:46 +01:00
5f0330dcaf chore(mise): make extensive use of ubi backend
Now that the ubi backend is built-in to mise, so the UBI binary is no
longer required, let's switch most things which can be installed by UBI,
to be installed by UBI.

This reduces the number of separate/external asdf plugins used to
install all my tooling.
2024-10-15 23:41:18 +01:00
154b9b1938 feat(xbar/mise-updates): add self-update menu item 2024-08-25 12:58:23 +01:00
25fa7329f8 feat(xkeysnail): add cmd+, shortcut for 1Password 2024-08-24 18:06:33 +01:00
93dbc451e3 feat(xkeysnail): add cmd+shift+t keybinding for Firefox and Chrome 2024-08-24 17:52:20 +01:00
Jim Myhrberg
4e461ad88c feat(util): add and setup restish CLI tool 2024-08-21 16:27:17 +01:00
97a7844508 feat(fonts): update Menlo Nerd Fonts with FontPatcher 3.2.1 2024-07-31 12:43:15 +01:00
fde1c930be fix(bin/generate-nerdfonts): correctly parse input sources and find fonts 2024-07-31 12:43:14 +01:00
32d6142548 feat(starship): add configure and add gleam module, enable swift and zig modules 2024-07-31 12:43:14 +01:00
Jim Myhrberg
fc4ebfd5fd fix(hammerspoon/misc): change kill keybinding to not conflict with window management 2024-07-15 09:44:06 +01:00
Jim Myhrberg
ae53c3fb23 feat(tools): add helmfile and helm-diff to global tools list 2024-07-15 09:32:01 +01:00
Jim Myhrberg
3072c61568 fix(xbar/mise-updates): ensure commands all run within the env root 2024-07-15 09:31:30 +01:00
Jim Myhrberg
576d03a17a feat(hammerspoon/misc): add helper to easily trigger killall Dock
When waking macOS from sleep with multiple external monitors, it does
not correctly display the wallpaper on all screens, and can sometimes
get confused about window placement boundaries. The fix is to simply
restart the Dock process which handles these things.

This simply adds a cmd+ctrl+alt+k keybinding to trigger this via
Hammerspoon. This is particularly useful in the rare occasion you wake a
laptop without external displays which were disconnected when it was
asleep, as it sometimes gets confused to the point it thinks there are
external displays, and does not consider the internal display as part of
the usable desktop space, meaning there's no way to open a terminal
window to execute `killall Dock`. While this VERY rare, it does still
happen, which more than justifies the few lines of lua here.
2024-07-15 09:27:20 +01:00
Jim Myhrberg
e0e872227f fix(hammerspoon/hati): remove defunct keybinding 2024-07-15 09:26:53 +01:00
Jim Myhrberg
9135f58a12 feat(mise): add argocd and kustomize to global tools list 2024-07-01 18:12:56 +01:00
Jim Myhrberg
4ea6ee4ade feat(allacritty): switch to new toml config format 2024-07-01 09:40:56 +01:00
Jim Myhrberg
ad66b0251f fix(zsh/helper): make command-path helper return 1 if command does not exist 2024-07-01 09:40:56 +01:00
e2f55741aa fix(xbar/mise-updates): correctly run upgrade commands so old versions are removed 2024-06-28 22:11:46 +01:00
Jim Myhrberg
9791d9e304 feat(mise/tools): add godef 2024-06-06 18:34:15 +01:00
Jim Myhrberg
aa17e6fa66 feat(mise/tools): upgrade gitu to latest 2024-06-06 18:33:54 +01:00
Jim Myhrberg
d9cb73260e chore(shell): add custom command-path helper 2024-06-06 18:33:38 +01:00
12b5ecee91 feat(logrotate): add logrorate.d confx files 2024-06-06 18:31:53 +01:00
Jim Myhrberg
de4fad562a fix(irb): remove history setup causing exceptions in latest Ruby versions 2024-05-27 10:21:20 +01:00
Jim Myhrberg
6c80e27c64 fix(shell/cached-eval): fix cache hashing issue 2024-05-27 10:21:20 +01:00
de14e784c3 chore(env/fzf): remove old defunct install path for fzf 2024-05-27 00:30:04 +01:00
ad40c1e2e3 feat(tools): add hivemind to global tools 2024-05-27 00:29:37 +01:00
c5a5e9f108 feat(mise): add fzf to global tool list 2024-05-26 20:57:17 +01:00
54aed5d054 feat(shell/completion): add a custom override for fzf-tab-source 2024-05-21 02:56:29 +01:00
6562df1c19 fix(shell/completions): fix typo 2024-05-21 02:52:43 +01:00
ddbbe283ff feat(shell/completions): tweak and improve fzf-based completion interactions 2024-05-21 02:21:58 +01:00
98f0b2ec66 feat(shell): major refactor around history and completion setup
Biggest change is embracing fzf for all shell completion. This includes
using tmux popup windows to effectively render completions as floating
popups with fuzzy matching via fzf.

Shell history has also been improved, with appending each command to the
history file one by one, rather than only at end of a shell session when
the shell exists. This should ensure I don't have any more lost commands
cause the shell didn't exit cleanly.
2024-05-19 00:08:04 +01:00
3a6e6a5256 fix(shell/cached-eval): find usable md5 command and improve reliability 2024-05-19 00:05:25 +01:00
43be9196eb chore(shell): move interactive shell helpers to their own file 2024-05-18 01:34:49 +01:00
e6572caac9 fix(shell): fix typos 2024-05-18 01:32:46 +01:00
e224c2219c fix(mise/tools): remove neovim and cargo-quickinstall 2024-05-18 01:16:34 +01:00
4a63857a49 fix(mise/tools): install actionlint via go instead of asdf plugin
The asdf plugin uses git to fetch a list of remove versions, which can
be quite slow, and also requires me to auth my ssh agent, which is not
ideal.
2024-05-18 01:16:34 +01:00
359f0642a6 chore(shell/kubernetes): minor tweaks 2024-05-18 01:16:34 +01:00
6f720ba985 feat(shell): create cached-eval helper to improve shell startup speed. 2024-05-18 01:16:31 +01:00
6858f50757 chore(mise): move settings back into single config.toml file 2024-05-12 21:57:37 +01:00
4e5644de9d feat(hammerspoon/noct): remove cmd+ctrl+s keybind for Music app 2024-05-12 17:53:32 +01:00
90617b48e0 feat(hammerspoon/noct): add keybind for show app info helper 2024-05-12 17:53:13 +01:00
995181f67f feat(hammerspoon/noct): don't URL handler anymore
For now, I'm trying out Arc as the main browser, and the Little Arc
feature doesn't play super nice when links open via Hammerspoon.
2024-05-12 17:52:13 +01:00
6336532081 fix(hammerspoon/window-management): move to space works with Arc browser 2024-05-12 17:51:14 +01:00
f30c675f3b fix(shell/1password): source cache file after updating it 2024-05-12 16:35:30 +01:00
53052def08 fix(shell/1password): automatically resolve shell completion issues
1Password's shell plugin integration is nice, but it currently uses
aliases to run various CLI tools through `op plugin run -- <tool>`.

This leads to shell completion for those tools not working correctly in
bash and zsh.

While zsh can work around that with `setopt completealiases`, it causes
shorthand aliases to break instead. Hence the only reasonable solution
is to replace the aliases with shell functions.

Previously I had manually been editing ~/.config/op/plugins.sh replacing
the aliases with functions. This is an automated solution that generates
unalias calls and function definitions for all 1Password aliases. It is
also efficient by means of writing the generated functions to a cache
file which is only updated when needed.
2024-05-12 16:28:08 +01:00
f399f5f085 feat(shell): add alias to function helpers
These can be useful to convert aliases to shell functions, which can
help resolve shell completion issues in some situations.
2024-05-12 16:28:08 +01:00
Jim Myhrberg
f3bf4ff98d fix(hammerspoon/hati): disable URL handling with Hammerspoon
When using Arc and having new links open in Little Arc while having
Hammerspoon set as the default browser, clicking a link first switches
to and focuses on the most recent main Arc window, before then opening
Little Arc.

While if Arc itself is the default browser, no such focus switching
happens, and instead Little Arc just opens and gets focus.
2024-05-10 17:49:41 +01:00
Jim Myhrberg
ee5c2284ea feat(karabiner): add emacs-ctrl-g-to-esc mondification JSON 2024-05-09 21:17:09 +01:00
Jim Myhrberg
430e9053be feat(mise): add command wrapper function 2024-05-09 21:16:47 +01:00
Jim Myhrberg
ee559256c1 feat(gitignore): add mise*.local.toml files 2024-05-09 21:16:30 +01:00
Jim Myhrberg
ba5ea568c8 fix(tools/awscli): remove awscli, the asdf plugin often hangs for minutes 2024-05-09 21:15:10 +01:00
Jim Myhrberg
7fff0e62af fix(hammerspoon/hati): use Arc as default browser via Hammerspoon 2024-05-09 21:14:36 +01:00
Jim Myhrberg
4e5259cd1e feat(hammerspoon/apptoggle): add showAppInfo() utility function 2024-05-09 21:14:17 +01:00
Jim Myhrberg
594022d8df fix(mise): hide missing tools notification in CLI
This often interferes with things that call shims and parse the output,
as output from shims will have a missing tool warning appended to the
end of the output.
2024-05-09 10:31:14 +01:00
Jim Myhrberg
f0c38533f5 feat(zsh/aliases): add root-ca shell function to print root CA certs for domains 2024-05-09 09:30:00 +01:00
Jim Myhrberg
bab3b33c97 chore(hammerspoon/hati): don't use hammerspoon as URL handler 2024-05-09 09:30:00 +01:00
698860e72c feat(tools): move over a few more tools to be managed by mise 2024-05-09 03:17:27 +01:00
3201add496 feat(hammerspoon/noct): switch cmd+ctrl+4 to HuggingChat 2024-05-09 02:28:36 +01:00
0704f6dbcc feat(mise): show env modification details when changing directory 2024-05-09 02:27:52 +01:00
abcff44c09 feat(ruby): add ruby-lsp to global package list 2024-05-09 02:27:17 +01:00
f7e1cb03e7 feat(tools/tldr): customize styling of Rust-based tlrc tldr client 2024-05-09 02:26:45 +01:00
8e67b0066a feat(tools): switch a few more tools from Homebrew to mise 2024-05-09 02:25:47 +01:00
9ece01a77e feat(tools): install Rust-based CLI tools with mise
Use `mise` to install and manage tools normally installed with
`cargo install`.
2024-05-09 02:24:22 +01:00
2cbd074bac feat(tools): install Node-based CLI tools with mise
Use `mise` to install and manage tools normally installed with `npm`.
2024-05-09 02:23:13 +01:00
1158360c21 feat(tools): install Go-based CLI tools with mise
Use `mise` to install and manage tools normally installed with
`go install`.
2024-05-09 02:21:17 +01:00
24f6280608 feat(xbar/mise-updates): various tweaks and enhancements 2024-05-08 23:35:11 +01:00
Jim Myhrberg
5e3beb61dc feat(xbar/mise-update): display requested version 2024-04-29 09:56:26 +01:00
Jim Myhrberg
9b8135e84d feat(hammerspoon/host/hati): add keybind for HuggingChat 2024-04-29 09:37:13 +01:00
Jim Myhrberg
2569e63464 feat(zsh/go): add govulncheck to global package list 2024-04-29 09:36:07 +01:00
284e558669 fix(xbar/mise-updated): handle no outdated tools 2024-04-28 23:11:12 +01:00
f50e41b3bb feat(xbar): add experimental mise-updates script 2024-04-28 23:00:34 +01:00
Jim Myhrberg
770486fa48 feat(bin): add helper tool for running ollama server for Obsidian Copilot 2024-04-03 11:34:13 +01:00
6f7dda418d fix(tools): install bat and exa via rust cargo again
Neither of them provide binary releases for Apple Silicon, meaning mise
cannot install them.
2024-04-03 10:40:39 +01:00
5b28ca6028 chore(starship): tweak direnv display to be less annoying 2024-04-03 10:39:44 +01:00
7a81ddff58 chore(go): disable default Go package list
Some of the packages no longer install on older Go versions, causing
issues when trying to install such Go versions with mise.
2024-04-03 10:39:44 +01:00
e77f402afd chore(zsh/rust): add gitu to global package list 2024-04-03 10:39:42 +01:00
7fec239046 feat(starship): configure direnv and bun modules 2024-04-03 10:39:13 +01:00
c091274fb7 chore(starship): disable swift language indicator 2024-04-03 10:39:13 +01:00
010c790922 chore(starship): adjust symbols for Nerf Fonts v3.x 2024-04-03 10:39:13 +01:00
20569a2a5e feat(xkeysnail): add cmd+g and cmd+shift+g keybindings 2024-03-27 03:40:55 +00:00
a39e2d8a41 feat(xkeysnail): add systemd unit and basic README 2024-03-27 02:07:35 +00:00
cacc0b09f4 chore(gnome): add gTile dconf dump 2024-03-27 02:07:35 +00:00
f6fcd94f2e chore(tools): install 1password-cli with mise 2024-03-27 02:07:35 +00:00
b686b420e6 chore(tools): install bat and exa with mise instead of rust/cargo 2024-03-27 02:07:35 +00:00
Jim Myhrberg
b0f8500e59 feat(hammerspoon/hati): tweak global keybindings 2024-03-11 17:24:19 +00:00
d1116deb8a fix(hammerspoon/window-management): enable moving VSCode windows between desktops 2024-03-07 12:55:07 +00:00
9dd02eadc4 chore(zshenv): simplify brew-prefix helper function 2024-03-02 11:44:19 +00:00
2cf962cb6e feat(Brewfile/noct): add raspberry-pi-imager 2024-02-27 22:41:18 +00:00
Jim Myhrberg
7660ac3afa feat(Brewfile/hati): add kubelogin 2024-02-27 12:11:05 +00:00
Jim Myhrberg
b9e733615d feat(Brewfile): add yq package 2024-02-27 12:10:00 +00:00
Jim Myhrberg
347db4ef48 feat(Brewfile): move resolutionator to global scope 2024-02-27 12:09:30 +00:00
Jim Myhrberg
8e5537f264 feat(hammerspoon/hati): disable Google Chrome global hotkey 2024-02-27 10:37:33 +00:00
9c041a4f9a feat(hammerspoon/window-management): add left/right super-narrow placements 2024-02-26 18:27:34 +00:00
a7ce4a5788 chore(hammerspoon): format window_management.lua via lsp server 2024-02-26 18:27:13 +00:00
779a589a3d feat(hammerspoon/noct): replace Mailplane with Mail+ for Gmail 2024-02-23 23:43:30 +00:00
1829517c54 feat(git): auth HTTP/HTTPS git clone via gh CLI and 1Password CLI 2024-02-23 01:25:22 +00:00
0b183f8acd fix(zsh/completion): ensure brew and brew installed shell completion works 2024-02-23 01:24:01 +00:00
ffa09cbf4c chore(Brewfile): shift things around 2024-02-22 23:57:42 +00:00
5cde6d16c0 feat(starship): show kubernetes context and namespace 2024-02-22 23:56:43 +00:00
1d3d630223 feat(gitconfig): add gitlab user section 2024-02-22 23:55:46 +00:00
32b25c3717 feat(hammerspoon/noct): tweak host-specific URL handling and keybindings 2024-02-22 23:55:26 +00:00
e30d66b7e7 chore(zsh/macos): disable old hypernation hacks
It has been over 7 years since I first put these together. I have not
needed for years, and no clue if they still or, or if they're even save
to run on more recent versions of macOS.
2024-02-22 23:54:13 +00:00
edcf9c122d fix(zshrc): smoother initial run and tool installation 2024-02-22 23:53:24 +00:00
65544c24cc chore(zsh/completion): improve completion setup for various commands 2024-02-22 23:52:26 +00:00
6cd1d787c7 feat(xkeysnail): tweak configuration 2024-02-19 01:05:22 +00:00
c04e2c77a8 chore(deps): upgrade TPM 2024-02-19 01:04:38 +00:00
4f1589d18c feat(mise): add ripgrep to list of global tools 2024-02-19 01:03:59 +00:00
6a7b67a889 feat(bin/linux-toggle-app): various tweaks and enhancements 2024-02-19 01:03:38 +00:00
40d191251b feat(nodejs): add prettier-pnp to global package list 2024-02-19 00:47:52 +00:00
e911f1ecb9 feat(shell): add zoxide as cd replacement 2024-02-19 00:47:30 +00:00
4fb30f9288 feat(linux): add initial xkeysnail config
This allows Linux's keybindings to be a bit more like macOS.
2024-02-16 01:58:43 +00:00
Jim Myhrberg
240a970ba5 chore(hammerspoon/hati): change default browser to firefox 2024-02-12 04:37:17 +00:00
Jim Myhrberg
a0ac91449b feat(hammerspoon/hosts): add hati specific configuration 2024-02-12 04:20:58 +00:00
Jim Myhrberg
9b1b1af109 chore(Brewfile): significant reshuffle to accomodate hati hostname 2024-02-12 03:34:38 +00:00
71212cfe34 chore(solargraph): disable Rails specific settings 2024-02-10 13:08:51 +00:00
efd401bc23 fix(irbrc): disable very annoying auto-completion in IRB 2024-02-07 10:52:48 +00:00
f8ed994dd5 feat(bin): add ffc helper CLI tool 2024-02-04 00:11:48 +01:00
f15bacb858 feat(nodejs): update list of global packages 2024-02-04 00:10:29 +01:00
bf27ad21e4 chore(zshrc): minor tweak to mise setup 2024-02-04 00:10:02 +01:00
f6ed37534a feat(rust): add jwt-cli to default installed crates 2024-02-04 00:09:27 +01:00
e51d08c738 feat(hammerspoon): add Notion Calendar to list of calendar apps 2024-02-04 00:09:20 +01:00
259244eb81 feat(hammerspoon): use Chrome default profile instead of Edge 2024-02-04 00:09:20 +01:00
8c5b2ac4c6 feat(starship): minor tweak to tool versions 2024-02-04 00:09:20 +01:00
86e2b4012f feat(mise): update config 2024-02-04 00:09:20 +01:00
ef6fb00128 feat(Brewfile): add yj 2024-02-04 00:05:43 +01:00
bf92527b6a feat(tools): migrate to mise (formally known as rtx) 2024-01-06 18:59:12 +00:00
f4ce515973 feat(fonts): update Menlo Nerd Fonts with FontPatcher 3.1.1 2024-01-06 17:28:07 +00:00
35a6ba4ab9 feat(bin): add generate-nerdfonts helper script 2024-01-06 17:27:34 +00:00
9526b57d38 chore(zsh): various tweaks to zsh env setup and helpers 2024-01-06 15:10:15 +00:00
a304b0bb26 feat(Brewfile): add carbon-copy-cloner 2024-01-06 14:14:56 +00:00
46ab18b0fd feat(userstyle): add disable-font-ligatures style 2023-12-18 10:57:25 +00:00
10bfd9a032 chore(install.sh): minor fix and update to help output 2023-12-18 10:57:00 +00:00
a5fc1a070a feat(hammerspoon/noct): add Google Calendar, improve URL matching 2023-12-18 10:55:49 +00:00
08011c0fef feat(Brewfile): add obsidian cask 2023-12-18 10:55:32 +00:00
78463acf94 feat(metrics): switch to improved macOS battery exporter 2023-12-18 10:55:05 +00:00
df56386d7a feat(hammerspoon): increase size of rounded corners drawn on screen 2023-11-20 11:11:56 +00:00
12505e939a chore(zshrc): fix typo 2023-11-20 11:11:43 +00:00
5ee117846b chore(playbooks): tweak maginified dock size 2023-11-20 11:11:12 +00:00
fea1e8bd99 feat(hammerspoon): tweak global hotkeys and URL handlers 2023-11-20 11:10:34 +00:00
10bf02e1b4 feat(homebrew): load/setup homebrew on Apple Silicon machines 2023-11-20 11:09:38 +00:00
db7a97fa6d fix(containers): correctly setup shell completion for orb 2023-11-20 11:08:46 +00:00
772295a5a0 fix(kubernetes): resolve shell completion setup for kubectl and krew 2023-11-20 11:08:20 +00:00
871fefce04 chore(deps): move some CLI tools from rtx to cargo install
bat, difftastic and exa don't seem to distribute arm64 binaries as part
of their releases. Hence we need to install them from source with cargo
instead.
2023-11-20 11:07:06 +00:00
319ad47d34 feat(deps): update Brewfile 2023-11-20 11:05:38 +00:00
047d11653b refactor(hammerspoon): improve URL handling setup, enabling host-specific setup 2023-10-28 17:04:23 +01:00
407e02895d feat(hammerspoon): setup URL handling to open different sites in different browsers 2023-10-24 13:59:53 +01:00
b7ff042053 feat(go): add bufls to default list of packages 2023-09-27 11:54:34 +01:00
c137a5978a feat(rtx): setup default ruby gems and go packages 2023-09-18 11:37:19 +01:00
0effd071f3 feat(git): set difft as diff application if available 2023-09-18 11:37:19 +01:00
0b432f137d chore(hammerspoon): update global hotkeys 2023-09-18 11:37:18 +01:00
49a28ce05e chore(zsh/completions): disable completealiases
This prevented aliases from being completed via their original command
that they're aliasing.
2023-09-18 11:37:18 +01:00
19aa1b1730 chore(go): tidy up default global packages list 2023-09-18 11:37:18 +01:00
6c9703520c fix(rust): auto-install with binstall 2023-09-18 11:37:18 +01:00
c51fde5e70 fix(kubernetes): fix bug with kubectx completions setup 2023-09-18 11:37:18 +01:00
f6e49a661b feat(starship): enable version details for a number of tools 2023-09-18 11:37:18 +01:00
c4cd5eaa2b chore(solargraph): update default list of disabled rubocop cops 2023-09-18 11:37:18 +01:00
5fba8ec527 chore(ruby): remove last remnants of rbenv setup, I use rtx instead 2023-09-18 11:37:18 +01:00
e87062e8ee feat(orbstack): shell setup for Orbstack (Docker Desktop replacement) 2023-09-18 11:37:17 +01:00
4cd9893126 chore(rtx): install via official script instead of zinit
A bit more code, but should make installation more reliable, as zinit
often has issues detecting the correct binary/tarball to download from
GitHub releases.
2023-09-18 11:37:17 +01:00
5eb71b08a8 chore(rtx): switch from ~/.tool-versions to ~/.config/rtx/config.toml 2023-09-18 11:37:17 +01:00
61d072a36c fix(xbar/brew-updates): show installed cask version(s) correctly
The brew outdated command recently changed the "installed_versions"
field on casks from a string to an array, bringing it in line with
formula details.

This allows it to deal with either string or array values, and show them
correctly.
2023-06-20 00:37:14 +01:00
6fe27107f0 chore(eslite): disable use of deprecated ~/.eslintrc.js file 2023-05-25 02:26:17 +01:00
e24d969a1e chore(golang): tidy up an update global package list 2023-05-25 02:25:38 +01:00
73556e6f6a chore(python): tidy up an update global package list 2023-05-25 02:24:53 +01:00
f005414ab2 chore(ruby): tidy up an update global package list 2023-05-25 02:24:29 +01:00
266c6dcfac feat(kubernetes): remove tools, simplify setup and rely on rtx 2023-05-25 02:23:33 +01:00
5feea990d4 feat(ruby): switch from rbenv to rtx for managing Ruby versions 2023-05-25 02:19:57 +01:00
e1105afa0c feat: move various various tools to be managed with rtx 2023-05-25 02:19:11 +01:00
fc33b59dc8 feat(font): update Menlo Nerd fonts and add Propo variant 2023-05-25 01:47:02 +01:00
3de163d673 feat(fonts): upgrade Nerd Fonts to v3.0.0 2023-05-03 02:22:45 +01:00
01fc707a2b feat(rtx): update config and shims as needed by latest rtx version 2023-04-30 23:54:43 +01:00
e66883cf73 feat(fonts): add Menlo Nerd Font
While Menlo Nerd Font Mono uses single-width icons (one character), the
non-Mono variant uses double-width icons (two characters).

This allows icons to visually be quite a bit bigger and usable.
2023-04-30 23:51:54 +01:00
fd6aaf0168 feat(fonts): update Menlo Nerd Font Mono 2023-04-30 23:50:50 +01:00
a142363fa7 chore(rtx): re-enable legacy version file supported
The issues I had with Go, can be resolved by running `rtx cache clear`.
2023-04-23 16:31:07 +01:00
9490cabd3d chore(hammerspoon): format init.lua with language server 2023-04-23 16:30:32 +01:00
ab0de76e79 feat(hammerspoon/app_toggle): enable multi-app toggles
A multi-app toggle is a keybinding which is configured to toggle 2 or
more applications.

This is intended as a context-ish-aware toggle, as it will only toggle
the most recently focused application. This essentially enables you to
bind a category/class of applications to a single hotkey, and whichever
of the apps that's running and was most recently focused is the one that
will be toggled.
2023-04-23 16:28:21 +01:00
fd94bd2774 chore(hammerspoon): minor refactor to host_config setup
Allows it to require any file off of disk by using loadfile instead of
require.
2023-04-23 16:27:31 +01:00
214d82f0aa chore(hammerspoon): add .luarc.json config file for lua-language-server 2023-04-23 16:27:01 +01:00
b5a3dc2e4e feat(shell): add neovim specific helpers 2023-04-21 15:12:45 +01:00
415fc774b1 feat(shell/ruby): add haml and rbs helpers for syntax_tree to global gem list 2023-04-21 15:11:54 +01:00
fcb8b04795 fix(rtx): disable legacy version files
Legacy version file support for Go is just broken.
2023-04-21 15:01:17 +01:00
50b21937b3 feat(shell): setup for GitHub Copilot CLI 2023-04-21 15:00:37 +01:00
43d8a4ad9f fix(xbar/brew-updates): fix method name typo 2023-04-06 23:24:46 +01:00
7b52ea47ec chore(rtx): fix issue shell completion setup 2023-04-05 00:27:16 +01:00
77367ea1db chore(rtx): move global versions back to ~/.tool-versions 2023-04-05 00:26:52 +01:00
84bb6dfb80 fix(xbar/brew-updates): fix issue with formula vs cask name conflicts
When upgrading all (formulas and casks), brew would yield an error if
there's a formula and cask installed with the same name.
2023-04-04 23:13:25 +01:00
96ed44adf3 fix(python): prefer pip over pip3 when installing python packages 2023-03-21 01:22:19 +00:00
06b5b5cd39 chore(rtx): move global tool versions to rtx config file 2023-03-21 01:21:47 +00:00
cc3121e472 chore(xbar/brew-services): minor tweaks and improvements 2023-03-21 00:46:22 +00:00
900e69a07f feat(xbar/brew-updates): few features and style changes
- Add more granular greedy options.
- Add post-run options for optionally running "brew cleanup" and/or
  "brew doctor" after operations that modify packages.
- Add new "Upgrade All: Exclude" option which simply excludes a
  formula/cask when using any of the Upgrade All operations.
- Re-style menus a bit with emoji as icons for most operations.
2023-03-21 00:37:01 +00:00
673bbecf59 chore(starhip): use literal strings everywhere for the same of consistency 2023-03-20 22:29:05 +00:00
10afd47a2b fix(xbar/brew-updates): fix VAR_GREEDY config value type 2023-03-12 12:39:19 +00:00
881e5f4d12 chore(xbar): fix typo in variable name 2023-03-12 12:35:18 +00:00
86efc403d1 fix(xbar/brew-services): handle unexpected config values better
Also refactor Xbar::Config and and Brew::Common classes a bit to make
them more generic, and have more features.
2023-03-12 12:26:50 +00:00
0afaeaa9a2 fix(xbar/brew-updates): handle unexpected config values better
Also refactor Xbar::Config and and Brew::Common classes a bit to make
them more generic, and have more features.
2023-03-12 12:23:40 +00:00
2a8e815db1 fix(alacritty): change default font 2023-03-09 23:36:26 +00:00
8830fa2af1 feat(prompt): minor tweak to starship config 2023-03-09 23:35:18 +00:00
08c8ac784d feat(git): add git-largest-objects helper 2023-03-09 23:35:18 +00:00
6e3154deba feat(nix): setup shell and env for nix 2023-03-09 23:35:18 +00:00
58f7306c6f chore(brewfile): minor update/removal 2023-03-09 23:35:18 +00:00
32b933c419 chore(git): update gitconfig 2023-03-09 23:35:18 +00:00
7fbc1e11d4 feat(ruby): add syntax_tree to global gem list 2023-03-09 23:35:17 +00:00
c101c417fd feat(python): update global package list 2023-03-09 23:35:17 +00:00
72dde13eb7 feat(rust): improve overall setup around Rust 2023-03-09 23:35:17 +00:00
664f06bbdb refactor: use rtx to manage Go, Python, NodeJS, and more
This means g, pyenv, and volta are gone. And a lot of other tools
previously installed with zinit are also installed with rtx now instead.

This includes starship, direnv, shfmt, jq, and more.
2023-03-09 23:35:17 +00:00
885fd26901 chore: install goreleaser and golangci-lint with Go instead of Homebrew 2023-03-09 22:42:17 +00:00
9abb969dbb feat(brewfile): install 1password-cli 2023-03-09 22:40:07 +00:00
4bf199c73b feat(xbar/brew-updates): support latest and auto-updates greedy options 2023-03-09 22:37:03 +00:00
ef48ac629a feat(xbar/brew-services): improve settings and style 2023-03-09 22:34:57 +00:00
0a17bbfadb feat: add 1Password CLI integration 2023-03-09 21:53:01 +00:00
4ff776f1f3 feat(rustfmt): add rustfmt default config of max_width = 80 2023-03-08 00:53:35 +00:00
fcf9ee7e2a fix(shell/kubernetes): improve shell startup time 2023-03-04 05:01:19 +00:00
9b63c538c2 feat(prompt/starship): update starship config 2023-03-04 04:55:11 +00:00
14c58e849f feat(xbar/brew-updates): add greedy option that displays more cask updates 2023-02-26 18:02:58 +00:00
204a8979da fix(xbar/brew): improve brew executable detection
This should address issues on Apple Silicon based Macs.
2023-02-26 17:58:32 +00:00
c7ea3d186a feat(hammerspoon/noct): tweak global keybindings for ChatGPT and Edge 2023-02-20 22:11:12 +00:00
78caf6e939 chore(hammerspoon): tidy-up and simplify host config and window management 2023-02-20 22:10:32 +00:00
e252161208 fix(xbar/brew-updates): resolve rubocop linting complaints 2023-01-29 16:03:44 +00:00
9244c7f92b fix(xbar/brew-services): handle when no brew services are installed 2023-01-29 16:01:45 +00:00
154 changed files with 14600 additions and 2766 deletions

2
.cursorignore Normal file
View File

@@ -0,0 +1,2 @@
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
private/*

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ Icon*
bin/boot2docker.iso
private/*
tmux/plugins/*
cursor/cache/*

5
.harper-dictionary.txt Normal file
View File

@@ -0,0 +1,5 @@
RPCError
SwiftBar
Xbar's
argv
yieldparam

9
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
// Allows for use of settings.shared.json and settings.local.json.
"swellaby.workspace-config-plus"
]
}

50
.vscode/settings.shared.json vendored Normal file
View File

@@ -0,0 +1,50 @@
{
"cSpell.words": [
"aegisub",
"appcleaner",
"appdir",
"apphider",
"apptoggle",
"aquaproj",
"atinit",
"autodmg",
"binstall",
"colordiff",
"correctall",
"difftastic",
"dirstat",
"dmgbuild",
"DOTBIN",
"DOTPFILES",
"DOTZSH",
"fonttools",
"funcs",
"geckodriver",
"getopt",
"gitu",
"Grooff",
"grpcurl",
"htmllint",
"httpsnippet",
"hwatch",
"INFOPATH",
"jwtui",
"keycastr",
"kubecm",
"KUBECONFIG",
"localtunnel",
"PLISTS",
"rakyll",
"rapidapi",
"RUSTC",
"shellenv",
"stfolder",
"stignore",
"tlrc",
"watchexec",
"zdharma",
"zicdreplay",
"zicompinit",
"zinitrc"
]
}

169
Brewfile
View File

@@ -7,14 +7,13 @@
hostname = `hostname -s`.strip
cask_args appdir: '/Applications'
tap 'homebrew/cask'
tap 'homebrew/cask-drivers'
tap 'homebrew/cask-versions'
#
# Command-Line Tools (Brew)
#
brew '1password-cli'
brew 'ack'
brew 'aspell'
brew 'bash'
@@ -22,58 +21,43 @@ brew 'caddy'
brew 'cmake'
brew 'colordiff'
brew 'coreutils'
brew 'ctop'
brew 'efm-langserver'
brew 'fd'
brew 'ffmpeg'
brew 'geckodriver'
brew 'git'
brew 'git-crypt'
brew 'git-delta'
brew 'go'
brew 'golangci-lint'
brew 'goreleaser'
brew 'hadolint'
brew 'hey'
brew 'highlight'
brew 'htop'
brew 'httpie'
brew 'jq'
brew 'less'
brew 'libvterm'
brew 'libyaml'
brew 'logrotate'
brew 'lua'
brew 'luarocks'
brew 'make'
brew 'mas'
brew 'mosh', args: ['HEAD']
brew 'mergiraf'
brew 'mosh'
brew 'node'
brew 'pandoc'
brew 'peco'
brew 'pgformatter'
brew 'pv'
brew 'readline'
brew 'redis'
brew 'ripgrep'
brew 'rustup-init'
brew 'shellcheck'
brew 'shfmt'
brew 'source-highlight'
brew 'svn'
brew 'telnet'
brew 'terraform'
brew 'terraform-ls'
brew 'tflint'
brew 'the_silver_searcher'
brew 'tldr'
brew 'tmux'
brew 'trash'
brew 'tree'
brew 'watch'
brew 'wget'
brew 'yank'
brew 'zsh'
tap 'heroku/brew'
brew 'heroku'
tap 'MisterTea/et'
brew 'MisterTea/et/et'
#
# Desktop Apps (Cask)
@@ -83,29 +67,22 @@ brew 'heroku'
cask '1password'
cask 'alfred'
cask 'appcleaner'
cask 'bartender'
cask 'betterzip'
cask 'bitbar'
cask 'daisydisk'
cask 'firefox'
cask 'fluid'
cask 'google-chrome'
cask 'gpg-suite'
cask 'hammerspoon'
cask 'handbrake'
cask 'iina'
cask 'intel-power-gadget'
cask 'istat-menus'
cask 'iterm2'
cask 'karabiner-elements'
cask 'logitech-options'
cask 'mplayerx'
cask 'name-mangler'
cask 'osxfuse'
cask 'microsoft-edge'
cask 'orion'
cask 'resolutionator'
cask 'soulver2'
cask 'stay'
cask 'suspicious-package'
cask 'transmission'
cask 'ubersicht'
cask 'vlc'
cask 'xbar'
# Fonts
tap 'homebrew/cask-fonts'
@@ -113,61 +90,30 @@ cask 'font-clear-sans'
cask 'font-menlo-for-powerline'
cask 'font-office-code-pro'
cask 'font-open-sans'
cask 'font-open-sans-condensed'
cask 'font-terminus'
cask 'font-ubuntu'
cask 'font-ubuntu-mono-derivative-powerline'
cask 'font-xkcd'
# Work Apps
cask 'bbedit'
cask 'chromedriver'
cask 'cyberduck'
cask 'dash'
cask 'docker'
cask 'drawio'
cask 'fork'
cask 'gitx'
cask 'hex-fiend'
cask 'insomnia'
cask 'kaleidoscope'
cask 'keycastr'
cask 'licecap'
cask 'paw'
cask 'postico'
cask 'robo-3t'
cask 'orbstack'
cask 'rapidapi'
cask 'sequel-pro'
cask 'slack'
cask 'transmit'
cask 'vagrant'
cask 'virtualbox'
cask 'visual-studio-code'
cask 'utm'
cask 'visual-studio-code-insiders'
cask 'zoom'
# noct
if hostname == 'noct'
brew 'ffmpeg', args: %w[
with-fdk-aac
with-libass
with-librsvg
with-libsoxr
with-libssh
with-libvidstab
with-openh264
with-openssl
with-rubberband
with-srt
with-tesseract
with-webp
]
brew 'get_iplayer'
cask 'basecamp'
brew 'go-jsonnet'
brew 'influxdb'
brew 'jsonnet-bundler'
brew 'kubernetes-cli'
brew 'kubernetes-helm'
brew 'kubectx'
brew 'mariadb'
cask 'lens'
@@ -175,6 +121,9 @@ if hostname == 'noct'
brew 'node_exporter'
brew 'prometheus'
tap 'jimeh/macos-battery-exporter'
brew 'macos-battery-exporter'
cask '4k-video-downloader'
cask 'adobe-creative-cloud'
cask 'aegisub'
@@ -185,26 +134,34 @@ if hostname == 'noct'
cask 'autodmg'
cask 'avidemux'
cask 'balenaetcher'
cask 'bartender'
cask 'betterzip'
cask 'calibre'
cask 'deluge'
cask 'discord'
cask 'dropbox'
cask 'carbon-copy-cloner'
cask 'daisydisk'
cask 'discord-ptb'
cask 'dropshare'
cask 'element'
cask 'epic-games'
cask 'evernote'
cask 'get-iplayer-automator'
cask 'gog-galaxy'
cask 'handbrake'
cask 'istumbler'
cask 'istat-menus'
cask 'kaleidoscope2'
cask 'kapitainsky-rclone-browser'
cask 'keybase'
cask 'lastfm'
cask 'ledger-live'
cask 'little-snitch'
cask 'lm-studio'
cask 'mailplane'
cask 'makemkv'
cask 'messenger'
cask 'mkvtoolnix'
cask 'monodraw'
cask 'name-mangler'
cask 'notion'
cask 'obsidian'
cask 'openemu'
cask 'paparazzi'
cask 'parallels'
@@ -219,50 +176,70 @@ if hostname == 'noct'
cask 'quicklook-csv'
cask 'quicklook-json'
cask 'quicklookapk'
cask 'raspberry-pi-imager'
cask 'scroll-reverser'
cask 'sixtyforce'
cask 'skype'
cask 'soulver2'
cask 'soundsource'
cask 'spotify'
cask 'steam'
cask 'suspicious-package'
cask 'tableplus'
cask 'teamspeak-client'
cask 'transmission'
cask 'transmit'
cask 'upscayl'
cask 'virtualc64'
cask 'viscosity'
cask 'vmware-fusion'
cask 'vnc-viewer'
cask 'webpquicklook'
cask 'whatsapp'
cask 'xld'
cask 'zoom'
# Mac App Store Apps
mas 'Apple Remote Desktop', id: 409_907_375
mas 'Awaken', id: 404_221_531
mas 'Bear', id: 1_091_189_122
mas 'Blackmagic Disk Speed Test', id: 425_264_550
mas 'Calca', id: 635_758_264
mas 'ColorSlurp', id: 1_287_239_339
mas 'Flame', id: 325_206_381
mas 'GoodNotes 5', id: 1_444_383_602
mas 'HTTP Client', id: 418_138_339
mas 'HiddenMe', id: 467_040_476
mas 'Medis', id: 1_063_631_769
mas 'Microsoft Remote Desktop 10', id: 1_295_203_466
mas 'MindNode', id: 1_289_197_285
mas 'Keepa', id: 1_533_805_339
mas 'MeetingBar', id: 1_532_419_400
mas 'Pocket', id: 568_494_494
mas 'Reeder 3', id: 880_001_334
mas 'Spark', id: 1_176_895_641
mas 'Tailscale', id: 1_475_387_142
mas 'TestFlight', id: 899_247_664
mas 'Textual 7', id: 1_262_957_439
mas 'Things 3', id: 904_280_696
mas 'Twitter', id: 1_482_454_543
mas 'WireGuard', id: 1_451_685_025
mas 'feedly', id: 865_500_966
# Safari Extensions
mas 'AdGuard for Safari', id: 1_440_147_259
mas 'Adguard for Safari', id: 1_440_147_259
mas 'Cascadea', id: 1_432_182_561
mas 'Dark Reader for Safari', id: 1_438_243_180
mas 'Evernote Web Clipper', id: 1_481_669_779
mas 'Octotree Pro', id: 1_457_450_145
mas 'Save to Pocket', id: 1_477_385_213
mas 'Notion Web Clipper', id: 1_559_269_364
mas 'OctoLinkter', id: 1_549_308_269
mas 'xSearch for Safari', id: 1_579_902_068
end
# hati
if hostname == 'hati'
brew 'grafana'
brew 'influxdb'
brew 'mariadb'
brew 'node_exporter'
brew 'prometheus'
tap 'int128/kubelogin'
brew 'kubelogin'
tap 'jimeh/macos-battery-exporter'
brew 'macos-battery-exporter'
cask 'obsidian'
cask 'setapp'
cask 'teamspeak-client'
mas 'MeetingBar', id: 1_532_419_400
mas 'Tailscale', id: 1_475_387_142
end

61
CLAUDE.md Normal file
View File

@@ -0,0 +1,61 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with
code in this repository.
## Overview
Personal dotfiles repository for macOS and Linux. Configuration files are
symlinked to `$HOME` with a `.` prefix (e.g., `zshrc` becomes `~/.zshrc`).
## Commands
### Installation
```sh
./install.sh # Install symlinks and initialize shell
./install.sh symlinks # Only install symlinks
./install.sh terminfo # Install terminfo entries
./install.sh launch-agents # Install macOS LaunchAgents
```
### Nix packages
```sh
nix-env -if ~/.dotfiles/default.nix # Install/update global Nix packages
nix flake update # Update nixpkgs lock
```
### Hammerspoon
```sh
cd hammerspoon && make install # Fetch Spoons and dependencies
cd hammerspoon && make update # Update all dependencies
```
## Structure
- `install.sh` - Main installer, manages symlinks to `$HOME`
- `flake.nix` / `default.nix` - Nix package management (nixfmt, nil LSP)
- `zshrc` / `zshenv` / `zprofile` - ZSH configuration entry points
- `zsh/` - Modular ZSH configs loaded by topic (aliases, kubernetes, golang,
etc.)
- `zsh/zshrc.funcs.zsh` - Helper functions used throughout ZSH config
- `bin/` - Personal scripts added to PATH
- `config/` - XDG config files (ghostty, kitty, mise, starship, etc.)
- `hammerspoon/` - macOS automation with Hammerspoon
- `tmux/` - Tmux config and plugins (git submodules)
- `private/` - Private dotfiles (separate repo, gitignored)
## Shell Setup
- Uses [zinit](https://github.com/zdharma-continuum/zinit) for ZSH plugin
management
- Uses [mise](https://mise.jdx.dev/) for runtime version management
- Uses [starship](https://starship.rs/) for prompt
- PATH construction happens in `zshenv`, interactive setup in `zshrc`
## Code Style
Shell scripts: 2-space indent, bash-style (see `.editorconfig` for shfmt
settings).

265
alacritty.toml Normal file
View File

@@ -0,0 +1,265 @@
[colors.normal]
black = "#000000"
red = "#cf5041"
green = "#8bba48"
yellow = "#c3a643"
blue = "#37689f"
magenta = "#9b589d"
cyan = "#69b8cb"
white = "#cccccc"
[colors.bright]
black = "#686a66"
red = "#ed3a2c"
green = "#96cf4d"
yellow = "#fdeb61"
blue = "#5696d5"
magenta = "#9d6aa5"
cyan = "#75cee3"
white = "#f5f5f5"
[colors.dim]
black = "#333333"
red = "#f2777a"
green = "#99cc99"
yellow = "#ffcc66"
blue = "#6699cc"
magenta = "#cc99cc"
cyan = "#66cccc"
white = "#dddddd"
[colors.primary]
background = "#000000"
foreground = "#D0D0D0"
[colors.selection]
background = "#7499c7"
text = "#000000"
[env]
TERM = "xterm-256color"
[font]
size = 12.0
[font.glyph_offset]
x = 0
y = 0
[font.normal]
family = "Menlo Nerd Font"
[font.offset]
x = 0
y = 0
[[keyboard.bindings]]
action = "Paste"
key = "Paste"
[[keyboard.bindings]]
action = "Copy"
key = "Copy"
[[keyboard.bindings]]
action = "ClearLogNotice"
key = "L"
mods = "Control"
[[keyboard.bindings]]
action = "ScrollPageUp"
key = "PageUp"
mode = "~Alt"
mods = "Shift"
[[keyboard.bindings]]
action = "ScrollPageDown"
key = "PageDown"
mode = "~Alt"
mods = "Shift"
[[keyboard.bindings]]
action = "ScrollToTop"
key = "Home"
mode = "~Alt"
mods = "Shift"
[[keyboard.bindings]]
action = "ScrollToBottom"
key = "End"
mode = "~Alt"
mods = "Shift"
[[keyboard.bindings]]
action = "SearchConfirm"
key = "Return"
mode = "Search|Vi"
[[keyboard.bindings]]
action = "SearchCancel"
key = "Escape"
mode = "Search"
[[keyboard.bindings]]
action = "SearchCancel"
key = "C"
mode = "Search"
mods = "Control"
[[keyboard.bindings]]
action = "SearchClear"
key = "U"
mode = "Search"
mods = "Control"
[[keyboard.bindings]]
action = "SearchDeleteWord"
key = "W"
mode = "Search"
mods = "Control"
[[keyboard.bindings]]
action = "SearchHistoryPrevious"
key = "P"
mode = "Search"
mods = "Control"
[[keyboard.bindings]]
action = "SearchHistoryNext"
key = "N"
mode = "Search"
mods = "Control"
[[keyboard.bindings]]
action = "SearchHistoryPrevious"
key = "Up"
mode = "Search"
[[keyboard.bindings]]
action = "SearchHistoryNext"
key = "Down"
mode = "Search"
[[keyboard.bindings]]
action = "SearchFocusNext"
key = "Return"
mode = "Search|~Vi"
[[keyboard.bindings]]
action = "SearchFocusPrevious"
key = "Return"
mode = "Search|~Vi"
mods = "Shift"
[[keyboard.bindings]]
chars = "\u0011"
key = "Q"
mods = "Control"
[[keyboard.bindings]]
action = "ResetFontSize"
key = "Key0"
mods = "Command"
[[keyboard.bindings]]
action = "IncreaseFontSize"
key = "Equals"
mods = "Command"
[[keyboard.bindings]]
action = "IncreaseFontSize"
key = "Plus"
mods = "Command"
[[keyboard.bindings]]
action = "IncreaseFontSize"
key = "NumpadAdd"
mods = "Command"
[[keyboard.bindings]]
action = "DecreaseFontSize"
key = "Minus"
mods = "Command"
[[keyboard.bindings]]
action = "DecreaseFontSize"
key = "NumpadSubtract"
mods = "Command"
[[keyboard.bindings]]
action = "Paste"
key = "V"
mods = "Command"
[[keyboard.bindings]]
action = "Copy"
key = "C"
mods = "Command"
[[keyboard.bindings]]
action = "Hide"
key = "H"
mods = "Command"
[[keyboard.bindings]]
action = "HideOtherApplications"
key = "H"
mods = "Command|Alt"
[[keyboard.bindings]]
action = "Minimize"
key = "M"
mods = "Command"
[[keyboard.bindings]]
action = "Quit"
key = "Q"
mods = "Command"
[[keyboard.bindings]]
action = "Quit"
key = "W"
mods = "Command"
[[keyboard.bindings]]
action = "SpawnNewInstance"
key = "N"
mods = "Command"
[[keyboard.bindings]]
action = "ToggleSimpleFullscreen"
key = "Return"
mods = "Command"
[[keyboard.bindings]]
action = "SearchForward"
key = "F"
mode = "~Search"
mods = "Command"
[[keyboard.bindings]]
action = "SearchBackward"
key = "G"
mode = "~Search"
mods = "Command"
[[mouse.bindings]]
action = "PasteSelection"
mode = "~Vi"
mouse = "Middle"
[scrolling]
history = 50000
multiplier = 3
[window]
decorations = "full"
dynamic_padding = true
[window.dimensions]
columns = 80
lines = 24
[window.padding]
x = 2
y = 2

View File

@@ -112,7 +112,7 @@ font:
# - (macOS) Menlo
# - (Linux/BSD) monospace
# - (Windows) Consolas
family: Menlo for Powerline
family: Menlo Nerd Font Mono
# The `style` can be specified to pick a specific face.
#style: Regular

View File

@@ -1,14 +1,15 @@
#! /usr/bin/env ruby
#!/usr/bin/env ruby
# frozen_string_literal: true
ARGV.each do |path|
print "Checking \"#{path}\"... "
if !File.exists?(path)
puts "FILE NOT FOUND"
unless File.exist?(path)
puts 'FILE NOT FOUND'
next
end
checksum = `crc32 "#{path}"`.strip.upcase
checksum = `crc32 "#{path}"`.strip.split(' ', 2).first.upcase
if File.basename(path).upcase.include?(checksum)
puts "MATCH (#{checksum})"
else

27
bin/ffc Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -e
# ffc - Files for ChatGPT
# Check if any arguments are provided
if [ $# -eq 0 ]; then
echo "Usage: $0 <file1> <file2> ... <fileN>" >&2
exit 1
fi
shopt -s globstar nullglob
# Iterate over provided arguments
for file_pattern in "$@"; do
# Use shell expansion to find matching files
for file in ./$file_pattern; do
if [ -f "$file" ]; then
rel_file="${file#./}"
echo '`'"$rel_file"'`:'
echo '```'
cat "$file"
echo '```'
echo
fi
done
done

194
bin/generate-nerdfonts Executable file
View File

@@ -0,0 +1,194 @@
#!/usr/bin/env bash
set -e
show-help() {
echo "Usage: $(basename "$0") [<options>] <font-patcher-script-path>" \
"<output-dir> [<font-sources>]"
echo ""
echo "Generates Nerd Fonts from source fonts."
echo ""
echo "By default generates Nerd Font, Nerd Font Mono and Nerd Font Propo"
echo "variants for all font files."
echo ""
echo "Arguments:"
echo " <font-patcher-script-path> Path to font-patcher script"
echo " <output-dir> Output directory for generated fonts"
echo " <font-sources> Optional one or more font sources." \
"Can be directories or files (default: current directory)"
echo ""
echo "Options:"
echo " --normal, -n / --no-normal Generate only/skip Nerd Font variant"
echo " --mono, -m / --no-mono Generate only/skip Nerd Font Mono variant"
echo " --propo, -p / --no-prop Generate only/skip Nerd Font Propo variant"
echo " --help, -h Show this help message"
}
main() {
local args=()
local generate_normal="1"
local generate_mono="1"
local generate_propo="1"
# Parsing flags
while [[ "$#" -gt 0 ]]; do
case $1 in
--help | -h)
show-help
exit 0
;;
--normal | -n)
generate_normal="1"
generate_mono=""
generate_propo=""
;;
--no-normal) generate_normal="" ;;
--mono | -m)
generate_mono="1"
generate_normal=""
generate_propo=""
;;
--no-mono) generate_mono="" ;;
--propo | -p)
generate_propo="1"
generate_normal=""
generate_mono=""
;;
--no-propo) generate_propo="" ;;
--* | -?)
echo "Unknown option: $1"
exit 1
;;
*)
args+=("$1")
;;
esac
shift
done
local patcher="${args[0]}"
local outputDir="${args[1]}"
local input_sources=("${args[@]:2}")
local sources=()
if [[ -z "$patcher" || -z "$outputDir" ]]; then
show-help 1>&2
exit 1
fi
if [[ -z "$generate_normal" && -z "$generate_mono" && -z "$generate_propo" ]]; then
error "No Nerd Font variants selected."
show-help 1>&2
exit 1
fi
patcher="$(abs_path "$patcher")"
if [[ ! -f "$patcher" ]]; then
fatal "Font patcher script not found: $patcher" 1>&2
fi
if ! command -v fontforge > /dev/null; then
fatal "fontforge is not installed. Please install it first."
fi
if [[ ! -d "$outputDir" ]]; then
mkdir -p "$outputDir"
fi
if [[ ${#input_sources[@]} -eq 0 ]]; then
echo "===> No sources specified, searching for font files in current directory"
while read -r line; do
sources+=("$line")
done < <(find-sources "$(abs_path "$(pwd)")")
echo "===> Found ${#sources[@]} font files:"
for src in "${sources[@]}"; do
echo "---> - $src"
done
fi
for item in "${input_sources[@]}"; do
if [[ -d $item ]]; then
echo "===> Finding for font files in ${item}:"
# If it's a directory, find sources and add to array
while read -r line; do
echo "---> - $line"
sources+=("$line")
done < <(find-sources "$item")
else
# If it's not a directory, process with abs_path and add to array
sources+=("$(abs_path "$item")")
fi
done
# abort if no sources found
if [[ ${#sources[@]} -eq 0 ]]; then
error "No font files found"
exit 1
fi
for src in "${sources[@]}"; do
echo "===> Processing $src"
# Nerd Font
if [[ -n "$generate_normal" ]]; then
echo "===> Generating Nerd Font for $src"
fontforge -lang=py -script "$patcher" --adjust-line-height --complete \
-out "$outputDir" "$src"
fi
# Nerd Font Mono
if [[ -n "$generate_mono" ]]; then
echo "===> Generating Nerd Font Mono for $src"
fontforge -lang=py -script "$patcher" --adjust-line-height --complete \
--mono \
-out "$outputDir" "$src"
fi
# Nerd Font Propo
if [[ -n "$generate_propo" ]]; then
echo "===> Generating Nerd Font Propo for $src"
fontforge -lang=py -script "$patcher" --adjust-line-height --complete \
--variable-width-glyphs \
-out "$outputDir" "$src"
fi
done
}
error() {
echo "ERROR: $1" 1>&2
}
fatal() {
error "$1"
exit 2
}
find-sources() {
local dir="$1"
find "$dir" -type f \( -iname '*.ttf' -o -iname '*.otf' -o -iname '*.ttc' \)
}
abs_dirname() {
local path="$1"
local cwd
cwd="$(pwd)"
while [ -n "$path" ]; do
cd "${path%/*}" 2> /dev/null
local name="${path##*/}"
path="$(resolve_link "$name" || true)"
done
pwd
cd "$cwd"
}
abs_path() {
local path="$1"
echo "$(cd "$(abs_dirname "$path")" && pwd)/$(basename "$path")"
}
resolve_link() {
$(type -p greadlink readlink | head -1) "$1"
}
main "$@"

52
bin/gh-git-credential-helper Executable file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/env bash
#
# Git credential helper that uses GitHub CLI (gh) for authentication.
# Falls back through multiple installation methods to find gh.
# Enable debug logging with GH_CREDENTIAL_DEBUG=1
debug() {
if [[ "${GH_CREDENTIAL_DEBUG:-}" == "1" ]]; then
echo "DEBUG: $*" >&2
fi
}
# Show help if requested
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
cat >&2 << 'EOF'
Git credential helper for GitHub CLI (gh)
This script attempts to use 'gh auth git-credential' through various
installation methods:
1. gh in PATH
2. gh via mise tool manager
3. gh via mise in ~/.local/bin/mise
4. gh via mise shims
Environment variables:
GH_CREDENTIAL_DEBUG=1 Enable debug output
Configuration example for .gitconfig:
[credential "https://github.com"]
helper = !gh-git-credential-helper
[credential "https://gist.github.com"]
helper = !gh-git-credential-helper
EOF
exit 0
fi
if command -v gh > /dev/null 2>&1; then
debug "Using gh from PATH"
exec gh auth git-credential "$@"
elif command -v mise > /dev/null 2>&1; then
debug "Using gh via mise from PATH"
exec mise x gh -- gh auth git-credential "$@"
elif [ -f "${HOME}/.local/bin/mise" ]; then
debug "Using gh via mise from ~/.local/bin/mise"
exec "${HOME}/.local/bin/mise" x gh -- gh auth git-credential "$@"
elif [ -f "${HOME}/.local/share/mise/shims/gh" ]; then
debug "Using gh from mise shims"
exec "${HOME}/.local/share/mise/shims/gh" auth git-credential "$@"
else
echo "ERROR: GitHub CLI (gh) not found via any method" >&2
exit 1
fi

View File

@@ -13,9 +13,13 @@ show-help() {
echo "Options:"
echo " -p (Required) process name given to pgrep to narrow list of"
echo " possible target windows."
echo " -f Search full process name instead of just the first word."
echo " -e Executable name or path used to launch process when it is not"
echo " running. If not specified, will attempt to use process name"
echo " instead."
echo " -c Command used to launch process when it is not running. Can used"
echo " instead of -e if the launch command is more complex than a"
echo " single executable."
echo " -w Title/name of window which belongs to process to focus on."
echo " Allows regular expression matching. If not specified, the first"
echo " window found belonging to process will be used."
@@ -38,20 +42,28 @@ error-help() {
}
OPT_PROC=""
OPT_FULL=""
OPT_BIN=""
OPT_CMD=""
OPT_WIN=""
OPT_BRING=""
OPT_ALL=""
parse-arguments() {
while getopts ":p:e:w:bah" opt; do
while getopts ":p:e:c:w:fbah" opt; do
case ${opt} in
p)
OPT_PROC="$OPTARG"
;;
f)
OPT_FULL="1"
;;
e)
OPT_BIN="$OPTARG"
;;
c)
OPT_CMD="$OPTARG"
;;
w)
OPT_WIN="$OPTARG"
;;
@@ -63,6 +75,7 @@ parse-arguments() {
;;
h)
show-help
exit 0
;;
\?)
error-help "Invalid option: -${OPTARG}\n" 1>&2
@@ -81,7 +94,7 @@ parse-arguments() {
OPT_BIN="$OPT_PROC"
fi
if ! command -v "$OPT_BIN" &> /dev/null; then
if [ -z "$OPT_CMD" ] && ! command -v "$OPT_BIN" &> /dev/null; then
error "\"${OPT_BIN}\" does not seem to be a valid executable."
exit 2
fi
@@ -100,8 +113,13 @@ main() {
win_id="${target[1]}"
if [ -z "$pid" ]; then
echo "$OPT_PROC not running, launching with: \"$OPT_BIN\""
"$OPT_BIN"
if [ -n "$OPT_CMD" ]; then
echo "$OPT_PROC not running, launching with: \"$OPT_CMD\""
exec $OPT_CMD
else
echo "$OPT_PROC not running, launching with: \"$OPT_BIN\""
exec "$OPT_BIN"
fi
else
echo "$OPT_PROC instance found"
focused_id="$(xdotool getactivewindow)"
@@ -130,14 +148,20 @@ find-window() {
local pid
local win_id
local opts
local pgrep_opts
pgrep_opts=()
opts=(--all --onlyvisible)
if [ -n "$OPT_WIN" ]; then
opts+=(--name "$OPT_WIN")
fi
for pid in $(pgrep "$OPT_PROC"); do
if [ -n "$OPT_FULL" ]; then
pgrep_opts+=(-f)
fi
for pid in $(pgrep "${pgrep_opts[@]}" "$OPT_PROC"); do
win_id="$(xdotool search --pid "$pid" "${opts[@]}")"
if [ -n "$win_id" ]; then
echo -e "$pid\n$win_id"

View File

@@ -1,81 +0,0 @@
#! /usr/bin/env ruby
# frozen_string_literal: true
require 'json'
class MacOSBatteryExporter
def export
puts metrics
end
def metrics
<<~METRICS
# HELP node_battery_charge_percent Battery charge percent
# TYPE node_battery_charge_percent gauge
node_battery_charge_percent{serial="#{battery_serial_number}"} #{charge_percentage}
# HELP node_battery_charge_ampere Battery charge ampere
# TYPE node_battery_charge_ampere gauge
node_battery_charge_ampere{serial="#{battery_serial_number}"} #{charge_info['sppower_battery_current_capacity'] / 1000.0}
# HELP node_battery_full_charge_ampere Battery full charge capacity
# TYPE node_battery_full_charge_ampere gauge
node_battery_full_charge_ampere{serial="#{battery_serial_number}"} #{charge_info['sppower_battery_max_capacity'] / 1000.0}
# HELP node_battery_current_flow_ampere Current flow of ampere in (+) or out (-) of battery
# TYPE node_battery_current_flow_ampere gauge
node_battery_current_flow_ampere{serial="#{battery_serial_number}"} #{battery_info['sppower_current_amperage'] / 1000.0}
# HELP node_battery_charger_connected Is charger connected?
# TYPE node_battery_charger_connected gauge
node_battery_charger_connected{serial="#{battery_serial_number}"} #{ac_charger_info['sppower_battery_charger_connected'] == 'TRUE' ? 1.0 : 0.0}
# HELP node_battery_is_charging Is charger connected?
# TYPE node_battery_is_charging gauge
node_battery_is_charging{serial="#{battery_serial_number}"} #{ac_charger_info['sppower_battery_is_charging'] == 'TRUE' ? 1.0 : 0.0}
# HELP node_battery_charger_watts Watts provided by charger
# TYPE node_battery_charger_watts gauge
node_battery_charger_watts{serial="#{battery_serial_number}"} #{ac_charger_info['sppower_ac_charger_watts'].to_f}
# HELP node_battery_fully_charged Is battery fully charged?
# TYPE node_battery_fully_charged gauge
node_battery_fully_charged{serial="#{battery_serial_number}"} #{ac_charger_info['sppower_battery_fully_charged'] == 'TRUE' ? 1.0 : 0.0}
# HELP node_battery_cycle_count Battery cycle count
# TYPE node_battery_cycle_count counter
node_battery_cycle_count{serial="#{battery_serial_number}"} #{health_info['sppower_battery_cycle_count']}
METRICS
end
def charge_percentage
@charge_percentage ||=
`pmset -g batt | grep -Eo '\\d+%' | cut -d% -f1`.strip
end
def battery_serial_number
battery_info.dig(
'sppower_battery_model_info',
'sppower_battery_serial_number'
)
end
def charge_info
@charge_info ||= battery_info['sppower_battery_charge_info']
end
def health_info
@health_info ||= battery_info['sppower_battery_health_info']
end
def battery_info
@battery_info ||= power_data&.find do |v|
v['_name'] == 'spbattery_information'
end
end
def ac_charger_info
@ac_charger_info ||= power_data&.find do |v|
v['_name'] == 'sppower_ac_charger_information'
end
end
def power_data
@power_data ||= JSON.parse(`system_profiler SPPowerDataType -json`)
&.[]('SPPowerDataType')
end
end
MacOSBatteryExporter.new.export if __FILE__ == $PROGRAM_NAME

60
bin/ollama-for-gitbutler Executable file
View File

@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Defaults
DEFAULT_BIND="127.0.0.1"
DEFAULT_PORT="11435"
DEFAULT_ORIGINS="*"
DEFAULT_KEEP_ALIVE="6h"
print-help() {
cat <<EOF
Usage: ollama-for-gitbutler [options]
Options:
-b, --bind <addr> The address to bind to (default: "${DEFAULT_BIND}")
-p, --port <port> The port to listen on (default: "${DEFAULT_PORT}")
-m, --models <dir> Override ollama's default models directory
-k, --keep-alive <duration> The duration that models stay loaded in memory (default: "${DEFAULT_KEEP_ALIVE}")
-h, --help Print this help message
EOF
}
BIND="${DEFAULT_BIND}"
PORT="${DEFAULT_PORT}"
KEEP_ALIVE="${DEFAULT_KEEP_ALIVE}"
while [[ $# -gt 0 ]]; do
case "$1" in
-b | --bind)
BIND="$2"
shift 2
;;
-p | --port)
PORT="$2"
shift 2
;;
-m | --models)
export OLLAMA_MODELS="$2"
shift 2
;;
-k | --keep-alive)
KEEP_ALIVE="$2"
shift 2
;;
-h | --help)
print-help
exit 0
;;
*)
echo "Unknown option: $1" >&2
print-help >&2
exit 1
;;
esac
done
export OLLAMA_HOST="${BIND}:${PORT}"
export OLLAMA_KEEP_ALIVE="${KEEP_ALIVE}"
export OLLAMA_ORIGINS="${DEFAULT_ORIGINS}"
exec ollama serve "$@"

69
bin/ollama-for-obsidian Executable file
View File

@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Defaults
DEFAULT_BIND="127.0.0.1"
DEFAULT_PORT="11439"
DEFAULT_ORIGINS="app://obsidian.md*"
DEFAULT_KEEP_ALIVE="1h"
print-help() {
cat << EOF
Usage: ollama-for-obsidian [options]
Options:
-b, --bind <addr> The address to bind to (default: "${DEFAULT_BIND}")
-p, --port <port> The port to listen on (default: "${DEFAULT_PORT}")
-o, --origins <origins> A comma separated list of extra allowed origins
-m, --models <dir> Override ollama's default models directory
-k, --keep-alive <duration> The duration that models stay loaded in memory (default: "${DEFAULT_KEEP_ALIVE}")
-h, --help Print this help message
EOF
}
BIND="${DEFAULT_BIND}"
PORT="${DEFAULT_PORT}"
EXTRA_ORIGINS=""
KEEP_ALIVE="${DEFAULT_KEEP_ALIVE}"
while [[ $# -gt 0 ]]; do
case "$1" in
-b | --bind)
BIND="$2"
shift 2
;;
-p | --port)
PORT="$2"
shift 2
;;
-o | --origins)
EXTRA_ORIGINS="$2"
shift 2
;;
-m | --models)
export OLLAMA_MODELS="$2"
shift 2
;;
-k | --keep-alive)
KEEP_ALIVE="$2"
shift 2
;;
-h | --help)
print-help
exit 0
;;
*)
echo "Unknown option: $1" >&2
print-help >&2
exit 1
;;
esac
done
export OLLAMA_HOST="${BIND}:${PORT}"
export OLLAMA_KEEP_ALIVE="${KEEP_ALIVE}"
export OLLAMA_ORIGINS="${DEFAULT_ORIGINS}"
if [[ -n "${EXTRA_ORIGINS}" ]]; then
export OLLAMA_ORIGINS="${OLLAMA_ORIGINS},${EXTRA_ORIGINS}"
fi
exec ollama serve "$@"

177
bin/png2icns Executable file
View File

@@ -0,0 +1,177 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
echo "Usage: $(basename "$0") <source.png> [output.icns]" >&2
}
gen-png() {
local src="$1"
local px="$2"
local dpi="$3"
local out="$4"
# Resize longest side to <px>, then pad to a square <px>x<px> with transparency.
local tmp_resized
tmp_resized="${out}.tmp.png"
# Scale preserving aspect ratio; longest side becomes <px>.
sips -Z "$px" "$src" --out "$tmp_resized" > /dev/null
# Pad to exact square dimensions centered on transparent background.
sips --padToHeightWidth "$px" "$px" \
"$tmp_resized" --out "$out" > /dev/null
rm -f "$tmp_resized" || true
# Set DPI metadata.
sips -s dpiHeight "$dpi" -s dpiWidth "$dpi" "$out" > /dev/null
}
get-image-max-dim() {
local src="$1"
local w h
# Capture width/height; avoid exiting on failure due to set -e/pipefail.
w=$(sips -g pixelWidth "$src" 2> /dev/null |
awk '/pixelWidth/ {print $2}') || true
h=$(sips -g pixelHeight "$src" 2> /dev/null |
awk '/pixelHeight/ {print $2}') || true
if [[ -z "$w" || -z "$h" ]]; then
echo "Failed to read image dimensions with sips: $src" >&2
return 1
fi
if ((w > h)); then
echo "$w"
else
echo "$h"
fi
}
verify-png() {
local src="$1"
local fmt
fmt=$(sips -g format "$src" 2> /dev/null |
awk '/format/ {print tolower($2)}') || true
if [[ -z "$fmt" ]]; then
echo "Unable to determine image format with sips: $src" >&2
return 1
fi
if [[ "$fmt" != "png" ]]; then
echo "Input must be a PNG image: $src" >&2
return 1
fi
}
make-iconset() {
local src="$1"
local dest_dir="$2"
mkdir -p "$dest_dir"
# Sizes to generate (@1x). @2x files are double the pixels.
local sizes=(16 32 128 256 512)
# Determine maximum source dimension to avoid upscaling.
local src_max
if ! src_max=$(get-image-max-dim "$src"); then
return 1
fi
local generated
generated=0
for sz in "${sizes[@]}"; do
# @1x
local out1="${dest_dir}/icon_${sz}x${sz}.png"
if ((sz <= src_max)); then
gen-png "$src" "$sz" 72 "$out1"
generated=$((generated + 1))
fi
# @2x (double pixels, higher DPI)
local dsz=$((sz * 2))
local out2="${dest_dir}/icon_${sz}x${sz}@2x.png"
if ((dsz <= src_max)); then
gen-png "$src" "$dsz" 144 "$out2"
generated=$((generated + 1))
fi
done
if ((generated == 0)); then
echo "Source image too small; no icon sizes generated: $src" >&2
return 1
fi
}
cleanup() {
local path="$1"
if [[ -z "$path" || ! -d "$path" ]]; then
return 0
fi
# Remove generated PNGs inside the temp directory
find "$path" -type f -name '*.png' -delete 2> /dev/null || true
# Remove generated `*.iconset` inside the temp directory
find "$path" -type d -name '*.iconset' -delete 2> /dev/null || true
# Attempt to remove the temp directory if now empty
rmdir "$path" 2> /dev/null || true
}
main() {
if [[ $# -lt 1 || $# -gt 2 ]]; then
usage
exit 1
fi
local src="$1"
local out="${2:-${src%.*}.icns}"
if [[ ! -f "$src" ]]; then
echo "Source file not found: $src" >&2
exit 1
fi
if ! command -v sips > /dev/null 2>&1; then
echo "This script requires 'sips' (macOS) but it wasn't found." >&2
exit 1
fi
if ! command -v iconutil > /dev/null 2>&1; then
echo "This script requires 'iconutil' (macOS) but it wasn't found." >&2
exit 1
fi
local outdir
outdir="$(dirname "$out")"
mkdir -p "$outdir"
# Create a temporary working directory
tmp_root="$(mktemp -d 2> /dev/null || mktemp -d -t png2icns)"
trap 'cleanup "$tmp_root"' EXIT
# Verify input is PNG; we do not accept other formats.
if ! verify-png "$src"; then
exit 1
fi
local iconset_dir
iconset_dir="${tmp_root}/icon.iconset"
make-iconset "$src" "$iconset_dir"
# Build `.icns` from the generated iconset
if ! iconutil -c icns -o "$out" "$iconset_dir" 2> /dev/null; then
echo "Failed to create .icns with iconutil" >&2
exit 1
fi
echo "Done. ICNS written to: $out"
}
main "$@"

461
bin/pngs2collage Executable file
View File

@@ -0,0 +1,461 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat >&2 << EOF
Usage: $(basename "$0") [OPTIONS] <input1.png> <input2.png> [input3.png ...]
Create a grid collage from multiple PNG images.
OPTIONS:
-o, --output <file> Output filename (default: collage.png)
--max-width <pixels> Maximum width for grid cells (default: 1024)
--max-height <pixels> Maximum height for grid cells (default: 1024)
--gap <pixels> Gap between images (default: 0)
--border <pixels> Border around entire collage (default: 0)
--max-columns <count> Maximum columns in grid (default: auto)
--dpi <value> DPI for output image (default: 72)
-h, --help Show this help message
ARGUMENTS:
At least 2 input PNG files are required.
EXAMPLES:
$(basename "$0") img1.png img2.png
$(basename "$0") -o output.png --gap 10 img1.png img2.png img3.png
$(basename "$0") --output result.png --max-columns 3 *.png
EOF
}
verify-png() {
local src="$1"
local fmt
fmt=$(sips -g format "$src" 2> /dev/null |
awk '/format/ {print tolower($2)}') || true
if [[ -z "$fmt" ]]; then
echo "Unable to determine image format with sips: $src" >&2
return 1
fi
if [[ "$fmt" != "png" ]]; then
echo "Input must be a PNG image: $src" >&2
return 1
fi
}
get-image-dimensions() {
local src="$1"
local w h
w=$(sips -g pixelWidth "$src" 2> /dev/null |
awk '/pixelWidth/ {print $2}') || true
h=$(sips -g pixelHeight "$src" 2> /dev/null |
awk '/pixelHeight/ {print $2}') || true
if [[ -z "$w" || -z "$h" ]]; then
echo "Failed to read image dimensions with sips: $src" >&2
return 1
fi
echo "$w $h"
}
calculate-cell-size() {
local max_w="$1"
local max_h="$2"
shift 2
local images=("$@")
local cell_w=0
local cell_h=0
local any_exceeds=0
for img in "${images[@]}"; do
local dims
if ! dims=$(get-image-dimensions "$img"); then
return 1
fi
local w h
read -r w h <<< "$dims"
if ((w > max_w || h > max_h)); then
any_exceeds=1
fi
if ((w > cell_w)); then
cell_w=$w
fi
if ((h > cell_h)); then
cell_h=$h
fi
done
# If any image exceeds max dimensions, use max dimensions as cell size
if ((any_exceeds)); then
if ((max_w > max_h)); then
echo "$max_w $max_w"
else
echo "$max_h $max_h"
fi
else
# Use largest dimension from all images to make square cells
if ((cell_w > cell_h)); then
echo "$cell_w $cell_w"
else
echo "$cell_h $cell_h"
fi
fi
}
calculate-padding() {
local row="$1"
local col="$2"
local total_rows="$3"
local total_cols="$4"
local gap="$5"
local border="$6"
local top bottom left right
local half_gap
# Calculate half gap (rounded for odd gaps)
half_gap=$(awk "BEGIN {print int($gap / 2)}")
# Top padding
if ((row == 0)); then
top=$border
else
top=$half_gap
fi
# Bottom padding
if ((row == total_rows - 1)); then
bottom=$border
else
bottom=$half_gap
fi
# Left padding
if ((col == 0)); then
left=$border
else
left=$half_gap
fi
# Right padding
if ((col == total_cols - 1)); then
right=$border
else
right=$half_gap
fi
echo "$top $bottom $left $right"
}
prepare-image() {
local src="$1"
local cell_size="$2"
local top="$3"
local bottom="$4"
local left="$5"
local right="$6"
local out="$7"
local tmp_resized tmp_padded
tmp_resized="${out}.tmp.resized.png"
tmp_padded="${out}.tmp.padded.png"
# Scale preserving aspect ratio; fit within cell size
sips -Z "$cell_size" "$src" --out "$tmp_resized" > /dev/null
# Pad to exact square dimensions centered on transparent background
sips --padToHeightWidth "$cell_size" "$cell_size" \
"$tmp_resized" --out "$tmp_padded" > /dev/null
# Apply asymmetric padding using ImageMagick
# Create a transparent canvas and composite the image at the correct position
local final_width final_height
final_width=$((cell_size + left + right))
final_height=$((cell_size + top + bottom))
# Create transparent canvas of final size, then composite the prepared
# image at position (left, top) to create the exact padding we want
if ! magick -size "${final_width}x${final_height}" xc:none \
"$tmp_padded" -geometry "+${left}+${top}" -composite \
"$out" 2> /dev/null; then
echo "Failed to apply padding to image: $src" >&2
rm -f "$tmp_resized" "$tmp_padded" || true
return 1
fi
rm -f "$tmp_resized" "$tmp_padded" || true
}
calculate-grid-dimensions() {
local count="$1"
local max_cols="$2"
local cols rows
if [[ "$max_cols" == "auto" ]]; then
# Calculate square-ish grid
cols=$(awk "BEGIN {print int(sqrt($count) + 0.999)}")
else
if ((max_cols > count)); then
cols=$count
else
cols=$max_cols
fi
fi
rows=$(awk "BEGIN {print int(($count + $cols - 1) / $cols)}")
echo "$cols $rows"
}
create-collage() {
local cols="$1"
local rows="$2"
local dpi="$3"
local out="$4"
shift 4
local images=("$@")
# Build ImageMagick montage command
# montage combines images in a grid with zero spacing
# (padding is already applied to individual images)
local tile_arg="${cols}x${rows}"
# Create collage with transparent background and zero spacing
if ! magick montage "${images[@]}" \
-tile "$tile_arg" \
-geometry "+0+0" \
-background "none" \
"$out" 2> /dev/null; then
echo "Failed to create collage with ImageMagick" >&2
return 1
fi
# Set DPI metadata
sips -s dpiHeight "$dpi" -s dpiWidth "$dpi" "$out" > /dev/null
}
cleanup() {
local path="$1"
if [[ -z "$path" || ! -d "$path" ]]; then
return 0
fi
# Remove generated PNGs inside the temp directory
find "$path" -type f -name '*.png' -delete 2> /dev/null || true
# Attempt to remove the temp directory if now empty
rmdir "$path" 2> /dev/null || true
}
parse-args() {
local max_width=1024
local max_height=1024
local gap=0
local border=0
local max_columns="auto"
local dpi=72
local output_file=""
local -a input_files=()
while [[ $# -gt 0 ]]; do
case "$1" in
-h | --help)
usage
exit 0
;;
-o | --output)
output_file="$2"
shift 2
;;
--max-width)
max_width="$2"
shift 2
;;
--max-height)
max_height="$2"
shift 2
;;
--gap)
gap="$2"
shift 2
;;
--border)
border="$2"
shift 2
;;
--max-columns)
max_columns="$2"
shift 2
;;
--dpi)
dpi="$2"
shift 2
;;
-*)
echo "Unknown option: $1" >&2
usage
exit 1
;;
*)
input_files+=("$1")
shift
;;
esac
done
# Export parsed values for main to access
export PARSED_MAX_WIDTH="$max_width"
export PARSED_MAX_HEIGHT="$max_height"
export PARSED_GAP="$gap"
export PARSED_BORDER="$border"
export PARSED_MAX_COLUMNS="$max_columns"
export PARSED_DPI="$dpi"
export PARSED_OUTPUT="$output_file"
export PARSED_INPUT_COUNT="${#input_files[@]}"
# Export input files as a string (will be parsed in main)
local i
for i in "${!input_files[@]}"; do
export "PARSED_INPUT_$i=${input_files[$i]}"
done
}
main() {
if [[ $# -lt 2 ]]; then
usage
exit 1
fi
# Parse arguments
parse-args "$@"
# Reconstruct input files array from exports
local -a all_files=()
local i
for ((i = 0; i < PARSED_INPUT_COUNT; i++)); do
local varname="PARSED_INPUT_$i"
all_files+=("${!varname}")
done
# Determine output file and input files
local -a input_files=()
local output_file
if [[ -n "$PARSED_OUTPUT" ]]; then
# Explicit output was specified via --output/-o
output_file="$PARSED_OUTPUT"
input_files=("${all_files[@]}")
else
# No explicit output, use default or check last argument
output_file="collage.png"
if [[ ${#all_files[@]} -ge 2 ]]; then
local last_file="${all_files[-1]}"
# If last file doesn't exist and has .png extension, treat as output
if [[ ! -f "$last_file" && "$last_file" == *.png ]]; then
output_file="$last_file"
input_files=("${all_files[@]:0:$((${#all_files[@]} - 1))}")
else
input_files=("${all_files[@]}")
fi
else
input_files=("${all_files[@]}")
fi
fi
# Validate we have at least 2 input files
if [[ ${#input_files[@]} -lt 2 ]]; then
echo "Error: At least 2 input PNG files are required." >&2
usage
exit 1
fi
# Check for required commands
if ! command -v sips > /dev/null 2>&1; then
echo "This script requires 'sips' (macOS) but it wasn't found." >&2
exit 1
fi
if ! command -v magick > /dev/null 2>&1; then
echo "This script requires 'magick' (ImageMagick) but it wasn't found." >&2
exit 1
fi
# Verify all input files exist and are PNGs
for img in "${input_files[@]}"; do
if [[ ! -f "$img" ]]; then
echo "Input file not found: $img" >&2
exit 1
fi
if ! verify-png "$img"; then
exit 1
fi
done
# Create output directory if needed
local outdir
outdir="$(dirname "$output_file")"
mkdir -p "$outdir"
# Create temporary working directory
tmp_root="$(mktemp -d 2> /dev/null || mktemp -d -t pngs2collage)"
trap 'cleanup "$tmp_root"' EXIT
# Calculate grid dimensions first
local grid_dims
grid_dims=$(calculate-grid-dimensions "${#input_files[@]}" \
"$PARSED_MAX_COLUMNS")
local cols rows
read -r cols rows <<< "$grid_dims"
# Calculate cell size
local cell_dims
if ! cell_dims=$(calculate-cell-size "$PARSED_MAX_WIDTH" \
"$PARSED_MAX_HEIGHT" "${input_files[@]}"); then
exit 1
fi
local cell_size
read -r cell_size _ <<< "$cell_dims"
# Prepare all images with position-specific padding
local -a prepared_images=()
local idx=0
for img in "${input_files[@]}"; do
# Calculate grid position (row, col) from index
local row col
row=$((idx / cols))
col=$((idx % cols))
# Calculate padding for this position
local padding
padding=$(calculate-padding "$row" "$col" "$rows" "$cols" \
"$PARSED_GAP" "$PARSED_BORDER")
local top bottom left right
read -r top bottom left right <<< "$padding"
# Prepare image with position-specific padding
local prepared="${tmp_root}/prepared_${idx}.png"
if ! prepare-image "$img" "$cell_size" "$top" "$bottom" "$left" \
"$right" "$prepared"; then
echo "Failed to prepare image: $img" >&2
exit 1
fi
prepared_images+=("$prepared")
idx=$((idx + 1))
done
# Create the collage
if ! create-collage "$cols" "$rows" "$PARSED_DPI" "$output_file" \
"${prepared_images[@]}"; then
exit 1
fi
echo "Done. Collage written to: $output_file"
}
main "$@"

12
bin/vk Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
export BACKEND_PORT=34802
if command -v vibe-kanban >/dev/null 2>&1; then
exec vibe-kanban "$@"
elif command -v npx >/dev/null 2>&1; then
exec npx vibe-kanban "$@"
else
echo "Neither vibe-kanban nor npx is installed"
exit 1
fi

78
config/ghostty/config Normal file
View File

@@ -0,0 +1,78 @@
# ------------------------------------------------------------------------------
# General settings
# ------------------------------------------------------------------------------
auto-update-channel = stable
# ------------------------------------------------------------------------------
# Theme settings
# ------------------------------------------------------------------------------
theme = tango-with-monokai
window-theme = dark
background-opacity = 0.8
background-blur-radius = 20
# ------------------------------------------------------------------------------
# Font settings
# ------------------------------------------------------------------------------
font-size = 12
font-family = Menlo Nerd Font
font-family-bold = Menlo Nerd Font Bold
font-family-italic = Menlo Nerd Font Italic
font-family-bold-italic = Menlo Nerd Font Bold Italic
# Match font rendering of iTerm and Terminal.app.
adjust-cell-width = -1
font-thicken = true
# Disable ligatures
font-feature = -calt
font-feature = -dlig
font-feature = -liga
# ------------------------------------------------------------------------------
# macOS settings
# ------------------------------------------------------------------------------
macos-non-native-fullscreen = true
macos-option-as-alt = true
macos-titlebar-proxy-icon = hidden
macos-titlebar-style = transparent
macos-secure-input-indication = true
macos-auto-secure-input = true
# ------------------------------------------------------------------------------
# Cursor settings
# ------------------------------------------------------------------------------
cursor-style = block
shell-integration-features = no-cursor
mouse-hide-while-typing = true
# ------------------------------------------------------------------------------
# Window settings
# ------------------------------------------------------------------------------
window-padding-balance = true
window-step-resize = true
# macOS notch padding: 14-inch@2704x1756
# window-padding-y = 60,20
# ------------------------------------------------------------------------------
# Keybindings
# ------------------------------------------------------------------------------
# Clipboard (ensure super/cmd+c/v are always available, including on Linux)
keybind = super+c=copy_to_clipboard
keybind = super+v=paste_from_clipboard
# Quick Terminal (alt+`)
keybind = global:alt+grave_accent=toggle_quick_terminal

View File

@@ -0,0 +1,21 @@
palette = 0=#000000
palette = 1=#cf5041
palette = 2=#8bba48
palette = 3=#c3a643
palette = 4=#37689f
palette = 5=#9b589d
palette = 6=#69b8cb
palette = 7=#cccccc
palette = 8=#686a66
palette = 9=#ed3a2c
palette = 10=#96cf4d
palette = 11=#fdeb61
palette = 12=#5696d5
palette = 13=#9d6aa5
palette = 14=#75cee3
palette = 15=#f5f5f5
background = 000000
foreground = ffffff
cursor-color = D0D0D0
selection-background = 7499c7
selection-foreground = 000000

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
color0 #000000
color1 #cf5041
color2 #8bba48
color3 #c3a643
color4 #37689f
color5 #9b589d
color6 #69b8cb
color7 #cccccc
color8 #686a66
color9 #ed3a2c
color10 #96cf4d
color11 #fdeb61
color12 #5696d5
color13 #9d6aa5
color14 #75cee3
color15 #f5f5f5
background #000000
foreground #ffffff
cursor #D0D0D0
selection_background #7499c7
selection_foreground #000000
url_color #0087bd

179
config/mise/config.toml Normal file
View File

@@ -0,0 +1,179 @@
[settings]
cargo_binstall = true
experimental = true
legacy_version_file = true
not_found_auto_install = false
idiomatic_version_file_enable_tools = [
"ruby",
"python",
"node",
"go",
"terraform",
"yarn",
]
[settings.status]
show_env = true
show_tools = false
[settings.npm]
bun = true
[settings.pipx]
# Allow mise to install pipx packages without manual installation of pipx.
uvx = true
[settings.ruby]
# Precompiled binaries causes issues with Ruby LSP.
compile = true
[tools]
"aqua:AlexanderGrooff/mermaid-ascii" = "latest"
"aqua:svenstaro/miniserve" = "latest"
"aqua:tldr-pages/tlrc" = "latest"
"cargo:bacon" = "latest"
"cargo:cargo-audit" = "latest"
"cargo:cargo-edit" = "latest"
"cargo:cargo-info" = "latest"
"cargo:cargo-show" = "latest"
"cargo:cargo-update" = "latest"
"cargo:dirstat-rs" = "latest"
"cargo:dutree" = "latest"
"cargo:eza" = "latest"
"cargo:kubectl-watch" = "latest"
"cargo:mergiraf" = "latest"
"cargo:paper-terminal" = "latest"
"cargo:parallel-disk-usage" = { version = "latest", bin = "pdu" }
"cargo:pastel" = "latest"
"cargo:riffdiff" = "latest"
"github:stacklok/toolhive" = "latest"
"go:github.com/rakyll/hey" = "latest"
"go:google.golang.org/grpc/cmd/protoc-gen-go-grpc" = "latest"
"go:google.golang.org/protobuf/cmd/protoc-gen-go" = "latest"
"npm:@github/copilot-language-server" = "latest"
"npm:@mermaid-js/mermaid-cli" = "latest"
"npm:@openai/codex" = "latest"
"npm:@prettier/plugin-php" = "latest"
"npm:claude-plugins" = "latest"
"npm:convex" = "latest"
"npm:dockerfile-language-server-nodejs" = "latest"
"npm:eslint" = "latest"
"npm:eslint-config-prettier" = "latest"
"npm:eslint-plugin-prettier" = "latest"
"npm:eslint_d" = "latest"
"npm:htmllint-cli" = "latest"
"npm:http-server" = "latest"
"npm:httpsnippet" = "latest"
"npm:jsonlint" = "latest"
"npm:localtunnel" = "latest"
"npm:markdown-it" = "latest"
"npm:oxfmt" = "latest"
"npm:oxlint" = "latest"
"npm:prettier" = "latest"
"npm:prettier-plugin-toml" = "latest"
"npm:prettier-pnp" = "latest"
"npm:skills" = "latest"
"npm:stylelint" = "latest"
"npm:svgo" = "latest"
"npm:typescript" = "latest"
"npm:typescript-formatter" = "latest"
"npm:typescript-language-server" = "latest"
"npm:vscode-css-languageserver-bin" = "latest"
"npm:vscode-json-languageserver" = "latest"
"npm:yaml-language-server" = "latest"
"npm:yarn" = "latest"
"pipx:ansible-lint" = "latest"
"pipx:dmgbuild" = "latest"
"pipx:fonttools" = "latest"
"pipx:pipx" = "latest"
"pipx:toml-sort" = "latest"
"pipx:yamllint" = "latest"
1password = "latest"
actionlint = "latest"
argo-rollouts = "latest"
argocd = "latest"
atuin = "latest"
aws-cli = "latest"
bat = "latest"
bat-extras = "latest"
buf = "latest"
bun = "latest"
cargo-binstall = "latest"
cloudflared = "latest"
crane = "latest"
ctop = "latest"
difftastic = "latest"
direnv = "latest"
dua = "latest"
dust = "latest"
evans = "latest"
fd = "latest"
fzf = "latest"
gemini = "latest"
gh = "latest"
gitu = "latest"
go = "latest"
gofumpt = "latest"
golangci-lint = "latest"
golangci-lint-langserver = "latest"
golines = "latest"
goreleaser = "latest"
grpcurl = "latest"
hadolint = "latest"
helm = "latest"
helmfile = "latest"
hexyl = "latest"
hivemind = "latest"
hwatch = "latest"
jq = "latest"
jwt = "latest"
jwtui = "latest"
k3d = "latest"
k9s = "latest"
kubecm = "latest"
kubectl = "latest"
kubectx = "latest"
kubelogin = "latest"
kubens = "latest"
kustomize = "latest"
lua = "latest"
lua-language-server = "latest"
markdownlint-cli2 = "latest"
node = "lts"
opencode = "latest"
opentofu = "latest"
pnpm = "latest"
python = "latest"
rclone = "latest"
restish = "latest"
ripgrep = "latest"
ruby = "latest"
rust = { version = "latest", components = "rust-analyzer,rust-src" }
sccache = "latest"
shellcheck = "latest"
shfmt = "latest"
slsa-verifier = "latest"
sops = "latest"
starship = "latest"
staticcheck = "latest"
stern = "latest"
talosctl = "latest"
taplo = "latest"
terraform = "latest"
terraform-ls = "latest"
tflint = "latest"
ubi = "latest"
usage = "latest"
uv = "latest"
watchexec = "latest"
yj = "latest"
yq = "latest"
zig = "latest"
zls = "latest"
zoxide = "latest"
# Install ansible with passlib and bcrypt<4.1, as passlib is not compatible with
# bcrypt 4.1 or later.
[tools."pipx:ansible"]
version = "latest"
uvx_args = "--with-executables-from=ansible-core --with=passlib --with='bcrypt<4.1'"

1
config/nix/nix.conf Normal file
View File

@@ -0,0 +1 @@
experimental-features = nix-command flakes

View File

@@ -1,4 +1,6 @@
---
# plugins:
# - solargraph-rails
exclude:
- ".bundle/**/*"
- "spec/**/*"
@@ -16,6 +18,7 @@ formatter:
- Lint/Debugger
- Lint/UnusedBlockArgument
- Lint/UnusedMethodArgument
- Rails/Output
- Lint/UselessAssignment
# - Rails/Output
- Style/EmptyMethod
- Style/Semicolon

View File

@@ -1,15 +1,20 @@
# ------------------------------------------------------------------------------
# jimeh's Starship Config
# ------------------------------------------------------------------------------
# Version: 0.1.2
# Version: 0.5.0
# URL: https://github.com/jimeh/dotfiles/blob/main/config/starship.toml
# ------------------------------------------------------------------------------
# This is minimalistic Starship (https://starship.rs/) prompt setup with most
# features shifted to the right prompt, and disabled, allowing for easy cherry
# picking of specific features to enable.
# ------------------------------------------------------------------------------
#
# ------------------------------------------------------------------------------
# MARK: Format
# ------------------------------------------------------------------------------
add_newline = false
format = """
$username\
$hostname\
@@ -20,195 +25,241 @@ $git_state\
$git_metrics\
$git_status\
$hg_branch\
$fossil_branch\
$fossil_metrics\
$hg_state\
$line_break\
$character"""
right_format = """
$all\
$localip\
$shlvl\
$singularity\
$kubernetes\
$vcsh\
$pijul_channel\
$docker_context\
$package\
$c\
$cmake\
$cobol\
$crystal\
$daml\
$dart\
$deno\
$dotnet\
$elixir\
$elm\
$erlang\
$fennel\
$fortran\
$gleam\
$golang\
$guix_shell\
$haskell\
$haxe\
$java\
$julia\
$kotlin\
$gradle\
$lua\
$nim\
$nodejs\
$bun\
$ocaml\
$opa\
$perl\
$php\
$pulumi\
$purescript\
$python\
$red\
$quarto\
$raku\
$rlang\
$red\
$ruby\
$rust\
$scala\
$solidity\
$swift\
$terraform\
$typst\
$vlang\
$vagrant\
$zig\
$aws\
$azure\
$buf\
$nix_shell\
$conda\
$docker_context\
$meson\
$spack\
$aws\
$gcloud\
$helm\
$kubernetes\
$nix_shell\
$openstack\
$package\
$singularity\
$terraform\
$vagrant\
$vcsh\
$shlvl\
$azure\
$env_var\
$crystal\
$custom\
$cmd_duration\
$status\
$line_break\
$direnv\
$mise\
$nats\
$jobs\
$netns\
$shell\
$sudo\
$battery\
$memory_usage\
$os\
$container\
$time"""
# ------------------------------------------------------------------------------
# Main left prompt components
# MARK: Left prompt
# ------------------------------------------------------------------------------
#
[username]
show_always = true
format = "[$user]($style)"
style_user = "fg:251"
style_root = "bold fg:196 bg:52"
format = '[$user]($style)'
style_user = 'fg:251'
style_root = 'bold fg:196 bg:52'
[hostname]
ssh_only = false
format = "([@$hostname]($style) )"
style = "fg:251"
format = '([@$hostname]($style) )'
style = 'fg:251'
[directory]
truncate_to_repo = false
truncation_length = 99
format = '[$path]($style)[$read_only]($read_only_style) '
style = ""
read_only = ""
read_only_style = "bold red"
style = ''
read_only = ''
read_only_style = 'bold red'
[line_break]
disabled = true
[character]
success_symbol = "[](bold fg:118)"
error_symbol = "[](bold fg:46)"
vicmd_symbol = "[](bold fg:46)"
success_symbol = '[](bold fg:118)'
error_symbol = '[](bold fg:46)'
vimcmd_symbol = '[](bold fg:46)'
# ------------------------------------------------------------------------------
# Source Control
# MARK: Source Control
# ------------------------------------------------------------------------------
#
[git_branch]
format = '([$symbol$branch]($style))'
style = "fg:51"
symbol = ""
style = 'fg:51'
symbol = ''
truncation_length = 24
[git_commit]
tag_symbol = ""
tag_symbol = ''
format = '[\($hash$tag\)]($style)'
style = "bold fg:213"
style = 'bold fg:213'
only_detached = true
tag_disabled = false
[git_state]
format = '\[[$state($progress_current/$progress_total)]($style)\]'
style = "bold fg:208"
style = 'bold fg:208'
[git_metrics]
added_style = "bold fg:148"
deleted_style = "bold fg:196"
added_style = 'bold fg:148'
deleted_style = 'bold fg:196'
format = '([\[](fg:51)([+$added]($added_style))([-$deleted]($deleted_style))[\]](fg:51))'
disabled = true
[git_status]
format = '([$all_status$ahead_behind]($style)) '
style = "bold fg:201"
stashed = ""
style = 'bold fg:201'
stashed = ''
ahead = ""
behind = ""
modified = "!"
untracked = "?"
diverged = ""
deleted = "✖"
behind = ''
modified = '!'
untracked = '?'
diverged = ''
deleted = '✖'
[hg_branch]
format = '([$symbol$branch]($style))'
style = "fg:51"
symbol = ""
style = 'fg:51'
symbol = ''
truncation_length = 24
# ------------------------------------------------------------------------------
# Right prompt "status" components
# MARK: Right prompt
# ------------------------------------------------------------------------------
#
[shlvl]
symbol = ""
format = "( [$symbol$shlvl]($style))"
symbol = ''
format = '( [$symbol$shlvl]($style))'
disabled = true
[cmd_duration]
format = '( [\(](fg:242)[$duration]($style)[\)](fg:242))'
style = "bold fg:220"
style = 'bold fg:220'
[sudo]
symbol = ""
format = "[$symbol]($style)"
style = "bold dimmed red"
symbol = ''
format = '[$symbol]($style)'
style = 'bold dimmed red'
disabled = false
[status]
format = "( [$symbol$status]($style))"
style = "bold red"
symbol = ''
success_symbol = ''
not_executable_symbol = ''
not_found_symbol = ''
sigint_symbol = ''
signal_symbol = '󱐋'
format = '( [$symbol$status]($style))'
style = 'bold red'
disabled = false
[jobs]
symbol = "✦"
format = "( [$symbol$number]($style))"
style = "bold blue"
symbol = '✦'
format = '( [$symbol$number]($style))'
style = 'bold blue'
symbol_threshold = 1
number_threshold = 1
disabled = false
[shell]
bash_indicator = " bsh"
fish_indicator = " fsh"
zsh_indicator = " zsh"
powershell_indicator = " psh"
bash_indicator = ' bsh'
fish_indicator = ' fsh'
zsh_indicator = ' zsh'
powershell_indicator = ' psh'
ion_indicator = " ion"
elvish_indicator = " esh"
tcsh_indicator = " tsh"
xonsh_indicator = " xsh"
cmd_indicator = " cmd"
nu_indicator = " nu"
format = "[$indicator]($style)"
elvish_indicator = ' esh'
tcsh_indicator = ' tsh'
xonsh_indicator = ' xsh'
cmd_indicator = ' cmd'
nu_indicator = ' nu'
format = '[$indicator]($style)'
disabled = true
[localip]
format = '( @[$localipv4](bold red))'
disabled = true
[os]
format = '( [$symbol]($style))'
disabled = true
[battery]
format = "( [$symbol$percentage]($style))"
format = '( [$symbol$percentage]($style))'
[[battery.display]]
threshold = 10
style = "bold red"
style = 'bold red'
[memory_usage]
threshold = 75
symbol = " "
symbol = '󰍛 '
format = '( [$symbol${ram}]($style))'
disabled = true
@@ -218,217 +269,326 @@ style = "bold fg:239"
disabled = false
# ------------------------------------------------------------------------------
# Languages
# MARK: Languages
# ------------------------------------------------------------------------------
#
[buf]
format = "( [$symbol($version)]($style))"
symbol = " "
symbol = ' '
format = '( [$symbol($version)]($style))'
disabled = false
[bun]
symbol = ' '
format = '( [$symbol$version]($style))'
style = 'bold blue'
disabled = false
[c]
format = '( [$symbol($version(-$name))]($style))'
disabled = true
[cmake]
format = "( [$symbol$version]($style))"
disabled = true
[crystal]
format = "( [$symbol$version]($style))"
format = '( [$symbol$version]($style))'
disabled = true
[cobol]
symbol = "漣 "
format = "( [$symbol$version]($style))"
symbol = '󰒓 '
format = '( [$symbol$version]($style))'
disabled = true
[crystal]
format = '( [$symbol$version]($style))'
disabled = false
[daml]
format = '( [$symbol($version)]($style))'
disabled = true
[dart]
symbol = ""
format = "( [$symbol$version]($style))"
disabled = true
symbol = ''
format = '( [$symbol$version]($style))'
disabled = false
[deno]
format = "( [$symbol$version]($style))"
disabled = true
format = '( [$symbol$version]($style))'
disabled = false
[dotnet]
symbol = " "
format = "( [$symbol($version)( 🎯 $tfm)]($style))"
disabled = true
symbol = '󰪮 '
format = '( [$symbol($version)( 🎯 $tfm)]($style))'
disabled = false
[elixir]
symbol = ""
symbol = ''
format = '( [$symbol($version \(OTP $otp_version\))]($style))'
disabled = true
disabled = false
[elm]
symbol = ""
format = "( [$symbol$version]($style))"
symbol = ''
format = '( [$symbol$version]($style))'
disabled = true
[erlang]
symbol = ""
format = "( [$symbol$version]($style))"
symbol = ''
format = '( [$symbol$version]($style))'
disabled = true
[fennel]
format = '( [$symbol($version)]($style))'
disabled = true
[fortran]
format = '( [$symbol$version]($style))'
disabled = true
[gleam]
format = '( [$symbol($version)]($style))'
disabled = false
[golang]
symbol = ""
format = "( [$symbol$version]($style))"
symbol = ''
format = '( [$symbol$version]($style))'
disabled = false
[guix_shell]
format = '( [$symbol]($style))'
disabled = true
[haskell]
format = '( [$symbol($version)]($style))'
disabled = false
[haxe]
format = '( [$symbol($version)]($style))'
disabled = true
[java]
symbol = ""
format = "( [$symbol$version]($style))"
symbol = ''
format = '( [$symbol$version]($style))'
disabled = true
[julia]
symbol = ""
format = "( [$symbol$version]($style))"
symbol = ''
format = '( [$symbol$version]($style))'
disabled = true
[kotlin]
format = "( [$symbol$version]($style))"
format = '( [$symbol$version]($style))'
disabled = true
[lua]
symbol = ""
format = "( [$symbol$version]($style))"
disabled = true
symbol = ''
format = '( [$symbol$version]($style))'
disabled = false
[nim]
symbol = " "
format = "( [$symbol$version]($style))"
symbol = '󰆥 '
format = '( [$symbol$version]($style))'
disabled = true
[nodejs]
symbol = ""
format = "( [$symbol$version]($style))"
disabled = true
symbol = ''
format = '( [$symbol$version]($style))'
disabled = false
[ocaml]
format = '( [$symbol$version(\($switch_indicator$switch_name\))]($style))'
disabled = true
[perl]
symbol = ""
format = "( [$symbol$version]($style))"
symbol = ''
format = '( [$symbol$version]($style))'
disabled = true
[php]
symbol = ""
format = "( [$symbol$version]($style))"
disabled = true
symbol = ''
format = '( [$symbol$version]($style))'
disabled = false
[purescript]
format = "( [$symbol$version]($style))"
format = '( [$symbol$version]($style))'
disabled = true
[python]
symbol = ""
symbol = ''
format = '( [${symbol}${pyenv_prefix}${version}( \($virtualenv\))]($style))'
disabled = true
[raku]
format = '( [$symbol($version-$vm_version)]($style))'
disabled = true
[rlang]
format = "( [$symbol$version]($style))"
format = '( [$symbol$version]($style))'
disabled = true
[red]
format = "( [$symbol$version]($style))"
format = '( [$symbol$version]($style))'
disabled = true
[ruby]
symbol = ""
format = "( [$symbol$version]($style))"
disabled = true
symbol = ''
format = '( [$symbol$version]($style))'
disabled = false
[rust]
symbol = ""
format = "( [$symbol$version]($style))"
disabled = true
symbol = ''
format = '( [$symbol$version]($style))'
disabled = false
[scala]
symbol = ""
format = "( [$symbol$version]($style))"
symbol = ''
format = '( [$symbol$version]($style))'
disabled = true
[solidity]
format = '( [$symbol$version]($style))'
disabled = false
[swift]
symbol = "ﯣ "
format = "( [$symbol$version]($style))"
disabled = true
symbol = ' '
format = '( [$symbol$version]($style))'
disabled = false
[vlang]
format = "( [$symbol$version]($style))"
format = '( [$symbol$version]($style))'
disabled = true
[zig]
format = "( [$symbol$version]($style))"
disabled = true
format = '( [$symbol$version]($style))'
disabled = false
# ------------------------------------------------------------------------------
# Tools
# MARK: Tools
# ------------------------------------------------------------------------------
#
[aws]
symbol = ""
symbol = ''
format = '( [$symbol($profile)( \($region\))( \[$duration\])]($style))'
disabled = true
[azure]
symbol = "ﴃ "
format = "( [$symbol($subscription)]($style))"
symbol = '󰠅 '
format = '( [$symbol($subscription)]($style))'
disabled = true
[conda]
symbol = ""
format = "( [$symbol$environment]($style))"
symbol = ''
format = '( [$symbol$environment]($style))'
[container]
format = '( [$symbol \[$name\]]($style))'
disabled = false
[direnv]
symbol = ' '
format = '( [$symbol]($style)[(($loaded/)$allowed)](red))'
allowed_msg = ''
denied_msg = 'denied'
loaded_msg = ''
not_allowed_msg = 'not allowed'
unloaded_msg = ''
disabled = false
style = 'bold green'
[docker_context]
symbol = ""
format = "( [$symbol$context]($style))"
symbol = ''
format = '( [$symbol$context]($style))'
disabled = true
[env_var]
format = '( with [$env_value]($style))'
disabled = true
[fossil_branch]
format = '( [$symbol$branch]($style))'
disabled = true
[gcloud]
symbol = ""
symbol = ''
format = '( [$symbol$account(@$domain)(\($region\))]($style))'
disabled = true
[helm]
format = "( [$symbol$version]($style))"
[gradle]
format = '( [$symbol($version)]($style))'
disabled = true
[helm]
format = '( [$symbol$version]($style))'
disabled = false
[kubernetes]
symbol = ""
symbol = ''
format = '( [$symbol$context(\($namespace\))]($style))'
disabled = false
[meson]
format = '( [$symbol$project]($style))'
disabled = true
[mise]
format = '( [$symbol$health]($style))'
disabled = true
[nats]
format = '( [$symbol$name]($style))'
disabled = false
[netns]
format = '( [$symbol \[$name\]]($style))'
disabled = true
[nix_shell]
symbol = ""
symbol = ''
format = '( [$symbol$state(\($name\))]($style))'
disabled = false
[opa]
format = '( [$symbol($version)]($style))'
disabled = true
[openstack]
symbol = ""
symbol = ''
format = '( [$symbol$cloud(\($project\))]($style))'
disabled = true
[package]
symbol = " "
format = "( [$symbol$version]($style))"
symbol = '󰏗 '
format = '( [$symbol$version]($style))'
disabled = true
[pijul_channel]
format = '( [$symbol$channel]($style))'
disabled = true
[pulumi]
format = "( [$symbol$stack]($style))"
format = '( [$symbol$stack]($style))'
disabled = true
[quarto]
format = '( [$symbol$version]($style))'
disabled = false
[singularity]
format = '( [$symbol\[$env\]]($style))'
disabled = true
[terraform]
format = "( [$symbol$workspace]($style))"
[spack]
format = '( [$symbol$environment]($style))'
disabled = true
[terraform]
format = '( [$symbol$workspace]($style))'
disabled = false
[typst]
format = '( [$symbol$version]($style))'
disabled = false
[vagrant]
format = "( [$symbol($version )]($style))"
disabled = true
format = '( [$symbol($version)]($style))'
disabled = false
[vcsh]
format = "(vcsh [$symbol$repo]($style))"
format = '(vcsh [$symbol$repo]($style))'
disabled = true

76
config/tlrc/config.toml Normal file
View File

@@ -0,0 +1,76 @@
[output]
show_title = true
platform_title = false
show_hyphens = true
example_prefix = "- "
compact = false
raw_markdown = false
[indent]
title = 2
description = 2
bullet = 2
example = 4
[style.title]
color = "magenta"
background = "default"
bold = true
underline = false
italic = false
dim = false
strikethrough = false
[style.description]
color = "default"
background = "default"
bold = false
underline = false
italic = false
dim = false
strikethrough = false
[style.bullet]
color = "default"
background = "default"
bold = false
underline = false
italic = false
dim = false
strikethrough = false
[style.example]
color = "green"
background = "default"
bold = false
underline = false
italic = false
dim = false
strikethrough = false
[style.url]
color = "cyan"
background = "default"
bold = false
underline = true
italic = false
dim = false
strikethrough = false
[style.inline_code]
color = "yellow"
background = "default"
bold = false
underline = false
italic = false
dim = false
strikethrough = false
[style.placeholder]
color = "red"
background = "default"
bold = false
underline = false
italic = false
dim = false
strikethrough = false

View File

@@ -0,0 +1,53 @@
# jimeh's xkeysnail setup
The goal here is to use [xkeysnail][] to make Linux keyboard shortcuts mostly
behave the same as on macOS.
[xkeysnail]: https://github.com/mooz/xkeysnail
## Pre-requisites
To be able to run xkeysnail as a non-root user / with sudo, we need to perform
some initial setup.
1. Create a `uinput` group and add your user to it:
```bash
sudo groupadd -f uinput
sudo gpasswd -a $USER uinput
```
2. Create udev rules for xkeysnail:
```bash
cat <<EOF | sudo tee /etc/udev/rules.d/70-xkeysnail.rules
KERNEL=="uinput", GROUP="uinput", MODE="0660", OPTIONS+="static_node=uinput"
KERNEL=="event[0-9]*", GROUP="uinput", MODE="0660"
EOF
```
3. Reboot.
(Borrowed from [here][1].)
[1]: https://github.com/mooz/xkeysnail/issues/64#issuecomment-600380800
## Setup systemd user service
1. Copy service into place:
```bash
mkdir -p $HOME/.config/systemd/user
cp ./systemd.service $HOME/.config/systemd/user/xkeysnail.service
```
2. Reload user units:
```bash
systemctl --user daemon-reload
```
3. Enable service:
```bash
systemctl --user enable xkeysnail.service
```
4. Start service:
```bash
systemctl --user start xkeysnail.service
```
5. Check status of service:
```bash
systemctl --user status xkeysnail.service
```

132
config/xkeysnail/config.py Normal file
View File

@@ -0,0 +1,132 @@
from xkeysnail.transform import *
from xkeysnail.key import Key
define_modmap({
# Treat CAPSLOCK as CTRL.
Key.CAPSLOCK: Key.LEFT_CTRL,
# Ensure ` and ~ key is next to left shift.
Key.KEY_102ND: Key.GRAVE,
Key.GRAVE: Key.KEY_102ND,
})
define_keymap(lambda wm_class: wm_class in ("Gnome-terminal", "Guake"), {
}, "Terminal keys")
define_keymap(lambda wm_class: wm_class in ("firefox", "Google-chrome"), {
K("Super-Shift-Left_Brace"): K("C-Shift-tab"),
K("Super-Shift-Right_Brace"): K("C-tab"),
K("Super-Left_Brace"): K("M-left"),
K("Super-Right_Brace"): K("M-right"),
K("Super-Shift-t"): K("C-Shift-t"),
}, "Browser keys")
define_keymap(lambda wm_class: wm_class in ("1Password"), {
K("Super-comma"): K("C-comma"),
}, "1Password specific keys")
# Emacs-like keybindings in non-Emacs applications
define_keymap(lambda wm_class: wm_class not in ("Emacs", "URxvt", "Gnome-terminal", "Guake"), {
K("Super-x"): K("C-x"),
K("Super-c"): K("C-c"),
K("Super-v"): K("C-v"),
K("Super-a"): K("C-a"),
K("Super-l"): K("C-l"),
K("Super-t"): K("C-t"),
K("Super-r"): K("C-r"),
K("Super-z"): K("C-z"),
K("Super-Shift-z"): K("C-Shift-z"),
# Cursor
K("C-b"): with_mark(K("left")),
K("C-f"): with_mark(K("right")),
K("C-p"): with_mark(K("up")),
K("C-n"): with_mark(K("down")),
K("C-h"): with_mark(K("backspace")),
K("Super-f"): K("C-f"),
K("Super-n"): K("C-n"),
K("Super-p"): K("C-p"),
K("Super-h"): K("C-h"),
# Forward/Backward word
K("M-b"): with_mark(K("C-left")),
K("M-f"): with_mark(K("C-right")),
# Beginning/End of line
K("C-a"): with_mark(K("home")),
K("C-e"): with_mark(K("end")),
# Page up/down
K("M-v"): with_mark(K("page_up")),
K("C-v"): with_mark(K("page_down")),
# Beginning/End of file
K("M-Shift-comma"): with_mark(K("C-home")),
K("M-Shift-dot"): with_mark(K("C-end")),
# Newline
K("C-m"): K("enter"),
K("C-j"): K("enter"),
K("C-o"): [K("enter"), K("left")],
K("Super-o"): K("C-o"),
# Copy
K("C-w"): [K("C-x"), set_mark(False)],
K("M-w"): [K("C-c"), set_mark(False)],
K("C-y"): [K("C-v"), set_mark(False)],
K("Super-w"): K("C-w"),
# Delete
K("C-d"): [K("delete"), set_mark(False)],
K("M-d"): [K("C-delete"), set_mark(False)],
K("Super-d"): K("C-d"),
# Backspace
K("M-backspace"): [K("C-backspace"), set_mark(False)],
# Kill line
# K("C-k"): [K("Shift-end"), K("C-x"), set_mark(False)],
# Undo
K("M-minus"): [K("C-z"), set_mark(False)],
# K("C-slash"): [K("C-z"), set_mark(False)],
# K("C-Shift-ro"): K("C-z"),
# Mark
K("C-space"): set_mark(True),
K("C-M-space"): with_or_set_mark(K("C-right")),
# Search
# K("C-s"): K("F3"),
# K("C-r"): K("Shift-F3"),
# K("M-Shift-key_5"): K("C-h"),
K("Super-s"): K("C-s"),
# Cancel
K("C-g"): [K("esc"), set_mark(False)],
K("Super-g"): K("C-g"),
K("Super-Shift-g"): K("C-Shift-g"),
# Escape
K("C-q"): escape_next_key,
K("C-left_brace"): K("esc"),
# C-x YYY
K("C-x"): {
# C-x h (select all)
K("h"): [K("C-home"), K("C-a"), set_mark(True)],
# C-x C-f (open)
K("C-f"): K("C-o"),
# C-x C-s (save)
K("C-s"): K("C-s"),
# C-x k (kill tab)
# K("k"): K("C-f4"),
# C-x C-c (exit)
# K("C-c"): K("C-q"),
# cancel
K("C-g"): pass_through_key,
# C-x u (undo)
# K("u"): [K("C-z"), set_mark(False)],
}
}, "Emacs-like keys")

View File

@@ -0,0 +1,13 @@
[Unit]
Description=xkeysnail
[Service]
Type=simple
KillMode=process
ExecStart=%h/.local/share/mise/shims/xkeysnail --watch --quiet %h/.config/xkeysnail/config.py
Restart=on-failure
RestartSec=3
Environment=DISPLAY=:0
[Install]
WantedBy=default.target

1
cursor/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
./cache/

View File

@@ -0,0 +1,75 @@
# cursor Extensions
# Generated on Sat Mar 8 23:55:51 GMT 2025
alefragnani.project-manager@12.8.0
antiantisepticeye.vscode-color-picker@0.0.4
antyos.openscad@1.3.2
arturodent.command-alias@0.6.2
bibhasdn.unique-lines@1.0.0
bodil.file-browser@0.2.11
britesnow.vscode-toggle-quotes@0.3.6
bufbuild.vscode-buf@0.7.0
carlos-algms.make-task-provider@2.5.1
christian-kohler.path-intellisense@2.10.0
connor4312.esbuild-problem-matchers@0.0.3
connorshea.vscode-ruby-test-adapter@0.9.2
ctf0.macros@1.1.1
dbaeumer.vscode-eslint@3.0.10
dnut.rewrap-revived@1.16.3
emeraldwalk.runonsave@0.2.7
exiasr.hadolint@1.1.2
foxundermoon.shell-format@7.2.5
github.remotehub@0.64.0
github.vscode-github-actions@0.27.1
github.vscode-pull-request-github@0.102.0
gofenix.go-lines@0.0.10
golang.go@0.46.1
gruntfuggly.todo-tree@0.0.226
hashicorp.terraform@2.34.3
hbenl.vscode-test-explorer@2.22.1
humao.rest-client@0.25.1
hverlin.mise-vscode@0.47.8
joshbolduc.commitlint@2.6.2
kahole.magit@0.6.66
karunamurti.haml@1.4.1
koichisasada.vscode-rdbg@0.2.2
letrieu.expand-region@0.1.4
m4ns0ur.base64@1.0.0
mattn.lisp@0.1.12
mhutchie.git-graph@1.30.0
ms-azuretools.vscode-docker@1.29.4
ms-python.debugpy@2024.6.0
ms-python.python@2024.12.3
ms-python.vscode-pylance@2024.8.1
ms-vscode-remote.remote-containers@0.394.0
ms-vscode-remote.remote-ssh@0.113.1
ms-vscode-remote.remote-ssh-edit@0.87.0
ms-vscode.extension-test-runner@0.0.12
ms-vscode.hexeditor@1.11.1
ms-vscode.remote-explorer@0.4.3
ms-vscode.remote-repositories@0.42.0
ms-vscode.remote-server@1.5.2
ms-vscode.test-adapter-converter@0.2.1
ms-vscode.vscode-speech@0.12.1
ms-vsliveshare.vsliveshare@1.0.5948
pflannery.vscode-versionlens@1.16.2
pkief.material-icon-theme@5.20.0
redhat.vscode-xml@0.28.0
redhat.vscode-yaml@1.17.0
rrudi.vscode-dired@0.0.9
rust-lang.rust-analyzer@0.3.2330
shopify.ruby-extensions-pack@0.1.13
shopify.ruby-lsp@0.9.7
sidneys1.gitconfig@2.0.1
sorbet.sorbet-vscode-extension@0.3.40
stuart.unique-window-colors@1.0.51
sumneko.lua@3.13.6
swellaby.vscode-rust-test-adapter@0.11.0
tamasfe.even-better-toml@0.21.2
tuttieee.emacs-mcx@0.65.1
tyriar.sort-lines@1.12.0
viktorzetterstrom.non-breaking-space-highlighter@0.0.3
wenhoujx.swiper@2.1.2
zhuangtongfa.material-theme@3.19.0
ziyasal.vscode-open-in-github@1.3.6
zxh404.vscode-proto3@0.5.5

View File

@@ -0,0 +1,77 @@
# vscode-insiders Extensions
# Generated on Sun Mar 9 00:18:12 GMT 2025
alefragnani.project-manager@12.8.0
antiantisepticeye.vscode-color-picker@0.0.4
antyos.openscad@1.3.2
arturodent.command-alias@0.6.2
bibhasdn.unique-lines@1.0.0
bodil.file-browser@0.2.11
britesnow.vscode-toggle-quotes@0.3.6
bufbuild.vscode-buf@0.7.0
carlos-algms.make-task-provider@2.5.1
christian-kohler.path-intellisense@2.10.0
connor4312.esbuild-problem-matchers@0.0.3
connorshea.vscode-ruby-test-adapter@0.9.2
ctf0.macros@1.1.1
dbaeumer.vscode-eslint@3.0.10
dnut.rewrap-revived@1.16.3
emeraldwalk.runonsave@0.2.7
exiasr.hadolint@1.1.2
foxundermoon.shell-format@7.2.5
github.copilot@1.280.1420
github.copilot-chat@0.25.2025022401
github.remotehub@0.64.0
github.vscode-github-actions@0.27.1
github.vscode-pull-request-github@0.106.0
gofenix.go-lines@0.0.10
golang.go@0.46.1
gruntfuggly.todo-tree@0.0.226
hashicorp.terraform@2.34.3
hbenl.vscode-test-explorer@2.22.1
humao.rest-client@0.25.1
hverlin.mise-vscode@0.47.8
joshbolduc.commitlint@2.6.2
kahole.magit@0.6.66
karunamurti.haml@1.4.1
koichisasada.vscode-rdbg@0.2.2
letrieu.expand-region@0.1.4
m4ns0ur.base64@1.0.0
mattn.lisp@0.1.12
mhutchie.git-graph@1.30.0
ms-azuretools.vscode-docker@1.29.4
ms-python.debugpy@2025.4.0
ms-python.python@2025.2.0
ms-python.vscode-pylance@2025.3.1
ms-vscode-remote.remote-containers@0.403.0
ms-vscode-remote.remote-ssh@0.118.0
ms-vscode-remote.remote-ssh-edit@0.87.0
ms-vscode.extension-test-runner@0.0.12
ms-vscode.hexeditor@1.11.1
ms-vscode.remote-explorer@0.4.3
ms-vscode.remote-repositories@0.42.0
ms-vscode.remote-server@1.5.2
ms-vscode.test-adapter-converter@0.2.1
ms-vscode.vscode-speech@0.12.1
ms-vsliveshare.vsliveshare@1.0.5948
pflannery.vscode-versionlens@1.16.2
pkief.material-icon-theme@5.20.0
redhat.vscode-xml@0.28.0
redhat.vscode-yaml@1.17.0
rrudi.vscode-dired@0.0.9
rust-lang.rust-analyzer@0.3.2330
shopify.ruby-extensions-pack@0.1.13
shopify.ruby-lsp@0.9.7
sidneys1.gitconfig@2.0.1
sorbet.sorbet-vscode-extension@0.3.40
stuart.unique-window-colors@1.0.51
sumneko.lua@3.13.6
swellaby.vscode-rust-test-adapter@0.11.0
tamasfe.even-better-toml@0.21.2
tuttieee.emacs-mcx@0.65.1
tyriar.sort-lines@1.12.0
viktorzetterstrom.non-breaking-space-highlighter@0.0.3
wenhoujx.swiper@2.1.2
zhuangtongfa.material-theme@3.19.0
ziyasal.vscode-open-in-github@1.3.6
zxh404.vscode-proto3@0.5.5

View File

@@ -0,0 +1,75 @@
# windsurf Extensions
# Generated on Sat Mar 8 23:40:17 GMT 2025
alefragnani.project-manager@12.8.0
antiantisepticeye.vscode-color-picker@0.0.4
antyos.openscad@1.3.2
arturodent.command-alias@0.6.2
bibhasdn.unique-lines@1.0.0
bodil.file-browser@0.2.11
britesnow.vscode-toggle-quotes@0.3.6
bufbuild.vscode-buf@0.7.0
carlos-algms.make-task-provider@2.4.2
christian-kohler.path-intellisense@2.8.0
codeium.windsurfpyright@1.28.0
connor4312.esbuild-problem-matchers@0.0.3
connorshea.vscode-ruby-test-adapter@0.9.0
ctf0.macros@1.1.1
dbaeumer.vscode-eslint@3.0.10
dnut.rewrap-revived@1.16.3
emeraldwalk.runonsave@0.1.0
exiasr.hadolint@1.1.2
foxundermoon.shell-format@7.0.1
github.remotehub@0.64.0
github.vscode-github-actions@0.27.1
github.vscode-pull-request-github@0.98.0
gofenix.go-lines@0.0.10
golang.go@0.46.1
gruntfuggly.todo-tree@0.0.215
hashicorp.terraform@2.34.3
hbenl.vscode-test-explorer@2.22.1
humao.rest-client@0.26.0
hverlin.mise-vscode@0.47.8
joshbolduc.commitlint@2.6.2
kahole.magit@0.6.66
karunamurti.haml@1.4.1
koichisasada.vscode-rdbg@0.2.1
letrieu.expand-region@0.1.4
m4ns0ur.base64@1.0.0
mattn.lisp@0.1.12
mhutchie.git-graph@1.30.0
ms-azuretools.vscode-docker@1.29.4
ms-python.debugpy@2025.4.0
ms-python.python@2025.2.0
ms-vscode-remote.remote-containers@0.394.0
ms-vscode-remote.remote-ssh@0.113.1
ms-vscode-remote.remote-ssh-edit@0.87.0
ms-vscode.extension-test-runner@0.0.12
ms-vscode.hexeditor@1.11.1
ms-vscode.remote-explorer@0.4.3
ms-vscode.remote-repositories@0.42.0
ms-vscode.remote-server@1.5.2
ms-vscode.test-adapter-converter@0.2.1
ms-vscode.vscode-speech@0.12.1
ms-vsliveshare.vsliveshare@1.0.5948
pflannery.vscode-versionlens@1.16.1
pkief.material-icon-theme@5.20.0
redhat.vscode-xml@0.28.0
redhat.vscode-yaml@1.17.0
rrudi.vscode-dired@0.0.9
rust-lang.rust-analyzer@0.3.2330
shopify.ruby-extensions-pack@0.1.13
shopify.ruby-lsp@0.9.7
sidneys1.gitconfig@2.0.1
sorbet.sorbet-vscode-extension@0.3.40
stuart.unique-window-colors@1.0.51
sumneko.lua@3.13.6
swellaby.vscode-rust-test-adapter@0.11.1
tamasfe.even-better-toml@0.21.2
tuttieee.emacs-mcx@0.65.1
tyriar.sort-lines@1.9.1
viktorzetterstrom.non-breaking-space-highlighter@0.0.3
wenhoujx.swiper@2.1.2
zhuangtongfa.material-theme@3.19.0
ziyasal.vscode-open-in-github@1.4.1
zxh404.vscode-proto3@0.5.5

838
cursor/keybindings.json Normal file
View File

@@ -0,0 +1,838 @@
[
//
// ===========================================================================
// MARK: Command Palette
// ===========================================================================
//
{ // Show commands.
"key": "ctrl+x m",
"command": "workbench.action.showCommands"
},
{ // Show commands.
"key": "ctrl+x ctrl+m",
"command": "workbench.action.showCommands"
},
{ // Show commands.
"key": "ctrl+x enter",
"command": "workbench.action.showCommands"
},
{ // Show commands.
"key": "alt+x",
"command": "workbench.action.showCommands"
},
//
// ===========================================================================
// MARK: Window
// ===========================================================================
//
{ // Reset zoom.
"key": "cmd+0",
"command": "workbench.action.zoomReset"
},
{ // Toggle sidebar visibility.
"key": "ctrl+s",
"command": "workbench.action.toggleSidebarVisibility"
},
//
// ===========================================================================
// MARK: Window Tabs
// ===========================================================================
//
{ // Go to next window tab.
"key": "ctrl+z ctrl+n",
"command": "workbench.action.showNextWindowTab"
},
{ // Go to next window tab.
//
// This is a workaround for ctrl+n not working globally as down arrow in
// all input fields. This relies on Karabiner-Elements to remap ctrl+n to
// down arrow.
"key": "ctrl+z down",
"command": "workbench.action.showNextWindowTab"
},
{ // Go to next window tab.
"key": "shift+cmd+]",
"command": "workbench.action.showNextWindowTab"
},
{ // Go to previous window tab.
"key": "ctrl+z ctrl+p",
"command": "workbench.action.showPreviousWindowTab"
},
{ // Go to previous window tab.
//
// This is a workaround for ctrl+p not working globally as up arrow in
// all input fields. This relies on Karabiner-Elements to remap ctrl+p to
// up arrow.
"key": "ctrl+z up",
"command": "workbench.action.showPreviousWindowTab"
},
{ // Go to previous window tab.
"key": "shift+cmd+[",
"command": "workbench.action.showPreviousWindowTab"
},
{ // Create new window tab.
"key": "ctrl+z ctrl+c",
"command": "workbench.action.newWindowTab"
},
{ // Merge all window tabs.
"key": "ctrl+z ctrl+j",
"command": "workbench.action.mergeAllWindowTabs"
},
{ // Move window tab to new window.
"key": "ctrl+z ctrl+t",
"command": "workbench.action.moveWindowTabToNewWindow"
},
{ // Toggle window tabs bar.
"key": "ctrl+z ctrl+z",
"command": "workbench.action.toggleWindowTabsBar"
},
{
"key": "ctrl+z ctrl+k",
"command": "workbench.action.closeWindow"
},
//
// ===========================================================================
// MARK: Save
// ===========================================================================
//
{ // Save file.
"key": "ctrl+x ctrl+s",
"command": "workbench.action.files.save"
},
//
// ===========================================================================
// MARK: File open
// ===========================================================================
//
{ // Open file via file browser extension.
"key": "ctrl+x ctrl+f",
"command": "file-browser.open",
"when": "editorTextFocus"
},
{ // Open file via file browser extension.
//
// This is a workaround for ctrl+f not working globally as right arrow in
// all input fields. This relies on Karabiner-Elements to remap ctrl+f to
// right arrow.
"key": "ctrl+x right",
"command": "file-browser.open",
"when": "editorTextFocus"
},
{ // Open quick open.
"key": "ctrl+x ctrl+;",
"command": "workbench.action.quickOpen"
},
//
// ===========================================================================
// MARK: File rename
// ===========================================================================
//
{ // Rename file via file browser extension.
"key": "ctrl+c r",
"command": "file-browser.rename"
},
//
// ===========================================================================
// MARK: Switch and close editor
// ===========================================================================
//
{ // Show all editors by most recently used.
"key": "ctrl+x ctrl+b",
"command": "workbench.action.showAllEditorsByMostRecentlyUsed",
"when": "!terminalFocus"
},
{ // Show all editors by most recently used.
//
// This is a workaround for ctrl+b not working globally as left arrow in
// all input fields. This relies on Karabiner-Elements to remap ctrl+b to
// left arrow.
"key": "ctrl+x left",
"command": "workbench.action.showAllEditorsByMostRecentlyUsed",
"when": "!terminalFocus"
},
{ // Close active editor.
"key": "ctrl+x ctrl+k",
"command": "workbench.action.closeActiveEditor",
"when": "!terminalFocus"
},
//
// ===========================================================================
// MARK: Fullscreen
// ===========================================================================
//
{ // Toggle fullscreen.
"key": "ctrl+cmd+enter",
"command": "workbench.action.toggleFullScreen",
"when": "!isIOS"
},
{ // Toggle fullscreen.
"key": "cmd+enter",
"command": "workbench.action.toggleFullScreen",
"when": "!isIOS"
},
{ // Disable default toggle fullscreen keybinding.
"key": "ctrl+cmd+f",
"command": "-workbench.action.toggleFullScreen",
"when": "!isIOS"
},
//
// ===========================================================================
// MARK: Editor Navigation
// ===========================================================================
//
{ // Go to next editor.
"key": "cmd+]",
"command": "workbench.action.nextEditor",
"when": "!(terminalFocus && terminalHasBeenCreated && !terminalEditorFocus || terminalFocus && terminalProcessSupported && !terminalEditorFocus)"
},
{ // Go to previous editor.
"key": "cmd+[",
"command": "workbench.action.previousEditor",
"when": "!(terminalFocus && terminalHasBeenCreated && !terminalEditorFocus || terminalFocus && terminalProcessSupported && !terminalEditorFocus)"
},
{ // Focus next terminal.
"key": "cmd+]",
"command": "workbench.action.terminal.focusNext",
"when": "terminalFocus && terminalHasBeenCreated && !terminalEditorFocus || terminalFocus && terminalProcessSupported && !terminalEditorFocus"
},
{ // Focus previous terminal.
"key": "cmd+[",
"command": "workbench.action.terminal.focusPrevious",
"when": "terminalFocus && terminalHasBeenCreated && !terminalEditorFocus || terminalFocus && terminalProcessSupported && !terminalEditorFocus"
},
//
// ===========================================================================
// MARK: Editor Group Navigation
// ===========================================================================
//
{ // Focus next editor group.
"key": "ctrl+x ctrl+o",
"command": "workbench.action.focusNextGroup",
"when": "!terminalFocus"
},
{ // Focus previous editor group.
"key": "ctrl+x ctrl+i",
"command": "workbench.action.focusPreviousGroup",
"when": "!terminalFocus"
},
{ // Navigate up.
"key": "alt+i",
"command": "workbench.action.navigateUp",
"when": "!terminalFocus"
},
{ // Navigate up.
//
// Workaround for alt+i not working in VSCode. Relies on Karabiner-Elements
// to remap alt+i to cmd+alt+up.
"key": "cmd+alt+up",
"command": "workbench.action.navigateUp",
"when": "!terminalFocus"
},
{ // Navigate right.
"key": "alt+l",
"command": "workbench.action.navigateRight",
},
{ // Navigate left.
"key": "alt+j",
"command": "workbench.action.navigateLeft",
},
{ // Navigate down.
"key": "alt+k",
"command": "workbench.action.navigateDown",
},
{ // Navigate up back to editor from terminal.
"key": "alt+i",
"command": "workbench.action.focusActiveEditorGroup",
"when": "terminalFocus"
},
{ // Navigate up back to editor from terminal.
//
// Workaround for alt+i not working in VSCode. Relies on Karabiner-Elements
// to remap alt+i to cmd+alt+up.
"key": "cmd+alt+up",
"command": "workbench.action.focusActiveEditorGroup",
"when": "terminalFocus"
},
//
// ===========================================================================
// MARK: Editor group layout
// ===========================================================================
//
{ // Even editor widths.
"key": "ctrl+x space",
"command": "workbench.action.evenEditorWidths",
"when": "!terminalFocus"
},
{
"key": "ctrl+z ctrl+enter",
"command": "workbench.action.toggleMaximizeEditorGroup",
"when": "editorPartMaximizedEditorGroup || editorPartMultipleEditorGroups"
},
{
"key": "ctrl+z enter",
"command": "workbench.action.minimizeOtherEditors",
"when": "editorPartMaximizedEditorGroup || editorPartMultipleEditorGroups"
},
//
// ===========================================================================
// MARK: Move active editor group
// ===========================================================================
//
{ // Move active editor group up.
"key": "ctrl+shift+i",
"command": "workbench.action.moveActiveEditorGroupUp"
},
{ // Move active editor group down.
"key": "ctrl+shift+k",
"command": "workbench.action.moveActiveEditorGroupDown"
},
{ // Move active editor group left.
"key": "ctrl+shift+j",
"command": "workbench.action.moveActiveEditorGroupLeft"
},
{ // Move active editor group right.
"key": "ctrl+shift+l",
"command": "workbench.action.moveActiveEditorGroupRight"
},
//
// ===========================================================================
// MARK: Move active editor into group
// ===========================================================================
//
{ // Move active editor into group above.
"key": "alt+shift+i",
"command": "workbench.action.moveEditorToAboveGroup"
},
{ // Move active editor into group above.
//
// Workaround for alt+i not working in VSCode. Relies on Karabiner-Elements
// to remap alt+i to cmd+alt+up.
"key": "cmd+alt+shift+up",
"command": "workbench.action.moveEditorToAboveGroup"
},
{ // Move active editor into group below.
"key": "alt+shift+k",
"command": "workbench.action.moveEditorToBelowGroup"
},
{ // Move active editor into group left.
"key": "alt+shift+j",
"command": "workbench.action.moveEditorToLeftGroup"
},
{ // Move active editor into group right.
"key": "alt+shift+l",
"command": "workbench.action.moveEditorToRightGroup"
},
//
// ===========================================================================
// MARK: Undo/redo
// ===========================================================================
//
{ // Undo.
"key": "alt+-",
"command": "undo"
},
{ // Redo.
"key": "alt+shift+-",
"command": "redo"
},
//
// ===========================================================================
// MARK: Scrolling
// ===========================================================================
//
{ // Scroll half page down.
"key": "ctrl+v",
"command": "editorScroll",
"args": {
"to": "down",
"by": "halfPage",
"revealCursor": true,
"value": 1
},
"when": "editorTextFocus && !suggestWidgetVisible"
},
{ // Scroll half page up.
"key": "alt+v",
"command": "editorScroll",
"args": {
"to": "up",
"by": "halfPage",
"revealCursor": true,
"value": 1
},
"when": "editorTextFocus && !suggestWidgetVisible"
},
{ // Disable awesome-emacs-keymap scroll down keybinding.
"key": "ctrl+v",
"command": "-emacs-mcx.scrollUpCommand",
"when": "editorTextFocus && !suggestWidgetVisible"
},
{ // Disable awesome-emacs-keymap scroll up keybinding.
"key": "alt+v",
"command": "-emacs-mcx.scrollDownCommand",
"when": "editorTextFocus && !config.emacs-mcx.useMetaPrefixMacCmd && !suggestWidgetVisible"
},
//
// ===========================================================================
// MARK: Suggestions
// ===========================================================================
//
{ // Trigger suggestions.
"key": "ctrl+/",
"command": "editor.action.triggerSuggest",
"when": "editorTextFocus"
},
{ // Toggle suggestion details.
"key": "ctrl+/",
"command": "toggleSuggestionDetails",
"when": "editorTextFocus && suggestWidgetVisible"
},
{ // Accept next word.
"key": "shift+alt+f",
"command": "editor.action.inlineSuggest.acceptNextWord"
},
{ // Accept next word.
//
// This is a workaround for alt+f not working globally to move to next word
// in all input fields. This relies on Karabiner-Elements to remap alt+f to
// alt+right arrow.
"key": "shift+alt+right",
"command": "editor.action.inlineSuggest.acceptNextWord"
},
//
// ===========================================================================
// MARK: Text navigation
// ===========================================================================
//
{ // Forward paragraph.
"key": "alt+e",
"command": "emacs-mcx.forwardParagraph",
"when": "editorTextFocus && !suggestWidgetVisible"
},
{ // Forward paragraph.
//
// This is a workaround for alt+e not working in VSCode. Relies on
// Karabiner-Elements to remap alt+e to cmd+alt+right.
"key": "cmd+alt+right",
"command": "emacs-mcx.forwardParagraph",
"when": "editorTextFocus && !suggestWidgetVisible"
},
{ // Backward paragraph.
"key": "alt+a",
"command": "emacs-mcx.backwardParagraph",
"when": "editorTextFocus && !suggestWidgetVisible"
},
{ // Go to symbol definition.
"key": "ctrl+t",
"command": "workbench.action.gotoSymbol",
"when": "editorTextFocus"
},
{ // Jump to definition.
"key": "ctrl+c ctrl+j",
"command": "editor.action.revealDefinition"
},
{ // Navigate back.
"key": "alt+g b",
"command": "workbench.action.navigateBack"
},
{ // Navigate back.
"key": "alt+g alt+b",
"command": "workbench.action.navigateBack"
},
{ // Peek symbol references.
"key": "alt+shift+/",
"command": "editor.action.referenceSearch.trigger",
"when": "editorHasReferenceProvider && editorTextFocus && !inReferenceSearchEditor && !isInEmbeddedEditor"
},
//
// ===========================================================================
// MARK: Text manipulation
// ===========================================================================
//
{ // Set indentation to correct level.
//
// This was borrowed from the awesome-emacs-keymap extension and modifed to
// add the `!cpp.shouldAcceptTab` condition for the sake of Cursor.
//
// Extension: https://marketplace.visualstudio.com/items?itemName=tuttieee.emacs-mcx
"key": "tab",
"command": "emacs-mcx.tabToTabStop",
"when": "editorTextFocus && !editorReadonly && !inlineSuggestionVisible && !editorHoverFocused && !editorTabMovesFocus && !suggestWidgetVisible && !inSnippetMode && !editorTabCompletion && !editorParameterHintsVisible && !cursorAtInlineEdit && !cpp.shouldAcceptTab"
},
{ // Cycle spacing.
//
// This is a basic emulation Emacs' built-in cycle-spacing command. It
// doesn't actually cycle spacing, but it performs the first step which is
// to replace all spacing surrounding the cursor with a single space.
"key": "alt+space",
"command": "macros.cycleSpacing",
"when": "editorTextFocus && !editorReadonly"
},
{ // Toggle quotes.
//
// Extension: https://marketplace.visualstudio.com/items?itemName=BriteSnow.vscode-toggle-quotes
"key": "ctrl+'",
"command": "editor.togglequotes",
"when": "editorTextFocus && !editorReadonly"
},
{ // Comment line.
"key": "ctrl+c /",
"command": "editor.action.commentLine",
"when": "editorTextFocus && !editorReadonly"
},
{ // Comment line.
"key": "ctrl+c ctrl+/",
"command": "editor.action.commentLine",
"when": "editorTextFocus && !editorReadonly"
},
{ // Format document.
"key": "ctrl+c ctrl+f",
"command": "editor.action.formatDocument",
"when": "editorTextFocus && !editorReadonly"
},
{ // Format document.
//
// This is a workaround for ctrl+f not working globally as right arrow in
// all input fields. This relies on Karabiner-Elements to remap ctrl+f to
// right arrow.
"key": "ctrl+c right",
"command": "editor.action.formatDocument",
"when": "editorTextFocus && !editorReadonly"
},
{ // Duplicate selection.
"key": "ctrl+x ctrl+d",
"command": "editor.action.duplicateSelection"
},
{ // Duplicate selection.
//
// This is a workaround for ctrl+d not working globally as forward delete in
// all input fields. This relies on Karabiner-Elements to remap ctrl+d to
// forward delete.
"key": "ctrl+x delete",
"command": "editor.action.duplicateSelection"
},
{ // Rename thing at point.
"key": "ctrl+c ctrl+.",
"command": "editor.action.rename",
"when": "editorHasRenameProvider && editorTextFocus && !editorReadonly"
},
{ // Indent line
"key": "ctrl+c ]",
"command": "editor.action.indentLines",
"when": "editorTextFocus && !editorReadonly"
},
{ // Indent line
"key": "alt+]",
"command": "editor.action.indentLines",
"when": "editorTextFocus && !editorReadonly"
},
{ // Outdent line
"key": "ctrl+c [",
"command": "editor.action.outdentLines",
"when": "editorTextFocus && !editorReadonly"
},
{ // Outdent line
"key": "alt+[",
"command": "editor.action.outdentLines",
"when": "editorTextFocus && !editorReadonly"
},
{ // Move lines up in editor.
"key": "alt+p",
"command": "editor.action.moveLinesUpAction",
"when": "editorTextFocus && !editorReadonly"
},
{ // Move lines down in editor.
"key": "alt+n",
"command": "editor.action.moveLinesDownAction",
"when": "editorTextFocus && !editorReadonly"
},
{ // Move lines down in editor.
//
// This is a workaround for alt+n not working in VSCode. Relies on
// Karabiner-Elements to remap alt+n to cmd+alt+down.
"key": "cmd+alt+down",
"command": "editor.action.moveLinesDownAction",
"when": "editorTextFocus && !editorReadonly"
},
{ // Move cell up in notebook editor.
"key": "alt+p",
"command": "notebook.cell.moveUp",
"when": "notebookEditorFocused && !inputFocus"
},
{ // Move cell down in notebook editor.
"key": "alt+n",
"command": "notebook.cell.moveDown",
"when": "notebookEditorFocused && !inputFocus"
},
{ // Move cell down in notebook editor.
//
// This is a workaround for alt+n not working in VSCode. Relies on
// Karabiner-Elements to remap alt+n to cmd+alt+down.
"key": "cmd+alt+down",
"command": "notebook.cell.moveDown",
"when": "notebookEditorFocused && !inputFocus"
},
//
// ===========================================================================
// MARK: Expand region
// ===========================================================================
// Extension:
// - https://marketplace.visualstudio.com/items?itemName=letrieu.expand-region
//
{ // Expand selection.
"key": "alt+.",
"command": "expand_region",
"when": "editorTextFocus"
},
{ // Shrink/contract selection.
"key": "alt+,",
"command": "undo_expand_region",
"when": "editorHasSelection && editorTextFocus"
},
//
// ===========================================================================
// MARK: Multi-cursor
// ===========================================================================
//
{ // Select all highlights.
"key": "shift+cmd+a",
"command": "editor.action.selectHighlights",
"when": "editorFocus"
},
{ // Disable default insert cursor above keybinding.
"key": "alt+cmd+up",
"command": "-editor.action.insertCursorAbove",
"when": "editorTextFocus"
},
{ // Insert cursor above.
"key": "alt+m",
"command": "editor.action.insertCursorAbove",
"when": "editorTextFocus && !editorHasSelection"
},
{ // Add selection to previous find match when editor has selection.
"key": "alt+m",
"command": "emacs-mcx.addSelectionToPreviousFindMatch",
"when": "editorTextFocus && editorHasSelection"
},
{ // Disable default insert cursor below keybinding.
"key": "alt+cmd+down",
"command": "-editor.action.insertCursorBelow",
"when": "editorTextFocus"
},
{ // Insert cursor below when editor has no selection.
"key": "alt+/",
"command": "editor.action.insertCursorBelow",
"when": "editorTextFocus && !editorHasSelection"
},
{ // Add selection to next find match when editor has selection.
"key": "alt+/",
"command": "emacs-mcx.addSelectionToNextFindMatch",
"when": "editorTextFocus && editorHasSelection"
},
//
// ===========================================================================
// MARK: Completion
// ===========================================================================
//
{
"key": "alt+shift+f",
"command": "editor.action.inlineSuggest.acceptNextWord"
},
//
// ===========================================================================
// MARK: Testing
// ===========================================================================
//
{ // Run current file.
"key": "ctrl+c , v",
"command": "macros.runTestCurrentFile",
"when": "editorTextFocus"
},
{ // Run with coverage for current file.
"key": "ctrl+c , ctrl+v",
"command": "macros.runTestCoverageCurrentFile",
"when": "editorTextFocus"
},
{ // Run at cursor.
"key": "ctrl+c , s",
"command": "macros.runTestAtCursor",
"when": "editorTextFocus"
},
{ // Run with coverage at cursor.
"key": "ctrl+c , ctrl+s",
"command": "macros.runTestCoverageAtCursor",
"when": "editorTextFocus"
},
{ // Run all tests.
"key": "ctrl+c , a",
"command": "macros.runTestAll",
"when": "editorTextFocus"
},
{ // Run with coverage for all tests.
"key": "ctrl+c , ctrl+a",
"command": "macros.runTestCoverageAll",
"when": "editorTextFocus"
},
{ // Re-run last run.
"key": "ctrl+c , l",
"command": "macros.runTestReRunLastRun",
"when": "editorTextFocus"
},
{ // Re-run with coverage for last run.
"key": "ctrl+c , ctrl+l",
"command": "macros.runTestCoverageLastRun",
"when": "editorTextFocus"
},
{ // Re-run failed tests.
"key": "ctrl+c , f",
"command": "macros.runTestReRunFailTests",
"when": "editorTextFocus"
},
{ // Debug failed tests.
"key": "ctrl+c , ctrl+f",
"command": "macros.runTestDebugFailTests",
"when": "editorTextFocus"
},
{ // Debug failed tests.
//
// This is a workaround for ctrl+f not working globally as right arrow in
// all input fields. This relies on Karabiner-Elements to remap ctrl+f to
// right arrow.
"key": "ctrl+c , right",
"command": "macros.runTestDebugFailTests",
"when": "editorTextFocus"
},
{ // Debug last run.
"key": "ctrl+c , d",
"command": "macros.runTestDebugLastRun",
"when": "editorTextFocus"
},
//
// ===========================================================================
// MARK: Documentation
// ===========================================================================
//
{ // Show documentation for symbol at cursor.
"key": "ctrl+c d",
"command": "editor.action.showHover"
},
{ // Show documentation for symbol at cursor.
"key": "ctrl+c ctrl+d",
"command": "editor.action.triggerParameterHints"
},
{ // Show documentation for symbol at cursor.
//
// This is a workaround for ctrl+d not working globally as forward delete in
// all input fields. Hence we rebind ctrl+d to forward delete, meaning any
// keybindings that use ctrl+d need to be updated to use delete aswell.
"key": "ctrl+c delete",
"command": "editor.action.triggerParameterHints"
},
//
// ===========================================================================
// MARK: edamagit
// ===========================================================================
// Extension URL:
// - https://marketplace.visualstudio.com/items?itemName=kahole.magit
//
{ // Open magit status.
"key": "ctrl+x g",
"command": "magit.status"
},
{ // Disable default open magit status keybinding.
"key": "alt+x g",
"command": "-magit.status"
},
//
// ===========================================================================
// MARK: GitHub
// ===========================================================================
// Extensions:
// - https://marketplace.visualstudio.com/items?itemName=GitHub.remotehub
// - https://marketplace.visualstudio.com/items?itemName=github.vscode-github-actions
// - https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github
// - https://marketplace.visualstudio.com/items?itemName=ziyasal.vscode-open-in-github
//
{
"key": "ctrl+c g",
"command": "extension.openInGitHub"
},
//
// ===========================================================================
// MARK: Project Manager
// ===========================================================================
// Extension URL:
// - https://marketplace.visualstudio.com/items?itemName=alefragnani.project-manager
//
{ // List projects.
"key": "ctrl+z ctrl+s",
"command": "projectManager.listProjects"
},
{ // Disable default list projects keybinding.
"key": "alt+cmd+p",
"command": "-projectManager.listProjects"
},
{
"key": "ctrl+z ctrl+;",
"command": "projectManager.listProjectsNewWindow"
},
//
// ===========================================================================
// MARK: dired
// ===========================================================================
// Extension URL:
// - https://marketplace.visualstudio.com/items?itemName=rrudi.vscode-dired
//
{ // Open dired.
"key": "ctrl+x ctrl+j",
"command": "extension.dired.open",
"when": "editorTextFocus && !inDebugRepl"
},
{ // Disable default open dired keybinding.
"key": "ctrl+x f",
"command": "-extension.dired.open",
"when": "editorTextFocus && !inDebugRepl"
},
{ // Create directory.
"key": "c",
"command": "extension.dired.createDir",
"when": "dired.open && !findWidgetVisible && !inQuickOpen"
},
{ // Disable default create directory keybinding.
"key": "shift+=",
"command": "-extension.dired.createDir",
"when": "dired.open && !findWidgetVisible && !inQuickOpen"
},
{ // Go up directory.
"key": "ctrl+l",
"command": "extension.dired.goUpDir",
"when": "dired.open && !findWidgetVisible && !inQuickOpen"
},
{ // Disable default go up directory keybinding.
"key": "shift+b",
"command": "-extension.dired.goUpDir",
"when": "dired.open && !findWidgetVisible && !inQuickOpen"
},
//
// ===========================================================================
// MARK: swiper
// ===========================================================================
// Extension URL:
// - https://marketplace.visualstudio.com/items?itemName=wenhoujx.swiper
//
{ // Swiper word at cursor.
"key": "alt+r",
"command": "swiper.swiper-word-at-cursor"
},
//
// ===========================================================================
// MARK: Makefile task runner
// ===========================================================================
// Extension URL:
// - https://marketplace.visualstudio.com/items?itemName=carlos-algms.make-task-provider
//
{ // Run target.
"key": "ctrl+c enter",
"command": "make-task-provider.runTarget"
}
]

574
cursor/settings.json Normal file
View File

@@ -0,0 +1,574 @@
{
//
// ===========================================================================
// MARK: Appearance
// ===========================================================================
//
// Dark/Light mode detection
"window.autoDetectColorScheme": true,
"window.systemColorTheme": "auto",
//
// Theme
"workbench.colorTheme": "One Dark Pro",
"workbench.iconTheme": "material-icon-theme",
"workbench.preferredDarkColorTheme": "One Dark Pro",
"workbench.preferredLightColorTheme": "Solarized Light",
//
// One Dark Pro theme settings
"oneDarkPro.vivid": true,
//
// ===========================================================================
// MARK: Window
// ===========================================================================
//
"window.commandCenter": true,
"window.customTitleBarVisibility": "auto",
"window.nativeFullScreen": false, // Does not work with nativeTabs enabled.
"window.nativeTabs": true,
"window.title": "${rootName}${separator}${profileName}",
//
// ===========================================================================
// MARK: Cursor (Text Editor)
// ===========================================================================
//
"cursor.aipreview.enabled": true,
"cursor.cpp.enablePartialAccepts": true,
"cursor.diffs.useCharacterLevelDiffs": true,
"cursor.terminal.usePreviewBox": true,
"cursor.general.enableShadowWorkspace": true,
//
// ===========================================================================
// MARK: Windsurf (Text Editor)
// ===========================================================================
//
"windsurf.autocompleteSpeed": "fast",
//
// ===========================================================================
// MARK: Editor
// ===========================================================================
//
// Font
"editor.fontFamily": "'Menlo Nerd Font Mono', 'Menlo Nerd Font', Menlo, Monaco, 'Courier New', monospace",
"editor.fontLigatures": false,
"editor.fontWeight": "normal",
"editor.fontSize": 12,
//
// Cursor
"editor.cursorSurroundingLines": 5,
//
// Whitespace
"editor.renderWhitespace": "boundary",
//
// Formatting
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.formatOnSaveMode": "file",
//
// Rulers
"editor.rulers": [
80,
100,
120
],
//
// Semantic Highlighting
"editor.semanticHighlighting.enabled": true,
//
// Minimap
"editor.minimap.enabled": true,
"editor.minimap.showSlider": "always",
//
// Modified Tabs
"workbench.editor.highlightModifiedTabs": true,
//
// Accessibility
"editor.accessibilitySupport": "off",
//
// ===========================================================================
// MARK: Workbench
// ===========================================================================
//
// Activity Bar
"workbench.activityBar.location": "top",
"workbench.activityBar.orientation": "horizontal",
//
// Command Palette
"workbench.commandPalette.history": 250,
"workbench.commandPalette.preserveInput": true,
//
// Hover
"workbench.hover.delay": 200,
//
// Local History
"workbench.localHistory.maxFileEntries": 250,
"workbench.localHistory.maxFileSize": 512,
//
// Tree
"workbench.tree.indent": 18,
"workbench.tree.renderIndentGuides": "always",
//
// ===========================================================================
// MARK: Explorer
// ===========================================================================
//
"explorer.sortOrder": "mixed",
"explorer.sortOrderLexicographicOptions": "unicode",
//
// ===========================================================================
// MARK: Files
// ===========================================================================
//
// Associations
"files.associations": {
"gitconfig": "gitconfig",
"gitignore": "gitignore"
},
//
// Encoding
"files.autoGuessEncoding": true,
//
// Formatting
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
//
// Read-only
"files.readonlyFromPermissions": true,
//
// ===========================================================================
// MARK: Git
// ===========================================================================
//
"git.blame.editorDecoration.enabled": false,
"scm.defaultViewMode": "tree",
//
// ===========================================================================
// MARK: Diffs
// ===========================================================================
//
"diffEditor.ignoreTrimWhitespace": false,
//
// ===========================================================================
// MARK: Terminal
// ===========================================================================
//
"terminal.integrated.allowChords": false,
"terminal.integrated.macOptionIsMeta": true,
"terminal.integrated.persistentSessionScrollback": 1000,
"terminal.integrated.scrollback": 50000,
"terminal.integrated.sendKeybindingsToShell": true,
//
// ===========================================================================
// MARK: Awesome Emacs Keybindings
// ===========================================================================
// Extension:
// - https://marketplace.visualstudio.com/items?itemName=tuttieee.emacs-mcx
//
"emacs-mcx.emacsLikeTab": false, // Done via custom keybinding instead.
"emacs-mcx.killRingMax": 120,
"emacs-mcx.markRingMax": 32,
//
// ===========================================================================
// MARK: File Browser
// ===========================================================================
// Extension:
// - https://marketplace.visualstudio.com/items?itemName=bodil.file-browser
//
"file-browser.hideDotfiles": false,
"file-browser.labelIgnoredFiles": true,
//
// ===========================================================================
// MARK: Project Manager
// ===========================================================================
// Extension:
// - https://marketplace.visualstudio.com/items?itemName=alefragnani.project-manager
//
"projectManager.git.baseFolders": [
"~/Projects",
"~/.dotfiles",
"~/.config"
],
"projectManager.git.ignoredFolders": [
"node_modules",
"out",
"typings",
"test",
".haxelib",
"tmp",
"vendor",
"straight",
"elpaca"
],
"projectManager.any.ignoredFolders": [
"node_modules",
"out",
"typings",
"test",
"tmp",
"vendor",
"straight",
"elpaca"
],
"projectManager.hg.ignoredFolders": [
"node_modules",
"out",
"typings",
"test",
".haxelib",
"straight",
"elpaca"
],
"projectManager.svn.ignoredFolders": [
"node_modules",
"out",
"typings",
"test",
"straight",
"elpaca"
],
"projectManager.vscode.ignoredFolders": [
"node_modules",
"out",
"typings",
"test",
"straight",
"elpaca"
],
//
// ===========================================================================
// MARK: GitHub
// ===========================================================================
// Extensions:
// - https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github
// - https://marketplace.visualstudio.com/items?itemName=GitHub.remotehub
//
"githubRepositories.autoFetch.enabled": false,
//
// ===========================================================================
// MARK: Mise (https://mise.jdx.dev/)
// ===========================================================================
// Extension:
// - https://marketplace.visualstudio.com/items?itemName=hverlin.mise-vscode
//
"mise.updateOpenTerminalsEnvAutomatically": true,
//
// ===========================================================================
// MARK: Hex editor
// ===========================================================================
// Extension:
// - https://marketplace.visualstudio.com/items?itemName=ms-vscode.hexeditor
//
"workbench.editor.defaultBinaryEditor": "hexEditor.hexedit",
//
// ===========================================================================
// MARK: Go
// ===========================================================================
// Extensions:
// - https://marketplace.visualstudio.com/items?itemName=golang.go
// - https://marketplace.visualstudio.com/items?itemName=gofenix.go-lines
//
"go-lines.lineLength": 120,
"go.coverOnSingleTest": true,
"go.coverShowCounts": true,
"go.formatTool": "gofumpt",
"go.inlayHints.constantValues": true,
"go.lintOnSave": "workspace",
"go.lintTool": "golangci-lint",
"go.testExplorer.packageDisplayMode": "nested",
"go.testExplorer.showDynamicSubtestsInEditor": true,
"go.testFlags": [
"-count=1"
],
"go.useLanguageServer": true,
"gopls": {
"ui.diagnostic.analyses": {
"shadow": true
},
"ui.diagnostic.staticcheck": true
},
//
// ===========================================================================
// MARK: JSON
// ===========================================================================
//
"[json]": {
"editor.insertSpaces": true,
"editor.tabSize": 2
},
//
// ===========================================================================
// MARK: Lua
// ===========================================================================
// Extensions:
// - https://marketplace.visualstudio.com/items?itemName=sumneko.lua
//
"Lua.codeLens.enable": true,
"Lua.diagnostics.globals": [
// Hammerspoon
"hs",
"spoon"
],
"Lua.diagnostics.workspaceEvent": "OnChange",
"Lua.hint.enable": true,
//
// ===========================================================================
// MARK: Ruby
// ===========================================================================
// Extensions:
// - https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-extensions-pack
// - https://marketplace.visualstudio.com/items?itemName=connorshea.vscode-ruby-test-adapter
//
"[ruby]": {
"editor.defaultFormatter": "Shopify.ruby-lsp",
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.insertSpaces": true,
"editor.tabSize": 2
},
// Ruby Test Explorer
"rubyTestExplorer.logpanel": false,
// Use mise to execute ruby test explorer bundler commands to ensure correct
// version of Ruby is used.
"rubyTestExplorer.minitestCommand": "mise x -- bundle exec rake",
"rubyTestExplorer.rspecCommand": "mise x -- bundle exec rspec",
//
// ===========================================================================
// MARK: Shellscript
// ===========================================================================
// Extensions:
// - https://marketplace.visualstudio.com/items?itemName=foxundermoon.shell-format
//
"[shellscript]": {
"editor.tabSize": 2,
"editor.insertSpaces": true
},
"[sh]": {
"editor.tabSize": 2,
"editor.insertSpaces": true
},
"[zsh]": {
"editor.tabSize": 2,
"editor.insertSpaces": true
},
"shellformat.useEditorConfig": true,
"shellformat.flag": "-i 2 -ci -sr",
//
// ===========================================================================
// MARK: YAML
// ===========================================================================
// Extensions:
// - https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml
// - https://marketplace.visualstudio.com/items?itemName=arahata.linter-actionlint
//
"redhat.telemetry.enabled": false,
//
// ===========================================================================
// MARK: Makefile
// ===========================================================================
// Extensions:
// - https://marketplace.visualstudio.com/items?itemName=carlos-algms.make-task-provider
//
"makefile.configureOnOpen": false,
"make-task-provider.telemetry": false,
//
// ===========================================================================
// MARK: Sort Lines
// ===========================================================================
// Extensions:
// - https://marketplace.visualstudio.com/items?itemName=Tyriar.sort-lines
//
"sortLines.filterBlankLines": true,
"sortLines.ignoreUnselectedLastLine": true,
//
// ===========================================================================
// MARK: vscode-color-picker
// ===========================================================================
// Extension:
// - https://marketplace.visualstudio.com/items?itemName=AntiAntiSepticeye.vscode-color-picker
//
"vscode-color-picker.languages": [
"css",
"go",
"javascript",
"less",
"postcss",
"python",
"ruby",
"sass",
"scss",
"sss",
"stylus",
"svg",
"typescript",
"xml"
],
//
// ===========================================================================
// MARK: Run on Save callbacks
// ===========================================================================
// Extension:
// - https://marketplace.visualstudio.com/items?itemName=emeraldwalk.RunOnSave
//
"emeraldwalk.runonsave": {
"commands": [
{ // Automatically chmod +x script files (first two bytes are `#!`).
"match": ".*",
"cmd": "if [ ! -x \"${file}\" ] && head -n 1 \"${file}\" | grep -q '^#!'; then echo \"Marking ${relativeFile} as executable...\" && chmod +x \"${file}\"; fi"
}
]
},
//
// ===========================================================================
// MARK: Command aliases
// ===========================================================================
//
"command aliases": {
// Sort Lines.
// Extension: https://marketplace.visualstudio.com/items?itemName=Tyriar.sort-lines
"sortLines.sortLines": "sl"
},
//
// ===========================================================================
// MARK: Macros
// ===========================================================================
// Extension:
// - https://marketplace.visualstudio.com/items?itemName=ctf0.macros
//
"macros.list": {
//
// cycleSpacing is a basic emulation Emacs' built-in cycle-spacing command.
// It doesn't actually cycle spacing, but it performs the first step which
// is to replace all spacing surrounding the cursor with a single space.
//
"cycleSpacing": [
"emacs-mcx.deleteHorizontalSpace",
{
"command": "type",
"args": {
"text": " "
}
}
],
//
// Run test macros which re-focus back into the editor after being
// triggered. The test commands changes focus to the Test Results panel, and
// no settings I found avoids this. Hence these macros trigger relevant test
// running commands and then switches focus back to the editor.
//
// The 1 ms delay between the running tests and switch focus back allows for
// the Test Results panel to appear and steal focus before we try and focus
// back into the editor. Without the delay the focus commands runs before
// Test Results appear and steal focus.
//
"runTestCurrentFile": [
"testing.runCurrentFile",
{
"command": "$delay",
"args": {
"delay": 1
}
},
"workbench.action.focusActiveEditorGroup"
],
"runTestCoverageCurrentFile": [
"testing.coverageCurrentFile",
{
"command": "$delay",
"args": {
"delay": 1
}
},
"workbench.action.focusActiveEditorGroup"
],
"runTestAtCursor": [
"testing.runAtCursor",
{
"command": "$delay",
"args": {
"delay": 1
}
},
"workbench.action.focusActiveEditorGroup"
],
"runTestCoverageAtCursor": [
"testing.coverageAtCursor",
{
"command": "$delay",
"args": {
"delay": 1
}
},
"workbench.action.focusActiveEditorGroup"
],
"runTestAll": [
"testing.runAll",
{
"command": "$delay",
"args": {
"delay": 1
}
},
"workbench.action.focusActiveEditorGroup"
],
"runTestCoverageAll": [
"testing.coverageAll",
{
"command": "$delay",
"args": {
"delay": 1
}
},
"workbench.action.focusActiveEditorGroup"
],
"runTestReRunLastRun": [
"testing.reRunLastRun",
{
"command": "$delay",
"args": {
"delay": 1
}
},
"workbench.action.focusActiveEditorGroup"
],
"runTestCoverageLastRun": [
"testing.coverageLastRun",
{
"command": "$delay",
"args": {
"delay": 1
}
},
"workbench.action.focusActiveEditorGroup"
],
"runTestReRunFailTests": [
"testing.reRunFailTests",
{
"command": "$delay",
"args": {
"delay": 1
}
},
"workbench.action.focusActiveEditorGroup"
],
"runTestDebugFailTests": [
"testing.debugFailTests",
{
"command": "$delay",
"args": {
"delay": 1
}
},
"workbench.action.focusActiveEditorGroup"
],
"runTestDebugLastRun": [
"testing.debugLastRun",
{
"command": "$delay",
"args": {
"delay": 1
}
},
"workbench.action.focusActiveEditorGroup"
]
}
}

506
cursor/setup.sh Executable file
View File

@@ -0,0 +1,506 @@
#! /usr/bin/env bash
# ==============================================================================
# Settings
# ==============================================================================
# Default editor to configure (cursor, vscode, or vscode-insiders)
SETUP_EDITOR="cursor"
# List of config files to symlink from current directory.
CONFIG_SOURCES=(
"settings.json"
"keybindings.json"
"snippets"
)
# Detect current script directory.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Get extensions lockfile path for current editor
get_extensions_lock() {
echo "${SCRIPT_DIR}/extensions.${SETUP_EDITOR}.lock"
}
# ==============================================================================
# Help
# ==============================================================================
show_help() {
cat << EOF
Usage: $(basename "$0") EDITOR COMMAND [OPTIONS]
Editors:
cursor, c Cursor editor
vscode, code, vsc, v Visual Studio Code
vscode-insiders, vsci, i Visual Studio Code Insiders
windsurf, surf, w Windsurf editor
Commands:
config, conf Create symlinks for editor config files
dump-extensions, dump Export installed editor extensions to extensions.txt
extensions, ext Install editor extensions from extensions.txt
Options:
--latest When used with extensions command, installs the
latest version of each extension instead of the
exact version from the lock file
Description:
This script manages editor configuration files and extensions.
It can create symlinks for settings, keybindings, and snippets,
as well as dump extension lock files and install extensions from them.
EOF
}
# ==============================================================================
# Functions
# ==============================================================================
# Determine editor config directory
editor_config_dir() {
case "$(uname -s)" in
"Darwin")
case "${SETUP_EDITOR}" in
"cursor")
echo "${HOME}/Library/Application Support/Cursor/User"
;;
"vscode")
echo "${HOME}/Library/Application Support/Code/User"
;;
"vscode-insiders")
echo "${HOME}/Library/Application Support/Code - Insiders/User"
;;
"windsurf")
echo "${HOME}/Library/Application Support/Windsurf/User"
;;
*)
echo "Error: Invalid editor '${SETUP_EDITOR}' for macOS"
exit 1
;;
esac
;;
"Linux")
case "${SETUP_EDITOR}" in
"cursor")
echo "${HOME}/.config/Cursor/User"
;;
"vscode")
echo "${HOME}/.config/Code/User"
;;
"vscode-insiders")
echo "${HOME}/.config/Code - Insiders/User"
;;
"windsurf")
echo "${HOME}/.config/Windsurf/User"
;;
*)
echo "Error: Invalid editor '${SETUP_EDITOR}' for Linux"
exit 1
;;
esac
;;
*)
echo "Error: Unsupported operating system"
exit 1
;;
esac
}
# Backup and symlink
backup_and_link() {
local source="$1"
local target="$2"
local real_target
local real_source
# Check if target already exists
if [[ -e "${target}" ]]; then
# If it's a symlink, check if it points to the same location
if [[ -L "${target}" ]]; then
real_target="$(readlink -f "${target}")"
real_source="$(readlink -f "${source}")"
if [[ "${real_target}" == "${real_source}" ]]; then
echo "Skipping ${target} - already linked to ${source}"
return
fi
fi
echo "Backing up existing ${target} to ${target}.bak"
mv "${target}" "${target}.bak"
fi
# Create symlink
echo "Creating symlink for ${source} to ${target}"
ln -s "${source}" "${target}"
}
# Create symlinks
do_symlink() {
# Create editor config directory if it doesn't exist
local config_dir
config_dir="$(editor_config_dir)"
mkdir -p "${config_dir}"
for path in "${CONFIG_SOURCES[@]}"; do
backup_and_link "${SCRIPT_DIR}/${path}" "${config_dir}/${path}"
done
echo "Symlink setup complete!"
}
# Find the editor CLI command
find_editor_cmd() {
local editor_cmd=""
local possible_commands=()
case "${SETUP_EDITOR}" in
"cursor")
# Set possible Cursor CLI command locations
possible_commands=(
"cursor"
"/Applications/Cursor.app/Contents/Resources/app/bin/cursor"
"${HOME}/Applications/Cursor.app/Contents/Resources/app/bin/cursor"
)
;;
"vscode")
# Set possible VSCode CLI command locations
possible_commands=(
"code"
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"
"${HOME}/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"
)
;;
"vscode-insiders")
# Set possible VSCode Insiders CLI command locations
possible_commands=(
"code-insiders"
"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"
"${HOME}/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"
)
;;
"windsurf")
# Set possible Windsurf CLI command locations
possible_commands=(
"windsurf"
"/Applications/Windsurf.app/Contents/Resources/app/bin/windsurf"
"${HOME}/Applications/Windsurf.app/Contents/Resources/app/bin/windsurf"
)
;;
*)
echo "Error: Invalid editor '${SETUP_EDITOR}'"
exit 1
;;
esac
# Check for the command in all possible locations
for cmd in "${possible_commands[@]}"; do
echo "Checking ${cmd}" >&2
if command -v "${cmd}" > /dev/null 2>&1; then
editor_cmd="${cmd}"
break
fi
done
if [[ -z "${editor_cmd}" ]]; then
echo "Error: ${SETUP_EDITOR} command not found" >&2
exit 1
fi
echo "${editor_cmd}"
}
# Dump installed extensions to extensions.lock
do_dump_extensions() {
local editor_cmd
editor_cmd="$(find_editor_cmd)"
local current_date
current_date="$(date)"
local extensions_lock
extensions_lock="$(get_extensions_lock)"
{
echo "# ${SETUP_EDITOR} Extensions"
echo "# Generated on ${current_date}"
echo
"${editor_cmd}" --list-extensions --show-versions
} > "${extensions_lock}"
echo "Extensions list dumped to ${extensions_lock}"
}
# Global variable to cache installed extensions
_INSTALLED_EXTENSIONS=""
# Check if extension is already installed, ignoring version
is_extension_installed() {
local editor_cmd="$1"
local extension="$2"
# Build cache if not already built
if [[ -z "${_INSTALLED_EXTENSIONS}" ]]; then
_INSTALLED_EXTENSIONS="$("${editor_cmd}" --list-extensions --show-versions)"
fi
# Check if extension exists in cached list
echo "${_INSTALLED_EXTENSIONS}" | grep -q "^${extension}@"
}
# Install an extension directly using the marketplace
install_extension_direct() {
local editor_cmd="$1"
local extension="$2"
local version="$3"
local use_latest="$4"
local result=0
if [[ "${use_latest}" == "true" ]]; then
echo "Installing ${extension} (latest version)"
if ! "${editor_cmd}" --install-extension "${extension}"; then
echo "Warning: Direct install failed for ${extension}"
result=1
fi
else
echo "Installing ${extension}@${version}"
if ! "${editor_cmd}" --install-extension "${extension}@${version}"; then
echo "Warning: Direct install failed for ${extension}@${version}"
result=1
fi
fi
return ${result}
}
# Install an extension via downloading .vsix file
install_extension_via_vsix() {
local editor_cmd="$1"
local extension="$2"
local version="$3"
local use_latest="$4"
local extensions_cache_dir="$5"
local result=0
local publisher_id="${extension%%.*}"
local extension_id="${extension#*.}"
local vsix_path=""
local vsix_url=""
local install_version=""
if [[ "${use_latest}" == "true" ]]; then
# In latest mode, we need to first query the marketplace to get the latest version
echo "Finding latest version for ${extension}..."
# Query the VS Marketplace API to get the extension metadata
local metadata_url="https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery"
local temp_metadata="${extensions_cache_dir}/metadata-${extension}.json"
# Create extensions directory if it doesn't exist
mkdir -p "${extensions_cache_dir}"
# Create a JSON request payload to query for the extension details
local request_data='{
"filters": [
{
"criteria": [
{ "filterType": 7, "value": "'${extension}'" }
]
}
],
"flags": 2
}'
# Query the marketplace for extension metadata
if ! curl --silent --compressed -X POST -H "Content-Type: application/json" -H "Accept: application/json; api-version=7.2-preview.1" -d "${request_data}" "${metadata_url}" > "${temp_metadata}"; then
echo "Warning: Failed to query metadata for ${extension}"
rm -f "${temp_metadata}"
return 1
fi
# Extract the latest version from the response
if command -v jq > /dev/null 2>&1; then
# If jq is available, use it to parse JSON
install_version=$(jq -r '.results[0].extensions[0].versions[0].version' "${temp_metadata}" 2> /dev/null)
else
# Fallback to grep/sed for basic extraction if jq is not available
install_version=$(grep -o '"version":"[^"]*"' "${temp_metadata}" | head -1 | sed 's/"version":"//;s/"//' 2> /dev/null)
fi
# Clean up metadata file
rm -f "${temp_metadata}"
# If we couldn't extract a version, use original version as fallback
if [[ -z "${install_version}" || "${install_version}" == "null" ]]; then
echo "Warning: Could not determine latest version, falling back to lock file version"
install_version="${version}"
else
echo "Latest version of ${extension} is ${install_version}"
fi
# Set up the download path and URL for the specific version we found
vsix_path="${extensions_cache_dir}/${extension}@${install_version}.vsix"
vsix_url="https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher_id}/vsextensions/${extension_id}/${install_version}/vspackage"
else
# In strict mode, use the exact version from the lock file
echo "Installing ${extension}@${version} via .vsix"
vsix_path="${extensions_cache_dir}/${extension}@${version}.vsix"
vsix_url="https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher_id}/vsextensions/${extension_id}/${version}/vspackage"
install_version="${version}"
fi
# Create extensions directory if it doesn't exist
mkdir -p "${extensions_cache_dir}"
# Download the .vsix file
echo "Downloading ${extension}@${install_version}.vsix..."
echo " - URL: ${vsix_url}"
if ! curl --compressed -L -o "${vsix_path}" "${vsix_url}"; then
echo "Warning: Failed to download ${extension}@${install_version}.vsix"
rm -f "${vsix_path}" # Clean up potential partial downloads
return 1
fi
# Install the extension from .vsix file
echo "Installing extension from ${vsix_path}"
if ! "${editor_cmd}" --install-extension "${vsix_path}"; then
echo "Warning: Failed to install ${extension}@${install_version} from .vsix"
result=1
fi
# Clean up the .vsix file after installation attempt
rm -f "${vsix_path}"
return ${result}
}
# Install extensions from extensions.lock
do_install_extensions() {
local editor_cmd
editor_cmd="$(find_editor_cmd)"
local extensions_cache_dir="${SCRIPT_DIR}/cache/extensions"
local extensions_lock
extensions_lock="$(get_extensions_lock)"
local use_latest="${1:-false}"
if [[ ! -f "${extensions_lock}" ]]; then
echo "Error: ${extensions_lock} not found"
exit 1
fi
# Process each extension
while IFS= read -r line; do
if [[ -n "${line}" && ! "${line}" =~ ^[[:space:]]*# ]]; then
extension="${line%@*}"
version="${line#*@}"
# Check if already installed with correct version
if is_extension_installed "${editor_cmd}" "${extension}"; then
echo "Extension ${extension} is already installed, skipping"
continue
fi
# For Cursor we need to download and install from .vsix file, as
# installation via ID fails with a signature verification error.
if [[ "${SETUP_EDITOR}" == "cursor" ]]; then
install_extension_via_vsix "${editor_cmd}" "${extension}" "${version}" "${use_latest}" "${extensions_cache_dir}"
continue
fi
if ! install_extension_direct "${editor_cmd}" "${extension}" "${version}" "${use_latest}"; then
echo "Direct installation failed, trying .vsix download method..."
install_extension_via_vsix "${editor_cmd}" "${extension}" "${version}" "${use_latest}" "${extensions_cache_dir}"
fi
fi
done < "${extensions_lock}"
# Clean up extensions directory if empty
rmdir "${extensions_cache_dir}" 2> /dev/null || true
echo "Extensions installation complete!"
}
# ==============================================================================
# Main
# ==============================================================================
main() {
if [[ $# -lt 1 ]]; then
echo "Error: No editor specified"
show_help
exit 1
fi
if [[ $# -lt 2 ]]; then
echo "Error: No command specified"
show_help
exit 1
fi
# Set editor from first argument
editor="$(echo "${1}" | tr '[:upper:]' '[:lower:]')"
case "${editor}" in
"vscode" | "code" | "vsc" | "v")
SETUP_EDITOR="vscode"
;;
"vscode-insiders" | "code-insiders" | "insiders" | "vsci" | "i")
SETUP_EDITOR="vscode-insiders"
;;
"cursor" | "c")
SETUP_EDITOR="cursor"
;;
"windsurf" | "wind" | "surf" | "w")
SETUP_EDITOR="windsurf"
;;
*)
echo "Error: Unsupported editor '${editor}'"
echo "Supported editors: cursor, vscode (vsc), vscode-insiders (vsci), windsurf (wind)"
exit 1
;;
esac
# Get command from second argument
local command="${2}"
shift 2
# Default values for options
local use_latest="false"
# Parse additional options
while [[ $# -gt 0 ]]; do
case "$1" in
--latest)
use_latest="true"
shift
;;
*)
echo "Error: Unknown option '$1'"
show_help
exit 1
;;
esac
done
# Handle commands
case "${command}" in
"config" | "conf")
do_symlink
;;
"dump-extensions" | "dump")
do_dump_extensions
;;
"extensions" | "ext")
do_install_extensions "${use_latest}"
;;
"")
echo "Error: No command provided"
show_help
exit 1
;;
*)
echo "Error: Unknown command '${command}'"
show_help
exit 1
;;
esac
}
# Run main function.
main "$@"

78
cursor/snippets/go.json Normal file
View File

@@ -0,0 +1,78 @@
{
"println": {
"prefix": "pd",
"body": [
"fmt.Println($0)",
],
"description": "fmt.Println(...)"
},
"debug print": {
"prefix": "ppd",
"body": [
"fmt.Printf(\"$1: %#v\\n\", $1)$0",
],
"description": "fmt.Printf(\"...: %+v\\n\", ...)"
},
"printf": {
"prefix": "pf",
"body": [
"fmt.Printf(${1:format}, ${2:a ...any})$0",
],
"description": "fmt.Printf(..., ...)"
},
"sprintf": {
"prefix": "spf",
"body": [
"fmt.Sprintf(${1:format}, ${2:a ...any})$0",
],
"description": "fmt.Sprintf(..., ...)"
},
"test func": {
"prefix": "tf",
"body": [
"func Test${1:Name}(t *testing.T) {",
"\ttests := []struct {",
"\t\tname string",
"\t\t$0",
"\t}{",
"\t\t{",
"\t\t\tname: \"\",",
"\t\t},",
"\t}",
"\tfor _, tt := range tests {",
"\t\tt.Run(tt.name, func(t *testing.T) {",
"\t\t\t",
"\t\t})",
"\t}",
"}"
],
"description": "func Test...(t *testing.T) { ... }"
},
"test table": {
"prefix": "tt",
"body": [
"tests := []struct {",
"\tname string",
"\t$0",
"}{",
"\t{",
"\t\tname: \"\",",
"\t},",
"}"
],
"description": "tests := []struct { ... }"
},
"benchmark func": {
"prefix": "bf",
"body": [
"func Benchmark${1:Name}(b *testing.B) {",
"\t$0",
"",
"\tfor n := 0; n < b.N; n++ {",
"\t\t",
"\t}",
"}"
],
"description": "func Benchmark...(b *testing.B) { ... }"
}
}

36
cursor/snippets/ruby.json Normal file
View File

@@ -0,0 +1,36 @@
{
// Place your snippets for ruby here. Each snippet is defined under a snippet name and has a prefix, body and
// description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
// same ids are connected.
// Example:
// "Print to console": {
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"print": {
"prefix": "pd",
"body": [
"puts \"\\n>>>>>> ${1:name}: ${2:#{$1${3:.inspect}\\}}\\n\"$0"
],
"description": "puts \">>>>> ...: #{....inspect}\""
},
"pretty print": {
"prefix": "ppd",
"body": [
"puts \"\\n>>>>>> ${1:name}:\"; pp $1$0"
],
"description": "puts \">>>>> ... \"; pp ..."
},
"byebug": {
"prefix": "bug",
"body": [
"require 'byebug'; byebug$0"
],
"description": "require 'byebug'; byebug"
}
}

4
default.nix Normal file
View File

@@ -0,0 +1,4 @@
let
flake = builtins.getFlake "git+file://${builtins.toString ./.}";
in
flake.packages.${builtins.currentSystem}.default

27
flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1768302833,
"narHash": "sha256-h5bRFy9bco+8QcK7rGoOiqMxMbmn21moTACofNLRMP4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "61db79b0c6b838d9894923920b612048e1201926",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

36
flake.nix Normal file
View File

@@ -0,0 +1,36 @@
{
description = "Global packages for dotfiles";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs =
{ self, nixpkgs }:
let
systems = [
"x86_64-darwin"
"aarch64-darwin"
"x86_64-linux"
"aarch64-linux"
];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
in
{
packages = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
default = pkgs.buildEnv {
name = "dotfiles-packages";
paths = [
pkgs.nil
pkgs.nixfmt
];
};
}
);
};
}

View File

@@ -2,25 +2,25 @@
font-family: "Menlo Nerd Font Mono";
font-style: normal;
font-weight: 400;
src: url("Menlo Regular Nerd Font Complete Mono.ttf") format("truetype");
src: url("MenloNerdFontMono-Regular.ttf") format("truetype");
}
@font-face {
font-family: "Menlo Nerd Font Mono";
font-style: italic;
font-weight: 400;
src: url("Menlo Italic Nerd Font Complete Mono.ttf") format("truetype");
src: url("MenloNerdFontMono-Italic.ttf") format("truetype");
}
@font-face {
font-family: "Menlo Nerd Font Mono";
font-weight: 700;
src: url("Menlo Bold Nerd Font Complete Mono.ttf") format("truetype");
src: url("MenloNerdFontMono-Bold.ttf") format("truetype");
}
@font-face {
font-family: "Menlo Nerd Font Mono";
font-style: italic;
font-weight: 700;
src: url("Menlo Bold Italic Nerd Font Complete Mono.ttf") format("truetype");
src: url("MenloNerdFontMono-BoldItalic.ttf") format("truetype");
}

Binary file not shown.

View File

@@ -0,0 +1,26 @@
@font-face {
font-family: "Menlo Nerd Font Propo";
font-style: normal;
font-weight: 400;
src: url("MenloNerdFontPropo-Regular.ttf") format("truetype");
}
@font-face {
font-family: "Menlo Nerd Font Propo";
font-style: italic;
font-weight: 400;
src: url("MenloNerdFontPropo-Italic.ttf") format("truetype");
}
@font-face {
font-family: "Menlo Nerd Font Propo";
font-weight: 700;
src: url("MenloNerdFontPropo-Bold.ttf") format("truetype");
}
@font-face {
font-family: "Menlo Nerd Font Propo";
font-style: italic;
font-weight: 700;
src: url("MenloNerdFontPropo-BoldItalic.ttf") format("truetype");
}

Binary file not shown.

26
fonts/Menlo Nerd Font.css Normal file
View File

@@ -0,0 +1,26 @@
@font-face {
font-family: "Menlo Nerd Font";
font-style: normal;
font-weight: 400;
src: url("MenloNerdFont-Regular.ttf") format("truetype");
}
@font-face {
font-family: "Menlo Nerd Font";
font-style: italic;
font-weight: 400;
src: url("MenloNerdFont-Italic.ttf") format("truetype");
}
@font-face {
font-family: "Menlo Nerd Font";
font-weight: 700;
src: url("MenloNerdFont-Bold.ttf") format("truetype");
}
@font-face {
font-family: "Menlo Nerd Font";
font-style: italic;
font-weight: 700;
src: url("MenloNerdFont-BoldItalic.ttf") format("truetype");
}

BIN
fonts/Menlo Nerd Font.ttc Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
gitattributes Normal file
View File

@@ -0,0 +1 @@
merge=mergiraf

View File

@@ -7,6 +7,7 @@
[tag]
gpgsign = true
[core]
attributesfile = ~/.gitattributes
excludesfile = ~/.gitignore
[color]
status = auto
@@ -16,6 +17,10 @@
whitespace = nowarn
[push]
default = simple
[pull]
rebase = true
[rerere]
enabled = true
[alias]
a = add
ai = add -i
@@ -39,10 +44,8 @@
pgp-on = config commit.gpgsign true
pgp-off = config commit.gpgsign false
tree = log --all --graph --decorate --oneline --simplify-by-decoration
[pull]
rebase = true
[difftool "Kaleidoscope"]
cmd = ksdiff-wrapper git \"$LOCAL\" \"$REMOTE\"
cmd = ksdiff --partial-changeset --relative-path \"$MERGED\" -- \"$LOCAL\" \"$REMOTE\"
[difftool]
prompt = false
[diff]
@@ -60,10 +63,30 @@
smudge = git hawser smudge %f
required = true
[filter "lfs"]
clean = git-lfs clean %f
smudge = git-lfs smudge %f
clean = git-lfs clean -- %f
smudge = git-lfs smudge -- %f
required = true
process = git-lfs filter-process
[include]
path = ~/.gitconfig_private
[init]
defaultBranch = main
[gitlab]
user = jimeh
[credential "https://github.com"]
# helper = !~/.dotfiles/bin/gh-git-credential-helper
helper = !cd ~/ && op plugin run -- gh auth git-credential
[credential "https://gist.github.com"]
# helper = !~/.dotfiles/bin/gh-git-credential-helper
helper = !cd ~/ && op plugin run -- gh auth git-credential
[merge "mergiraf"]
name = mergiraf
driver = mergiraf merge --git %O %A %B -s %S -x %X -y %Y -p %P -l %L
[mergetool "Kaleidoscope"]
cmd = ksdiff --merge --output \"$MERGED\" --base \"$BASE\" -- \"$LOCAL\" --snapshot \"$REMOTE\" --snapshot
trustExitCode = true
[gitbutler]
aiModelProvider = anthropic
aiAnthropicKeyOption = butlerAPI
signCommits = true
aiOpenAIKeyOption = butlerAPI

View File

@@ -1,19 +1,34 @@
**/._zinit/*
**/.agents/drafts/
**/.agents/plans/
**/.agents/reviews/
**/.claude/drafts/
**/.claude/plans/
**/.claude/reviews/
**/.claude/settings.local.json
**/.claude/worktrees/
**/.vscode/settings.json
**/.vscode/settings.local.json
**/chart/preprod-values.yaml
**/chart/production-values.yaml
**/chart/staging-values.yaml
**/log/*.bz2
**/log/*.gz
**/test/dummy/log/*.bz2
**/test/dummy/log/*.gz
**/vendor/bundle
.AppleDouble
.DS_Store
._zinit/*
.bin
.bundle
.byebug_history
.dir-locals.el
.envrc
.mise.*local.toml
.projectile
.stfolder
.stignore
.vagrant
.vscode
.zinitrc.zsh
log/*.bz2
log/*.gz
mise.*local.toml
routes.txt
test/dummy/log/*.bz2
test/dummy/log/*.gz
vendor/bundle

View File

@@ -0,0 +1,77 @@
[/]
action-contract-bottom=['<Control><Alt><Super>i']
action-contract-left=@as []
action-contract-right=['<Control><Alt><Super>j']
action-contract-top=@as []
action-expand-bottom=['<Control><Alt><Super>k']
action-expand-right=['<Control><Alt><Super>l']
action-move-down=['<Control><Alt>k']
action-move-left=['<Control><Alt>j']
action-move-next-monitor=['<Control><Super>period']
action-move-right=['<Control><Alt>l']
action-move-up=['<Control><Alt>i']
contract-down=['<Shift>i']
contract-right=['<Shift>j']
expand-down=['<Shift>k']
expand-right=['<Shift>l']
grid-sizes='30x20'
insets-primary-bottom=1
insets-primary-left=4
insets-primary-right=4
insets-primary-top=4
insets-secondary-bottom=1
insets-secondary-left=4
insets-secondary-right=4
insets-secondary-top=4
move-down=['k']
move-left=['j']
move-next-monitor=['n']
move-right=['l']
move-up=['i']
preset-resize-1=['<Control><Super>j']
preset-resize-10=['<Control><Alt>backslash']
preset-resize-11=['<Control><Super>backslash']
preset-resize-12=['<Control><Alt>apostrophe']
preset-resize-13=['<Control><Super>apostrophe']
preset-resize-14=['<Control><Alt>semicolon']
preset-resize-15=['<Control><Super>semicolon']
preset-resize-16=['<Control><Super>h']
preset-resize-17=@as []
preset-resize-18=@as []
preset-resize-19=@as []
preset-resize-2=['<Control><Super>l']
preset-resize-21=@as []
preset-resize-22=@as []
preset-resize-23=@as []
preset-resize-24=@as []
preset-resize-25=@as []
preset-resize-26=@as []
preset-resize-27=@as []
preset-resize-28=@as []
preset-resize-29=@as []
preset-resize-3=['<Control><Super>i']
preset-resize-4=['<Control><Super>k']
preset-resize-5=['<Control><Alt>u']
preset-resize-6=['<Control><Alt>o']
preset-resize-7=['<Control><Super>u']
preset-resize-8=['<Control><Super>o']
preset-resize-9=['<Control><Alt><Super>backslash']
resize1='30x20 1:1 15:20'
resize10='30x20 10:1 21:20'
resize11='30x20 8:1 23:20'
resize12='30x20 7:1 24:20'
resize13='30x20 6:1 25:20'
resize14='30x20 5:1 26:20'
resize15='30x20 4:1 27:20'
resize16='30x20 1:1 30:20'
resize2='30x20 16:1 30:20'
resize3='30x20 1:1 30:10'
resize4='30x20 1:11 30:20'
resize5='30x20 1:1 10:20'
resize6='30x20 21:1 30:20'
resize7='30x20 1:1 18:20'
resize8='30x20 13:1 30:20'
resize9='30x20 11:1 20:20'
show-grid-lines=true
show-toggle-tiling=['<Control><Super>1']
window-spacing=2

10
hammerspoon/.luarc.json Normal file
View File

@@ -0,0 +1,10 @@
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"diagnostics.globals": [
"hs",
"spoon"
],
"workspace.library": [
"/Applications/Hammerspoon.app/Contents/Resources/extensions/hs"
]
}

View File

@@ -68,5 +68,6 @@ endef
#
$(eval $(call dep-file,inspect.lua,"https://github.com/kikito/inspect.lua/raw/v3.1.0/inspect.lua"))
$(eval $(call dep-spoon,RoundedCorners,"https://github.com/Hammerspoon/Spoons/raw/master/Spoons/RoundedCorners.spoon.zip"))
$(eval $(call dep-spoon,HeadphoneAutoPause,"https://github.com/Hammerspoon/Spoons/raw/master/Spoons/HeadphoneAutoPause.spoon.zip"))
$(eval $(call dep-spoon,RoundedCorners,"https://github.com/Hammerspoon/Spoons/raw/master/Spoons/RoundedCorners.spoon.zip"))
$(eval $(call dep-spoon,URLDispatcher,"https://github.com/Hammerspoon/Spoons/raw/master/Spoons/URLDispatcher.spoon.zip"))

View File

@@ -0,0 +1,400 @@
[
{
"Command": [],
"Constant": [],
"Constructor": [],
"Deprecated": [],
"Field": [],
"Function": [],
"Method": [
{
"def": "URLDispatcher:dispatchURL(scheme, host, params, fullUrl, senderPid)",
"desc": "Dispatch a URL to an application according to the defined `url_patterns`.",
"doc": "Dispatch a URL to an application according to the defined `url_patterns`.\n\nParameters:\n * scheme - A string containing the URL scheme (i.e. \"http\")\n * host - A string containing the host requested (e.g. \"www.hammerspoon.org\")\n * params - A table containing the key/value pairs of all the URL parameters\n * fullURL - A string containing the full, original URL. This is the only parameter used in this implementation.\n * senderPID - An integer containing the PID of the application that opened the URL, if available (otherwise -1)\n\nNotes:\n * The parameters (follow to the [httpCallback](http://www.hammerspoon.org/docs/hs.urlevent.html#httpCallback) specification)",
"examples": [],
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "188",
"name": "dispatchURL",
"notes": [
" * The parameters (follow to the [httpCallback](http://www.hammerspoon.org/docs/hs.urlevent.html#httpCallback) specification)"
],
"parameters": [
" * scheme - A string containing the URL scheme (i.e. \"http\")",
" * host - A string containing the host requested (e.g. \"www.hammerspoon.org\")",
" * params - A table containing the key/value pairs of all the URL parameters",
" * fullURL - A string containing the full, original URL. This is the only parameter used in this implementation.",
" * senderPID - An integer containing the PID of the application that opened the URL, if available (otherwise -1)"
],
"returns": [],
"signature": "URLDispatcher:dispatchURL(scheme, host, params, fullUrl, senderPid)",
"stripped_doc": "",
"type": "Method"
},
{
"def": "URLDispatcher:start()",
"desc": "Start dispatching URLs according to the rules",
"doc": "Start dispatching URLs according to the rules\n\nParameters:\n * None",
"examples": [],
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "312",
"name": "start",
"notes": [],
"parameters": [
" * None"
],
"returns": [],
"signature": "URLDispatcher:start()",
"stripped_doc": "",
"type": "Method"
}
],
"Variable": [
{
"def": "URLDispatcher.decode_slack_redir_urls",
"desc": "If true, handle Slack-redir URLs to apply the rule on the destination URL. Defaults to `true`",
"doc": "If true, handle Slack-redir URLs to apply the rule on the destination URL. Defaults to `true`",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "30",
"name": "decode_slack_redir_urls",
"signature": "URLDispatcher.decode_slack_redir_urls",
"stripped_doc": "",
"type": "Variable"
},
{
"def": "URLDispatcher.default_handler",
"desc": "Default URL handler (Defaults to `\"com.apple.Safari\"`)",
"doc": "Default URL handler (Defaults to `\"com.apple.Safari\"`)\n\nNotes:\nCan be a string containing the Bundle ID of an application, or a function\nthat takes one argument, and which will be invoked with the URL to open.",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "21",
"name": "default_handler",
"notes": [
"Can be a string containing the Bundle ID of an application, or a function",
"that takes one argument, and which will be invoked with the URL to open."
],
"signature": "URLDispatcher.default_handler",
"stripped_doc": "",
"type": "Variable"
},
{
"def": "URLDispatcher.logger",
"desc": "Logger object used within the Spoon. Can be accessed to set the default log",
"doc": "Logger object used within the Spoon. Can be accessed to set the default log\nlevel for the messages coming from the Spoon.\n\nNotes:\nExample: `spoon.URLDispatcher.logger.setLogLevel(\"debug\")`",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "99",
"name": "logger",
"notes": [
"Example: `spoon.URLDispatcher.logger.setLogLevel(\"debug\")`"
],
"signature": "URLDispatcher.logger",
"stripped_doc": "level for the messages coming from the Spoon.",
"type": "Variable"
},
{
"def": "URLDispatcher.pat_files",
"desc": "Internal variable containing a table where the pattern lists read from files are kept indexed by file name, and automatically updated.",
"doc": "Internal variable containing a table where the pattern lists read from files are kept indexed by file name, and automatically updated.",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "114",
"name": "pat_files",
"signature": "URLDispatcher.pat_files",
"stripped_doc": "",
"type": "Variable"
},
{
"def": "URLDispatcher.pat_watchers",
"desc": "Internal variable containing a table where the watchers for the pattern files are kept indexed by file name.",
"doc": "Internal variable containing a table where the watchers for the pattern files are kept indexed by file name.",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "119",
"name": "pat_watchers",
"signature": "URLDispatcher.pat_watchers",
"stripped_doc": "",
"type": "Variable"
},
{
"def": "URLDispatcher.set_system_handler",
"desc": "If true, URLDispatcher sets itself as system handler for http requests.",
"doc": "If true, URLDispatcher sets itself as system handler for http requests.\nDefaults to `true`",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "108",
"name": "set_system_handler",
"signature": "URLDispatcher.set_system_handler",
"stripped_doc": "Defaults to `true`",
"type": "Variable"
},
{
"def": "URLDispatcher.url_patterns",
"desc": "URL dispatch rules.",
"doc": "URL dispatch rules.\n\nNotes:\n A table containing a list of dispatch rules. Rules are evaluated in the\n order they are declared. Each rule is a table with the following structure:\n `{ url-patterns, app-bundle-ID-or-function, function, app-patterns }`\n * `url-patterns` can be: (a) a single pattern as a string, (b) a table\n containing a list of strings, or (c) a string containing the path of a\n file from which the patterns will be read (if the string contains a valid\n filename it's used as a file, otherwise as a pattern). In case (c), a\n watcher will be set to automatically re-read the contents of the file\n when it changes. If a relative path is given (not starting with a \"/\"),\n then it is considered to be relative to the Hammerspoon configuration\n directory.\n * If `app-bundle-ID-or-function` is specified as a string, it is\n interpreted as a macOS application ID, and that application will be used\n to open matching URLs. If it is a function pointer, or not given but\n \"function\" is provided, it is expected to be a function that accepts a\n single argument, and it will be called with the URL.\n * If `app-patterns` is given, it should be a string or a table containing a\n pattern/list of patterns, and the rule will only be evaluated if the URL\n was opened from an application whose name matches one of those patterns.\n * Note that the patterns are [Lua patterns](https://www.lua.org/pil/20.2.html)\n and not regular expressions.\n * Defaults to an empty table, which has the effect of having all URLs\n dispatched to the `default_handler`.",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "69",
"name": "url_patterns",
"notes": [
" A table containing a list of dispatch rules. Rules are evaluated in the",
" order they are declared. Each rule is a table with the following structure:",
" `{ url-patterns, app-bundle-ID-or-function, function, app-patterns }`",
" * `url-patterns` can be: (a) a single pattern as a string, (b) a table",
" containing a list of strings, or (c) a string containing the path of a",
" file from which the patterns will be read (if the string contains a valid",
" filename it's used as a file, otherwise as a pattern). In case (c), a",
" watcher will be set to automatically re-read the contents of the file",
" when it changes. If a relative path is given (not starting with a \"/\"),",
" then it is considered to be relative to the Hammerspoon configuration",
" directory.",
" * If `app-bundle-ID-or-function` is specified as a string, it is",
" interpreted as a macOS application ID, and that application will be used",
" to open matching URLs. If it is a function pointer, or not given but",
" \"function\" is provided, it is expected to be a function that accepts a",
" single argument, and it will be called with the URL.",
" * If `app-patterns` is given, it should be a string or a table containing a",
" pattern/list of patterns, and the rule will only be evaluated if the URL",
" was opened from an application whose name matches one of those patterns.",
" * Note that the patterns are [Lua patterns](https://www.lua.org/pil/20.2.html)",
" and not regular expressions.",
" * Defaults to an empty table, which has the effect of having all URLs",
" dispatched to the `default_handler`."
],
"signature": "URLDispatcher.url_patterns",
"stripped_doc": "",
"type": "Variable"
},
{
"def": "URLDispatcher.url_redir_decoders",
"desc": "URL redirection decoders. Default value: empty list",
"doc": "URL redirection decoders. Default value: empty list\n\nNotes:\nList containing optional redirection decoders (other than the known Slack\ndecoder, which is enabled by `URLDispatcher.decode_slack_redir_urls` to\napply to URLs before dispatching them. Each list element must be a list\nitself with a maximum of five elements:\n * `decoder-name`: (String) a name to identify the decoder;\n * `decoder-pattern-or-function`: (String or Function) if a string is\n given, it is used as a [Lua pattern](https://www.lua.org/pil/20.2.html)\n to match against the URL. If a function is given, it will be called with\n arguments `scheme`, `host`, `params`, `fullUrl`, `senderPid` (the same\n arguments as passed to\n [hs.urlevent.httpCallback](https://www.hammerspoon.org/docs/hs.urlevent.html#httpCallback)),\n and must return a string that contains the URL to be opened. The\n returned value will be URL-decoded according to the value of `skip-decode-url` (below).\n * `pattern-replacement`: (String) a replacement pattern to apply if a\n match is found when a decoder pattern (previous argument) is provided.\n If a decoder function is given, this argument is ignored.\n * `skip-decode-url`: (Boolean, optional) whether to skip URL-decoding of the\n resulting string (defaults to `false`, by default URLs are always decoded)\n * `source-application`: (String or Table, optional): a pattern or list of\n patterns to match against the name of the application from which the URL\n was opened. If this parameter is present, the decoder will only be\n applied when the application matches. Default is to apply the decoder\n regardless of the application.\nIf given as strings, `decoder-pattern-or-function` and `pattern-replacement`\nare passed as arguments to\n[string.gsub](https://www.lua.org/manual/5.3/manual.html#pdf-string.gsub)\napplied on the original URL.",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "35",
"name": "url_redir_decoders",
"notes": [
"List containing optional redirection decoders (other than the known Slack",
"decoder, which is enabled by `URLDispatcher.decode_slack_redir_urls` to",
"apply to URLs before dispatching them. Each list element must be a list",
"itself with a maximum of five elements:",
" * `decoder-name`: (String) a name to identify the decoder;",
" * `decoder-pattern-or-function`: (String or Function) if a string is",
" given, it is used as a [Lua pattern](https://www.lua.org/pil/20.2.html)",
" to match against the URL. If a function is given, it will be called with",
" arguments `scheme`, `host`, `params`, `fullUrl`, `senderPid` (the same",
" arguments as passed to",
" [hs.urlevent.httpCallback](https://www.hammerspoon.org/docs/hs.urlevent.html#httpCallback)),",
" and must return a string that contains the URL to be opened. The",
" returned value will be URL-decoded according to the value of `skip-decode-url` (below).",
" * `pattern-replacement`: (String) a replacement pattern to apply if a",
" match is found when a decoder pattern (previous argument) is provided.",
" If a decoder function is given, this argument is ignored.",
" * `skip-decode-url`: (Boolean, optional) whether to skip URL-decoding of the",
" resulting string (defaults to `false`, by default URLs are always decoded)",
" * `source-application`: (String or Table, optional): a pattern or list of",
" patterns to match against the name of the application from which the URL",
" was opened. If this parameter is present, the decoder will only be",
" applied when the application matches. Default is to apply the decoder",
" regardless of the application.",
"If given as strings, `decoder-pattern-or-function` and `pattern-replacement`",
"are passed as arguments to",
"[string.gsub](https://www.lua.org/manual/5.3/manual.html#pdf-string.gsub)",
"applied on the original URL."
],
"signature": "URLDispatcher.url_redir_decoders",
"stripped_doc": "",
"type": "Variable"
}
],
"desc": "Route URLs to different applications with pattern matching",
"doc": "Route URLs to different applications with pattern matching\n\nDownload: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/URLDispatcher.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/URLDispatcher.spoon.zip)\n\nSets Hammerspoon as the default browser for HTTP/HTTPS links, and\ndispatches them to different apps according to the patterns defined\nin the config. If no pattern matches, `default_handler` is used.",
"items": [
{
"def": "URLDispatcher.decode_slack_redir_urls",
"desc": "If true, handle Slack-redir URLs to apply the rule on the destination URL. Defaults to `true`",
"doc": "If true, handle Slack-redir URLs to apply the rule on the destination URL. Defaults to `true`",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "30",
"name": "decode_slack_redir_urls",
"signature": "URLDispatcher.decode_slack_redir_urls",
"stripped_doc": "",
"type": "Variable"
},
{
"def": "URLDispatcher.default_handler",
"desc": "Default URL handler (Defaults to `\"com.apple.Safari\"`)",
"doc": "Default URL handler (Defaults to `\"com.apple.Safari\"`)\n\nNotes:\nCan be a string containing the Bundle ID of an application, or a function\nthat takes one argument, and which will be invoked with the URL to open.",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "21",
"name": "default_handler",
"notes": [
"Can be a string containing the Bundle ID of an application, or a function",
"that takes one argument, and which will be invoked with the URL to open."
],
"signature": "URLDispatcher.default_handler",
"stripped_doc": "",
"type": "Variable"
},
{
"def": "URLDispatcher:dispatchURL(scheme, host, params, fullUrl, senderPid)",
"desc": "Dispatch a URL to an application according to the defined `url_patterns`.",
"doc": "Dispatch a URL to an application according to the defined `url_patterns`.\n\nParameters:\n * scheme - A string containing the URL scheme (i.e. \"http\")\n * host - A string containing the host requested (e.g. \"www.hammerspoon.org\")\n * params - A table containing the key/value pairs of all the URL parameters\n * fullURL - A string containing the full, original URL. This is the only parameter used in this implementation.\n * senderPID - An integer containing the PID of the application that opened the URL, if available (otherwise -1)\n\nNotes:\n * The parameters (follow to the [httpCallback](http://www.hammerspoon.org/docs/hs.urlevent.html#httpCallback) specification)",
"examples": [],
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "188",
"name": "dispatchURL",
"notes": [
" * The parameters (follow to the [httpCallback](http://www.hammerspoon.org/docs/hs.urlevent.html#httpCallback) specification)"
],
"parameters": [
" * scheme - A string containing the URL scheme (i.e. \"http\")",
" * host - A string containing the host requested (e.g. \"www.hammerspoon.org\")",
" * params - A table containing the key/value pairs of all the URL parameters",
" * fullURL - A string containing the full, original URL. This is the only parameter used in this implementation.",
" * senderPID - An integer containing the PID of the application that opened the URL, if available (otherwise -1)"
],
"returns": [],
"signature": "URLDispatcher:dispatchURL(scheme, host, params, fullUrl, senderPid)",
"stripped_doc": "",
"type": "Method"
},
{
"def": "URLDispatcher.logger",
"desc": "Logger object used within the Spoon. Can be accessed to set the default log",
"doc": "Logger object used within the Spoon. Can be accessed to set the default log\nlevel for the messages coming from the Spoon.\n\nNotes:\nExample: `spoon.URLDispatcher.logger.setLogLevel(\"debug\")`",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "99",
"name": "logger",
"notes": [
"Example: `spoon.URLDispatcher.logger.setLogLevel(\"debug\")`"
],
"signature": "URLDispatcher.logger",
"stripped_doc": "level for the messages coming from the Spoon.",
"type": "Variable"
},
{
"def": "URLDispatcher.pat_files",
"desc": "Internal variable containing a table where the pattern lists read from files are kept indexed by file name, and automatically updated.",
"doc": "Internal variable containing a table where the pattern lists read from files are kept indexed by file name, and automatically updated.",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "114",
"name": "pat_files",
"signature": "URLDispatcher.pat_files",
"stripped_doc": "",
"type": "Variable"
},
{
"def": "URLDispatcher.pat_watchers",
"desc": "Internal variable containing a table where the watchers for the pattern files are kept indexed by file name.",
"doc": "Internal variable containing a table where the watchers for the pattern files are kept indexed by file name.",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "119",
"name": "pat_watchers",
"signature": "URLDispatcher.pat_watchers",
"stripped_doc": "",
"type": "Variable"
},
{
"def": "URLDispatcher.set_system_handler",
"desc": "If true, URLDispatcher sets itself as system handler for http requests.",
"doc": "If true, URLDispatcher sets itself as system handler for http requests.\nDefaults to `true`",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "108",
"name": "set_system_handler",
"signature": "URLDispatcher.set_system_handler",
"stripped_doc": "Defaults to `true`",
"type": "Variable"
},
{
"def": "URLDispatcher:start()",
"desc": "Start dispatching URLs according to the rules",
"doc": "Start dispatching URLs according to the rules\n\nParameters:\n * None",
"examples": [],
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "312",
"name": "start",
"notes": [],
"parameters": [
" * None"
],
"returns": [],
"signature": "URLDispatcher:start()",
"stripped_doc": "",
"type": "Method"
},
{
"def": "URLDispatcher.url_patterns",
"desc": "URL dispatch rules.",
"doc": "URL dispatch rules.\n\nNotes:\n A table containing a list of dispatch rules. Rules are evaluated in the\n order they are declared. Each rule is a table with the following structure:\n `{ url-patterns, app-bundle-ID-or-function, function, app-patterns }`\n * `url-patterns` can be: (a) a single pattern as a string, (b) a table\n containing a list of strings, or (c) a string containing the path of a\n file from which the patterns will be read (if the string contains a valid\n filename it's used as a file, otherwise as a pattern). In case (c), a\n watcher will be set to automatically re-read the contents of the file\n when it changes. If a relative path is given (not starting with a \"/\"),\n then it is considered to be relative to the Hammerspoon configuration\n directory.\n * If `app-bundle-ID-or-function` is specified as a string, it is\n interpreted as a macOS application ID, and that application will be used\n to open matching URLs. If it is a function pointer, or not given but\n \"function\" is provided, it is expected to be a function that accepts a\n single argument, and it will be called with the URL.\n * If `app-patterns` is given, it should be a string or a table containing a\n pattern/list of patterns, and the rule will only be evaluated if the URL\n was opened from an application whose name matches one of those patterns.\n * Note that the patterns are [Lua patterns](https://www.lua.org/pil/20.2.html)\n and not regular expressions.\n * Defaults to an empty table, which has the effect of having all URLs\n dispatched to the `default_handler`.",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "69",
"name": "url_patterns",
"notes": [
" A table containing a list of dispatch rules. Rules are evaluated in the",
" order they are declared. Each rule is a table with the following structure:",
" `{ url-patterns, app-bundle-ID-or-function, function, app-patterns }`",
" * `url-patterns` can be: (a) a single pattern as a string, (b) a table",
" containing a list of strings, or (c) a string containing the path of a",
" file from which the patterns will be read (if the string contains a valid",
" filename it's used as a file, otherwise as a pattern). In case (c), a",
" watcher will be set to automatically re-read the contents of the file",
" when it changes. If a relative path is given (not starting with a \"/\"),",
" then it is considered to be relative to the Hammerspoon configuration",
" directory.",
" * If `app-bundle-ID-or-function` is specified as a string, it is",
" interpreted as a macOS application ID, and that application will be used",
" to open matching URLs. If it is a function pointer, or not given but",
" \"function\" is provided, it is expected to be a function that accepts a",
" single argument, and it will be called with the URL.",
" * If `app-patterns` is given, it should be a string or a table containing a",
" pattern/list of patterns, and the rule will only be evaluated if the URL",
" was opened from an application whose name matches one of those patterns.",
" * Note that the patterns are [Lua patterns](https://www.lua.org/pil/20.2.html)",
" and not regular expressions.",
" * Defaults to an empty table, which has the effect of having all URLs",
" dispatched to the `default_handler`."
],
"signature": "URLDispatcher.url_patterns",
"stripped_doc": "",
"type": "Variable"
},
{
"def": "URLDispatcher.url_redir_decoders",
"desc": "URL redirection decoders. Default value: empty list",
"doc": "URL redirection decoders. Default value: empty list\n\nNotes:\nList containing optional redirection decoders (other than the known Slack\ndecoder, which is enabled by `URLDispatcher.decode_slack_redir_urls` to\napply to URLs before dispatching them. Each list element must be a list\nitself with a maximum of five elements:\n * `decoder-name`: (String) a name to identify the decoder;\n * `decoder-pattern-or-function`: (String or Function) if a string is\n given, it is used as a [Lua pattern](https://www.lua.org/pil/20.2.html)\n to match against the URL. If a function is given, it will be called with\n arguments `scheme`, `host`, `params`, `fullUrl`, `senderPid` (the same\n arguments as passed to\n [hs.urlevent.httpCallback](https://www.hammerspoon.org/docs/hs.urlevent.html#httpCallback)),\n and must return a string that contains the URL to be opened. The\n returned value will be URL-decoded according to the value of `skip-decode-url` (below).\n * `pattern-replacement`: (String) a replacement pattern to apply if a\n match is found when a decoder pattern (previous argument) is provided.\n If a decoder function is given, this argument is ignored.\n * `skip-decode-url`: (Boolean, optional) whether to skip URL-decoding of the\n resulting string (defaults to `false`, by default URLs are always decoded)\n * `source-application`: (String or Table, optional): a pattern or list of\n patterns to match against the name of the application from which the URL\n was opened. If this parameter is present, the decoder will only be\n applied when the application matches. Default is to apply the decoder\n regardless of the application.\nIf given as strings, `decoder-pattern-or-function` and `pattern-replacement`\nare passed as arguments to\n[string.gsub](https://www.lua.org/manual/5.3/manual.html#pdf-string.gsub)\napplied on the original URL.",
"file": "Source/URLDispatcher.spoon//init.lua",
"lineno": "35",
"name": "url_redir_decoders",
"notes": [
"List containing optional redirection decoders (other than the known Slack",
"decoder, which is enabled by `URLDispatcher.decode_slack_redir_urls` to",
"apply to URLs before dispatching them. Each list element must be a list",
"itself with a maximum of five elements:",
" * `decoder-name`: (String) a name to identify the decoder;",
" * `decoder-pattern-or-function`: (String or Function) if a string is",
" given, it is used as a [Lua pattern](https://www.lua.org/pil/20.2.html)",
" to match against the URL. If a function is given, it will be called with",
" arguments `scheme`, `host`, `params`, `fullUrl`, `senderPid` (the same",
" arguments as passed to",
" [hs.urlevent.httpCallback](https://www.hammerspoon.org/docs/hs.urlevent.html#httpCallback)),",
" and must return a string that contains the URL to be opened. The",
" returned value will be URL-decoded according to the value of `skip-decode-url` (below).",
" * `pattern-replacement`: (String) a replacement pattern to apply if a",
" match is found when a decoder pattern (previous argument) is provided.",
" If a decoder function is given, this argument is ignored.",
" * `skip-decode-url`: (Boolean, optional) whether to skip URL-decoding of the",
" resulting string (defaults to `false`, by default URLs are always decoded)",
" * `source-application`: (String or Table, optional): a pattern or list of",
" patterns to match against the name of the application from which the URL",
" was opened. If this parameter is present, the decoder will only be",
" applied when the application matches. Default is to apply the decoder",
" regardless of the application.",
"If given as strings, `decoder-pattern-or-function` and `pattern-replacement`",
"are passed as arguments to",
"[string.gsub](https://www.lua.org/manual/5.3/manual.html#pdf-string.gsub)",
"applied on the original URL."
],
"signature": "URLDispatcher.url_redir_decoders",
"stripped_doc": "",
"type": "Variable"
}
],
"name": "URLDispatcher",
"stripped_doc": "\nDownload: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/URLDispatcher.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/URLDispatcher.spoon.zip)\n\nSets Hammerspoon as the default browser for HTTP/HTTPS links, and\ndispatches them to different apps according to the patterns defined\nin the config. If no pattern matches, `default_handler` is used.",
"submodules": [],
"type": "Module"
}
]

View File

@@ -0,0 +1,330 @@
--- === URLDispatcher ===
---
--- Route URLs to different applications with pattern matching
---
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/URLDispatcher.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/URLDispatcher.spoon.zip)
---
--- Sets Hammerspoon as the default browser for HTTP/HTTPS links, and
--- dispatches them to different apps according to the patterns defined
--- in the config. If no pattern matches, `default_handler` is used.
local obj={}
obj.__index = obj
-- Metadata
obj.name = "URLDispatcher"
obj.version = "0.5"
obj.author = "Diego Zamboni <diego@zzamboni.org>"
obj.homepage = "https://github.com/Hammerspoon/Spoons"
obj.license = "MIT - https://opensource.org/licenses/MIT"
--- URLDispatcher.default_handler
--- Variable
--- Default URL handler (Defaults to `"com.apple.Safari"`)
---
--- Notes:
--- Can be a string containing the Bundle ID of an application, or a function
--- that takes one argument, and which will be invoked with the URL to open.
obj.default_handler = "com.apple.Safari"
--- URLDispatcher.decode_slack_redir_urls
--- Variable
--- If true, handle Slack-redir URLs to apply the rule on the destination URL. Defaults to `true`
obj.decode_slack_redir_urls = true
--- URLDispatcher.url_redir_decoders
--- Variable
--- URL redirection decoders. Default value: empty list
---
--- Notes:
--- List containing optional redirection decoders (other than the known Slack
--- decoder, which is enabled by `URLDispatcher.decode_slack_redir_urls` to
--- apply to URLs before dispatching them. Each list element must be a list
--- itself with a maximum of five elements:
--- * `decoder-name`: (String) a name to identify the decoder;
--- * `decoder-pattern-or-function`: (String or Function) if a string is
--- given, it is used as a [Lua pattern](https://www.lua.org/pil/20.2.html)
--- to match against the URL. If a function is given, it will be called with
--- arguments `scheme`, `host`, `params`, `fullUrl`, `senderPid` (the same
--- arguments as passed to
--- [hs.urlevent.httpCallback](https://www.hammerspoon.org/docs/hs.urlevent.html#httpCallback)),
--- and must return a string that contains the URL to be opened. The
--- returned value will be URL-decoded according to the value of `skip-decode-url` (below).
--- * `pattern-replacement`: (String) a replacement pattern to apply if a
--- match is found when a decoder pattern (previous argument) is provided.
--- If a decoder function is given, this argument is ignored.
--- * `skip-decode-url`: (Boolean, optional) whether to skip URL-decoding of the
--- resulting string (defaults to `false`, by default URLs are always decoded)
--- * `source-application`: (String or Table, optional): a pattern or list of
--- patterns to match against the name of the application from which the URL
--- was opened. If this parameter is present, the decoder will only be
--- applied when the application matches. Default is to apply the decoder
--- regardless of the application.
--- If given as strings, `decoder-pattern-or-function` and `pattern-replacement`
--- are passed as arguments to
--- [string.gsub](https://www.lua.org/manual/5.3/manual.html#pdf-string.gsub)
--- applied on the original URL.
obj.url_redir_decoders = { }
--- URLDispatcher.url_patterns
--- Variable
--- URL dispatch rules.
---
--- Notes:
--- A table containing a list of dispatch rules. Rules are evaluated in the
--- order they are declared. Each rule is a table with the following structure:
--- `{ url-patterns, app-bundle-ID-or-function, function, app-patterns }`
--- * `url-patterns` can be: (a) a single pattern as a string, (b) a table
--- containing a list of strings, or (c) a string containing the path of a
--- file from which the patterns will be read (if the string contains a valid
--- filename it's used as a file, otherwise as a pattern). In case (c), a
--- watcher will be set to automatically re-read the contents of the file
--- when it changes. If a relative path is given (not starting with a "/"),
--- then it is considered to be relative to the Hammerspoon configuration
--- directory.
--- * If `app-bundle-ID-or-function` is specified as a string, it is
--- interpreted as a macOS application ID, and that application will be used
--- to open matching URLs. If it is a function pointer, or not given but
--- "function" is provided, it is expected to be a function that accepts a
--- single argument, and it will be called with the URL.
--- * If `app-patterns` is given, it should be a string or a table containing a
--- pattern/list of patterns, and the rule will only be evaluated if the URL
--- was opened from an application whose name matches one of those patterns.
--- * Note that the patterns are [Lua patterns](https://www.lua.org/pil/20.2.html)
--- and not regular expressions.
--- * Defaults to an empty table, which has the effect of having all URLs
--- dispatched to the `default_handler`.
obj.url_patterns = { }
--- URLDispatcher.logger
--- Variable
--- Logger object used within the Spoon. Can be accessed to set the default log
--- level for the messages coming from the Spoon.
---
--- Notes:
--- Example: `spoon.URLDispatcher.logger.setLogLevel("debug")`
obj.logger = hs.logger.new('URLDispatcher')
--- URLDispatcher.set_system_handler
--- Variable
--- If true, URLDispatcher sets itself as system handler for http requests.
--- Defaults to `true`
obj.set_system_handler = true
--- URLDispatcher.pat_files
--- Variable
--- Internal variable containing a table where the pattern lists read from files are kept indexed by file name, and automatically updated.
obj.pat_files = {}
--- URLDispatcher.pat_watchers
--- Variable
--- Internal variable containing a table where the watchers for the pattern files are kept indexed by file name.
obj.pat_watchers = {}
-- Local functions to decode URLs
function hex_to_char(x)
return string.char(tonumber(x, 16))
end
function obj.unescape(url)
return url:gsub("%%(%x%x)", hex_to_char)
end
-- Match a single pattern against an application name.
function obj.matchapp(app, pat)
obj.logger.df("Matching appname '%s' against pattern '%s'", app, pat)
return string.find(app, pat)
end
-- Match a pattern or a list of patterns against an application name.
-- The pattern can also be nil, in this case it's considered a success.
function obj.matchapps(app, pat)
local ismatch = (pat == nil) or
(type(pat) == 'string' and obj.matchapp(app, pat)) or
(type(pat) == 'table' and hs.fnutils.some(pat, hs.fnutils.partial(obj.matchapp, app)))
if ismatch then
obj.logger.df(" App pattern '%s' is nil or matches application name '%s' - evaluating rule.", pat, app)
else
obj.logger.df(" App pattern '%s' does not match application name '%s' - skipping rule.", pat, app)
end
return ismatch
end
function obj:read_and_store(patfile)
self.logger.df("Reading patterns from file '%s'", patfile)
local pats = {}
for line in io.lines(patfile) do
-- Skip empty lines and lines starting with "#" (comments)
if (line ~= '') and not (string.find(line, '^%s*#')) then
table.insert(pats, line)
end
end
self.pat_files[patfile] = hs.fnutils.copy(pats)
end
function obj:patfileWatcher(patfile, paths, flags)
-- Only trigger re-reading the file when the 'itemModified' flag is present,
-- otherwise the file gets read multiple times due to file manipulations done
-- by editors
if hs.fnutils.some(flags, function(f) return f['itemModified'] end) then
self:read_and_store(patfile)
end
end
function obj:setupPatfile(patfile)
-- If the file exists, read it and setup a watcher to update it.
if hs.fs.attributes(patfile) then
self.logger.df("File '%s' has not been loaded, reading it now.", patfile)
-- Read the file and set up the watcher to auto-update it.
self:read_and_store(patfile)
self.logger.df("Creating watcher for file '%s'", patfile)
self.pat_watchers[patfile] = hs.pathwatcher.new(patfile, hs.fnutils.partial(self.patfileWatcher, self, patfile)):start()
return self.pat_files[patfile]
else
return nil
end
end
--- URLDispatcher:dispatchURL(scheme, host, params, fullUrl, senderPid)
--- Method
--- Dispatch a URL to an application according to the defined `url_patterns`.
---
--- Parameters:
--- * scheme - A string containing the URL scheme (i.e. "http")
--- * host - A string containing the host requested (e.g. "www.hammerspoon.org")
--- * params - A table containing the key/value pairs of all the URL parameters
--- * fullURL - A string containing the full, original URL. This is the only parameter used in this implementation.
--- * senderPID - An integer containing the PID of the application that opened the URL, if available (otherwise -1)
---
--- Notes:
--- * The parameters (follow to the [httpCallback](http://www.hammerspoon.org/docs/hs.urlevent.html#httpCallback) specification)
function obj:dispatchURL(scheme, host, params, fullUrl, senderPid)
local url = fullUrl
local currentApp = ""
if senderPid ~= -1 then
currentApp = hs.application.applicationForPID(senderPid):name()
end
self.logger.df("Dispatching URL '%s' from application '%s'", url, currentApp)
if self.decode_slack_redir_urls then
local newUrl = string.match(url, 'https://slack.redir.net/.*url=(.*)')
if newUrl then
url = obj.unescape(newUrl)
self.logger.df(" Decoded Slack redirect. New URL: '%s'", url)
end
end
for i,dec in ipairs(self.url_redir_decoders) do
self.logger.df(" Testing decoder '%s'", dec[1])
local processed = false
if self.matchapps(currentApp, dec[5]) then
if type(dec[2]) == "string" then
if string.find(url, dec[2]) then
self.logger.df(" Applying pattern-based decoder '%s' to URL '%s'", dec[1], url)
url = string.gsub(url, dec[2], dec[3])
self.logger.df(" Decoded URL: '%s'", url)
processed = true
end
elseif type(dec[2]) == "function" then
self.logger.df(" Applying function-based decoder '%s' to URL '%s'", dec[1], url)
url = dec[2](scheme, host, params, fullUrl, senderPid)
self.logger.df(" Decoded URL: '%s'", url)
processed = true
else
self.logger.ef(" Decoder '%s' has an unknown second value of type '%s'", dec[1], dec[2])
end
if processed and (not dec[4]) then
self.logger.df(" Unescaping decoded URL '%s'", url)
url = obj.unescape(url)
self.logger.df(" Unescaped URL: '%s'", url)
end
end
end
self.logger.df("Final URL to open: '%s'", url)
for i,pair in ipairs(self.url_patterns) do
self.logger.df("Evaluating rule %s", hs.inspect(pair))
local pats = pair[1]
local app = pair[2]
local func = pair[3]
local app_pats = pair[4]
-- If app_pats is given, then first of all check whether the source app
-- matches, otherwise we skip the whole thing
if self.matchapps(currentApp, app_pats) then
-- First determine how to interpret the url-patterns
if type(pats) == "string" then
-- A string can be a single pattern, or a filename to load
if self.pat_files[pats] then
-- If it's already a known pattern file, use its content
self.logger.df(" File '%s' is already read, using its contents.", pats)
pats = self.pat_files[pats]
else
-- Else, try to load it as a file
local patsfile = self:setupPatfile(pats)
-- If this fails, we use it as a single pattern
if patsfile then
pats = patsfile
else
self.logger.df(" Single pattern given, converting to list for processing.")
pats = { pats }
end
end
end
for i,p in ipairs(pats) do
self.logger.df(" Testing URL with pattern '%s'", p)
if string.match(url, p) then
local id = nil
if type(app) == "string" then
id = app
elseif type(app) == "function" then
func = app
end
if id ~= nil then
self.logger.df(" Match found, opening with '%s'", id)
hs.application.launchOrFocusByBundleID(id)
hs.urlevent.openURLWithBundle(url, id)
return
end
if func ~= nil then
self.logger.df(" Match found, calling func '%s'", func)
func(url)
return
end
end
end
end
end
-- Fall through to the default handler
if type(self.default_handler) == "string" then
self.logger.df("No match found, opening with default handler '%s'", self.default_handler)
hs.application.launchOrFocusByBundleID(self.default_handler)
hs.urlevent.openURLWithBundle(url, self.default_handler)
elseif type(self.default_handler) == "function" then
self.logger.df("No match found, opening with default handler func '%s'", self.default_handler)
self.default_handler(url)
else
self.logger.ef("Unknown type '%s' for default_handler '%s', must be a string or a function.",
type(self.default_handler), self.default_handler)
end
end
--- URLDispatcher:start()
--- Method
--- Start dispatching URLs according to the rules
---
--- Parameters:
--- * None
function obj:start()
if hs.urlevent.httpCallback then
self.logger.w("An hs.urlevent.httpCallback was already set. I'm overriding it with my own but you should check if this breaks any other functionality")
end
hs.urlevent.httpCallback = function(...) self:dispatchURL(...) end
if self.set_system_handler then
hs.urlevent.setDefaultHandler('http')
end
-- hs.urlevent.setRestoreHandler('http', self.default_handler)
return self
end
return obj

91
hammerspoon/app_hider.lua Normal file
View File

@@ -0,0 +1,91 @@
--- === app_hider ===
---
--- A Hammerspoon module which hides specified applications when they are
--- deactivated (lose focus).
local obj = {}
local autoHideApps = {}
local function shouldAutoHide(appName)
for _, name in ipairs(autoHideApps) do
if appName == name then
return true
end
end
return false
end
local function appWatcherCallback(appName, eventType, appObject)
if eventType == hs.application.watcher.deactivated and shouldAutoHide(appName) then
appObject:hide()
end
end
local appWatcher = hs.application.watcher.new(appWatcherCallback)
obj.started = false
function obj:start()
if obj.started then
return
end
appWatcher:start()
obj.started = true
end
function obj:stop()
if not obj.started then
return
end
appWatcher:stop()
obj.started = false
end
--- app_hider:autoHide(appName)
--- Method
--- Adds an application to the auto-hide list.
---
--- Parameters:
--- * appName - A string with the name of the application to auto-hide.
function obj:autoHide(appName)
if not appName or type(appName) ~= "string" then
print("Error: Invalid app name provided to autoHide.")
return
end
obj:start() -- Ensure the watcher is running
-- Prevent duplicates in the autoHideApps table
for _, name in ipairs(autoHideApps) do
if name == appName then
print("App already in auto-hide list: " .. appName)
return
end
end
table.insert(autoHideApps, appName)
end
--- app_hider:remove(appName)
--- Method
--- Removes an application from the auto-hide list.
---
--- Parameters:
--- * appName - A string with the name of the application to remove from the
--- auto-hide list.
function obj:remove(appName)
for i, name in ipairs(autoHideApps) do
if name == appName then
table.remove(autoHideApps, i)
break
end
end
-- Stop the watcher if the list is empty
if #autoHideApps == 0 and obj.started then
obj:stop()
end
end
return obj

View File

@@ -1,38 +1,187 @@
-- luacheck: read_globals hs
--- === app_toggle ===
---
--- A Hammerspoon module for toggling between specified applications using
--- hotkeys.
---
--- This module allows you to bind a hotkey to switch focus between specific
--- applications and show/hide them.
local obj = {}
function obj:bind (mods, key, name, path)
hs.hotkey.bind(mods, key, self:toggleFn(name, path))
end
function obj:toggleFn (name, path)
return function ()
self:toggle(name, path)
end
end
function obj:toggle (name, path)
local app = self.findRunningApp(name, path)
if app == nil then
return hs.application.open(path or name)
end
if app == hs.application.frontmostApplication() then
return app:hide()
end
return app:activate()
end
function obj.findRunningApp (name, path)
local function findRunningApp(name, path)
for _, app in ipairs(hs.application.runningApplications()) do
if app:name() == name and (path == nil or path == app:path()) then
-- Get app name, and also get a sanitized version of the name by removing
-- any non-printable characters. Some apps like WhatsApp have names that
-- contain invisible characters that cause the app to not be found.
local appName = app:name()
local sanitizedAppName = appName:gsub('[^%g+]', '')
-- app:path() can error for certain pseudo-apps.
-- Guard with pcall and skip on failure to keep iterating.
local ok, appPath = pcall(function()
return app:path()
end)
-- Skip apps that don't have a path or that don't end with ".app". If the
-- path doesn't end with ".app", it's not likely to be a GUI app.
if ok and appPath and appPath:match("%.app$")
and (appName == name or sanitizedAppName == name)
and (path == nil or path == appPath) then
return app
end
end
end
local focusTimes = {}
local function focusWatcher(_, eventType, appObject)
if eventType == hs.application.watcher.activated then
focusTimes[appObject:bundleID()] = hs.timer.secondsSinceEpoch()
end
end
local appWatcher = hs.application.watcher.new(focusWatcher)
obj.started = false
function obj:start()
if obj.started then
return
end
appWatcher:start()
obj.started = true
end
function obj:stop()
if not obj.started then
return
end
appWatcher:stop()
obj.started = false
end
--- app_toggle:bind(mods, key, ...)
--- Method
--- Binds a hotkey to toggle between the specified applications.
---
--- Parameters:
--- * mods - A table with the modifiers for the hotkey
--- * key - A string with the key for the hotkey
--- * ... - A list of tables, each containing an application name and an
--- optional path
function obj:bind(mods, key, ...)
local apps = { ... }
if #apps > 1 then
self:start()
end
hs.hotkey.bind(mods, key, self:toggleFn(apps))
end
--- app_toggle:toggleFn(apps)
--- Method
--- Creates and returns a function that toggles between the specified
--- applications via app_toggle:toggle() when called.
---
--- Parameters:
--- * apps - A table containing application configurations. Each configuration
--- is a table with an application name at the first index and an
--- optional path at the second index.
---
--- Returns:
--- * A function that, when called, toggles between the specified applications.
---
--- Example:
--- local toggleApps = obj:toggleFn({{"Firefox"}, {"Safari"}})
--- hs.hotkey.bind({"cmd", "ctrl"}, "b", toggleApps)
---
--- Notes:
--- * The returned function can be used as a callback for hotkey bindings or
--- other event-driven scenarios.
function obj:toggleFn(apps)
return function()
self:toggle(apps)
end
end
--- app_toggle:toggle(apps)
--- Method
--- Toggles focus/visibility specified applications.
---
--- Parameters:
--- * apps - A table containing application configurations. Each configuration
--- is a table with an application name at the first index and an
--- optional path at the second index.
---
--- Notes:
--- * If none of the specified applications are running, the function attempts
--- to launch the first application in the list.
--- * If the most recently focused application in the list is the current
--- frontmost application, it will be hidden. Otherwise, the most recently
--- focused application will be brought to the front.
function obj:toggle(apps)
local runningApps = {}
local mostRecentApp = nil
local mostRecentTime = -1
for _, appInfo in ipairs(apps) do
local name, path = appInfo[1], appInfo[2]
local app = findRunningApp(name, path)
if app then
table.insert(runningApps, app)
local focusTime = focusTimes[app:bundleID()] or 0
if focusTime > mostRecentTime then
mostRecentTime = focusTime
mostRecentApp = app
end
end
end
if #runningApps == 0 then
local app = apps[1]
local status, err = pcall(hs.application.open, app[2] or app[1])
if not status then
hs.alert.show('Failed to open ' .. (app[2] or app[1]) .. ': ' .. err)
end
return
end
if not mostRecentApp then
mostRecentApp = runningApps[1]
end
local frontMostApp = hs.application.frontmostApplication()
if frontMostApp and mostRecentApp == frontMostApp then
return mostRecentApp:hide()
end
return mostRecentApp:activate()
end
--- app_toggle:showAppInfo()
--- Method
--- Shows an alert with information about the frontmost application.
function obj:showAppInfo()
local app = hs.application.frontmostApplication()
local ok, appPath = pcall(function()
return app:path()
end)
local info = { app:name() .. " (" .. app:bundleID() .. ")" }
if ok and appPath then
table.insert(info, appPath)
else
table.insert(info, "Path: <unavailable>")
end
table.insert(info, "PID: " .. app:pid())
print("Frontmost app info:")
for _, line in ipairs(info) do
hs.alert.show(line)
print(line)
end
end
-- the end
return obj

View File

@@ -1,14 +0,0 @@
local obj = {
hostname = nil
}
function obj.getHostname()
local f = io.popen ("hostname -s")
local hostname = f:read("*a") or ""
f:close()
hostname = string.gsub(hostname, "\n$", "")
return hostname
end
obj.hostname = obj.getHostname()
return obj

View File

@@ -1,20 +1,19 @@
local function require_file(path)
local ok, module = pcall(loadfile, path)
return (ok and module and module() or nil)
end
local obj = {}
function obj:init()
local env = require('env')
local conf_file = "hosts/" .. env.hostname .. ".lua"
local conf_req = "hosts." .. env.hostname
local hostname = hs.host.localizedName()
local conf_file = 'hosts/' .. hostname .. '.lua'
local hostmod = require_file(conf_file)
if self.file_exists(conf_file) then
print("loading host config: " .. conf_file)
local conf_module = require(conf_req)
conf_module:init()
if hostmod then
print('loading host config: ' .. conf_file)
hostmod.init()
end
end
function obj.file_exists(name)
local f = io.open(name, "r")
if f ~= nil then io.close(f) return true else return false end
end
return obj

View File

@@ -0,0 +1,74 @@
local obj = {}
--------------------------------------------------------------------------------
-- Global Hotkeys
--------------------------------------------------------------------------------
local apptoggle = require('app_toggle')
local apphider = require('app_hider')
local function init_hotkeys()
hs.hotkey.bind({ 'cmd', 'alt', 'ctrl' }, 'S', apptoggle.showAppInfo)
apptoggle:bind({ 'cmd', 'alt', 'ctrl' }, 'A', { 'Activity Monitor' })
apptoggle:bind({ 'cmd', 'ctrl' }, '1', { 'Codex' })
apptoggle:bind({ 'cmd', 'ctrl' }, '2', { 'Claude' })
apptoggle:bind({ 'cmd', 'ctrl' }, '3', { 'Conductor' })
apptoggle:bind({ 'cmd', 'ctrl' }, '4', { 'ChatGPT Atlas' }, { 'ChatGPT' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'A', { 'Argo CD' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'B', { 'TablePlus' }, { 'Lens' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'D', { 'Mail+ for Gmail' }, { 'Notion Mail' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'F', { 'GitButler' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'G', { 'Emacs', '/Applications/Emacs.app' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'T', { 'TeamSpeak 3', '/Applications/TeamSpeak 3 Client.app' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'X', { 'Notion' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'Z', { 'Slack' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'C',
{ 'Notion Calendar' },
{ 'Calendar' },
{ 'Google Calendar' }
)
apptoggle:bind({ 'cmd', 'ctrl' }, 'E', { 'Cursor' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'W',
{ 'Code - Insiders', '/Applications/Visual Studio Code - Insiders.app' },
{ 'Code', '/Applications/Visual Studio Code.app' }
)
-- -- Use Ghostty as my primary terminal application.
-- apptoggle:bind({ 'cmd', 'ctrl' }, 'R', { 'Ghostty' })
-- apphider:autoHide('Ghostty') -- auto-hide Ghostty when it loses focus
-- -- Use Warp as my primary terminal application.
-- apptoggle:bind({ 'cmd', 'ctrl' }, 'R', { 'Warp' })
-- apphider:autoHide('Warp') -- auto-hide Warp when it loses focus
end
--------------------------------------------------------------------------------
-- URL Handling
--------------------------------------------------------------------------------
-- local uh = require('url_handler')
-- local function init_url_handler()
-- uh.default_handler = uh.browsers.arc
-- uh.url_patterns = {
-- {
-- { "%://meet.google.com/" }, uh.browsers.chrome, nil,
-- }
-- }
-- uh:init()
-- end
--------------------------------------------------------------------------------
-- Initialization
--------------------------------------------------------------------------------
function obj.init()
init_hotkeys()
-- init_url_handler()
end
return obj

View File

@@ -1,22 +1,87 @@
local obj = {}
function obj.init()
local apptoggle = require('app_toggle')
--------------------------------------------------------------------------------
-- Global Hotkeys
--------------------------------------------------------------------------------
apptoggle:bind({'cmd', 'alt', 'ctrl'}, 'A', 'Activity Monitor')
apptoggle:bind({'cmd', 'ctrl'}, '1', 'Microsoft To-Do')
apptoggle:bind({'cmd', 'ctrl'}, '4', 'Skitch')
apptoggle:bind({'cmd', 'ctrl'}, 'A', 'Messages')
apptoggle:bind({'cmd', 'ctrl'}, 'B', 'TablePlus')
apptoggle:bind({'cmd', 'ctrl'}, 'C', 'Calendar')
apptoggle:bind({'cmd', 'ctrl'}, 'D', 'Mailplane')
apptoggle:bind({'cmd', 'ctrl'}, 'E', 'Emacs', '/Applications/Emacs.app')
apptoggle:bind({'cmd', 'ctrl'}, 'F', 'Element Nightly')
apptoggle:bind({'cmd', 'ctrl'}, 'S', 'Music')
apptoggle:bind({'cmd', 'ctrl'}, 'T', 'Discord PTB')
apptoggle:bind({'cmd', 'ctrl'}, 'W', 'WhatsApp')
apptoggle:bind({'cmd', 'ctrl'}, 'X', 'Notion')
apptoggle:bind({'cmd', 'ctrl'}, 'Z', 'Slack')
local apptoggle = require('app_toggle')
local apphider = require('app_hider')
local function init_hotkeys()
hs.hotkey.bind({ 'cmd', 'alt', 'ctrl' }, 'S', apptoggle.showAppInfo)
apptoggle:bind({ 'cmd', 'alt', 'ctrl' }, 'A', { 'Activity Monitor' })
apptoggle:bind({ 'cmd', 'ctrl' }, '1', { 'Codex' })
apptoggle:bind({ 'cmd', 'ctrl' }, '2', { 'Claude' })
apptoggle:bind({ 'cmd', 'ctrl' }, '3', { 'Conductor' })
apptoggle:bind({ 'cmd', 'ctrl' }, '4', { 'ChatGPT' }, { 'ChatGPT Atlas' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'A', { 'Messages' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'B', { 'TablePlus' }, { 'Sequel Pro' }, { 'Lens' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'D', { 'Mail+ for Gmail' }, { 'Mimestream' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'E', { 'Cursor' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'F', { 'GitButler' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'G', { 'Emacs', '/Applications/Emacs.app' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'T', { 'Discord PTB' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'X', { 'Notion' }, { 'Obsidian' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'Z', { 'WhatsApp' })
apptoggle:bind({ 'cmd', 'ctrl' }, 'W',
{ 'Code', '/Applications/Visual Studio Code.app' },
{ 'Code - Insiders', '/Applications/Visual Studio Code - Insiders.app' }
)
apptoggle:bind({ 'cmd', 'ctrl' }, 'C',
{ 'Calendar' },
{ 'Google Calendar' },
{ 'Notion Calendar' }
)
-- -- Use Warp as my primary terminal application.
-- apptoggle:bind({ 'cmd', 'ctrl' }, 'R', { 'Warp' })
-- apphider:autoHide('Warp') -- auto-hide Warp when it loses focus
-- Use Ghostty as my primary terminal application.
-- apptoggle:bind({ 'cmd', 'ctrl' }, 'R', { 'Ghostty' })
-- apphider:autoHide('Ghostty') -- auto-hide Ghostty when it loses focus
end
--------------------------------------------------------------------------------
-- URL Handling
--------------------------------------------------------------------------------
-- local uh = require('url_handler')
-- local function init_url_handler()
-- uh.default_handler = uh.browsers.arc
-- uh.url_patterns = {
-- {
-- { "%://meet.google.com/" }, uh.browsers.chrome, nil,
-- }
-- }
-- -- uh.url_redir_decoders = {
-- -- {
-- -- "MS Teams links",
-- -- function(_, _, params, fullUrl)
-- -- if params.url then
-- -- return params.url
-- -- else
-- -- return fullUrl
-- -- end
-- -- end,
-- -- nil, true, "Microsoft Teams"
-- -- },
-- -- }
-- uh:init()
-- end
--------------------------------------------------------------------------------
-- Initialization
--------------------------------------------------------------------------------
function obj.init()
init_hotkeys()
-- init_url_handler()
end
return obj

View File

@@ -1,8 +1,8 @@
-- luacheck: read_globals hs spoon
-- Reload config hotkey
hs.hotkey.bind({'cmd', 'alt', 'ctrl'}, 'R', hs.reload)
hs.hotkey.bind({'cmd', 'alt', 'ctrl'}, 'C', hs.toggleConsole)
hs.hotkey.bind({ 'cmd', 'alt', 'ctrl' }, 'R', hs.reload)
hs.hotkey.bind({ 'cmd', 'alt', 'ctrl' }, 'C', hs.toggleConsole)
--------------------------------------------------------------------------------
-- Set Hammerspoon options
@@ -21,6 +21,7 @@ hs.console.behaviorAsLabels { 'moveToActiveSpace' }
-- Draw pretty rounded corners on all screens.
hs.loadSpoon('RoundedCorners')
spoon.RoundedCorners.radius = 12
spoon.RoundedCorners:start()
-- Automatically pause music when headphones are unplugged.
@@ -42,6 +43,13 @@ hostconfig:init()
local wm = require('window_management')
wm:init()
--------------------------------------------------------------------------------
-- Misc. Helpers
--------------------------------------------------------------------------------
local kd = require('kill_dock')
hs.hotkey.bind({ 'cmd', 'alt', 'ctrl' }, 'D', kd.killDock)
--------------------------------------------------------------------------------
-- The End
--------------------------------------------------------------------------------

18
hammerspoon/kill_dock.lua Normal file
View File

@@ -0,0 +1,18 @@
-- luacheck: read_globals hs
--- === kill_dock ===
---
--- Function to kill the Dock.
local obj = {}
--- kill_dock.killDock()
--- Function
--- Kills the Dock by executing `killall Dock`.
function obj.killDock()
hs.alert.show('Restarting Dock...')
hs.execute("killall Dock", true)
end
-- the end
return obj

143
hammerspoon/url_handler.lua Normal file
View File

@@ -0,0 +1,143 @@
-- luacheck: read_globals hs
--- === url_handler ===
---
--- URL handler for Hammerspoon. This module wraps the URLDispatcher spoon with
--- a number of extra helper function to make it easier to configure.
---
--- You will need to separately install the URLDispatcher spoon for this module
--- to work.
hs.loadSpoon('URLDispatcher')
local obj = {
_url_dispatcher = spoon.URLDispatcher
}
--- url_handler.default_handler
--- Variable
--- The default handler for URLs that don't match any of the patterns. If this
--- is not nil, it will be set as default_handler on the URLDispatcher spoon.
obj.default_handler = nil
--- url_handler.decode_slack_redir_urls
--- Variable
--- A boolean indicating whether to decode Slack redirect URLs. If this is not
--- nil, it will be set as decode_slack_redir_urls on the URLDispatcher spoon.
obj.decode_slack_redir_urls = nil
--- url_handler.url_patterns
--- Variable
--- A table of URL patterns to match against. If this is not nil, it will be set
--- as url_patterns on the URLDispatcher spoon.
obj.url_patterns = nil
--- url_handler.url_redir_decoders
--- Variable
--- A table of URL redirect decoders. If this is not nil, it will be set as
--- url_redir_decoders on the URLDispatcher spoon.
obj.url_redir_decoders = nil
--- url_handler.browsers
--- Variable
--- A table of browser names and corresponding bundle IDs that can be used in
--- the url_patterns table.
obj.browsers = {
arc = 'company.thebrowser.Browser',
brave = 'com.brave.Browser',
camino = 'org.mozilla.camino',
chrome = 'com.google.Chrome',
chromium = 'org.chromium.Chromium',
edge = 'com.microsoft.edgemac',
firefox = 'org.mozilla.firefox',
flock = 'com.flock.Flock',
icab = 'de.icab.iCab',
maxthon = 'com.maxthon.Maxthon',
omniweb = 'com.omnigroup.OmniWeb5',
opera = 'com.operasoftware.Opera',
orion = 'com.orionbrowser.orion',
palemoon = 'com.palemoon.palemoon',
safari = 'com.apple.Safari',
seamonkey = 'org.mozilla.seamonkey',
sigmaos = 'com.sigmaos.sigmaos.macos',
tor = 'org.mozilla.tor browser',
vivaldi = 'com.vivaldi.Vivaldi',
waterfox = 'net.waterfox.waterfox',
yandex = 'ru.yandex.desktop.yandex-browser',
}
--- url_handler.open(appID, url)
--- Function
--- Open url with the specified application bundle ID. This is a helper function
--- that's useful if you need to build custom functions to open URLs.
function obj.open(url, appID)
hs.application.launchOrFocusByBundleID(appID)
hs.urlevent.openURLWithBundle(url, appID)
end
--- url_handler.appID(appPath)
--- Function
--- Returns the bundle ID for the specified application path.
function obj.appID(appPath)
local info = hs.application.infoForBundlePath(appPath)
if info then
return info['CFBundleIdentifier']
end
end
function obj._chromiumProfile(app, profile)
return function(url)
hs.task.new("/usr/bin/open", nil, {
"-n",
"-a", app,
"--args",
"--profile-directory=" .. profile,
url
}):start()
end
end
--- url_handler.chromeProfile(profile)
--- Function
--- Returns a function that opens the specified URL in Google Chrome using the
--- given profile.
---
--- This assumes that Microsoft Edge is already installed and available to use.
function obj.chromeProfile(profile)
return obj._chromiumProfile("Google Chrome", profile)
end
--- url_handler.edgeProfile(profile)
--- Function
--- Returns a function that opens the specified URL in Microsoft Edge using the
--- given profile.
---
--- This assumes that Microsoft Edge is already installed and available to use.
function obj.edgeProfile(profile)
return obj._chromiumProfile("Microsoft Edge", profile)
end
--- url_handler.init()
--- Function
--- Initialize URL handler. This will set the default_handler, url_patterns,
--- and url_redir_decoders on the URLDispatcher spoon, followed by calling its
--- start() method.
function obj:init()
local keys = {
"default_handler",
"decode_slack_redir_urls",
"url_patterns",
"url_redir_decoders"
}
for _, key in ipairs(keys) do
if self[key] ~= nil then
self._url_dispatcher[key] = self[key]
end
end
self._url_dispatcher:start()
end
-- the end
return obj

View File

@@ -1,33 +1,37 @@
-- luacheck: read_globals hs
local mouse = require('hs.mouse')
local eventtap = require('hs.eventtap')
local grid = require('hs.grid')
local hotkey = require('hs.hotkey')
local mouse = require('hs.mouse')
local timer = require('hs.timer')
local window = require('hs.window')
-- configuration
-- Configuration
local wm = {
animationDuration = 0.0,
gridSizes = { default = '30x20', interactive = '8x4' },
gridTextSize = 50,
margins = { w = 4, h = 4 }
margins = { w = 0, h = 0 }
}
-- initialize and register keybindings
function wm:init ()
-- Initialize and register keybindings
function wm:init()
-- setup
local bind = require('hs.hotkey').bind
local bindAndRepeat = self.bindAndRepeat
hs.window.animationDuration = self.animationDuration
window.animationDuration = self.animationDuration
grid.setGrid(self.gridSizes.default)
grid.setMargins(self.margins)
grid.ui.textSize = self.gridTextSize
--
-- move and resize to preset grid locations
-- Move and resize to preset grid locations
--
-- show interactive grid menu
bind({'cmd', 'ctrl'}, '2',
-- Show interactive grid menu
bind({ 'cmd', 'ctrl' }, '§',
function()
grid.setGrid(self.gridSizes.interactive)
grid.show(
@@ -38,145 +42,148 @@ function wm:init ()
end
)
-- left half
bind({'cmd', 'ctrl'}, 'J', self.adjustWindow(0, 0, 15, 20))
-- right half
bind({'cmd', 'ctrl'}, 'L', self.adjustWindow(15, 0, 15, 20))
-- top half
bind({'cmd', 'ctrl'}, 'I', self.adjustWindow(0, 0, 30, 10))
-- bottom half
bind({'cmd', 'ctrl'}, 'K', self.adjustWindow(0, 10, 30, 10))
-- Left half
bind({ 'cmd', 'ctrl' }, 'J', self.adjustWindow(0, 0, 15, 20))
-- Right half
bind({ 'cmd', 'ctrl' }, 'L', self.adjustWindow(15, 0, 15, 20))
-- Top half
bind({ 'cmd', 'ctrl' }, 'I', self.adjustWindow(0, 0, 30, 10))
-- Bottom half
bind({ 'cmd', 'ctrl' }, 'K', self.adjustWindow(0, 10, 30, 10))
-- left narrow
bind({'ctrl', 'alt'}, 'U', self.adjustWindow(0, 0, 12, 20))
-- right narrow
bind({'ctrl', 'alt'}, 'O', self.adjustWindow(18, 0, 12, 20))
-- Left narrow
bind({ 'ctrl', 'alt' }, 'U', self.adjustWindow(0, 0, 12, 20))
-- Left super narrow
bind({ 'cmd', 'ctrl', 'alt' }, 'U', self.adjustWindow(0, 0, 9, 20))
-- Right narrow
bind({ 'ctrl', 'alt' }, 'O', self.adjustWindow(18, 0, 12, 20))
-- Right super narrow
bind({ 'cmd', 'ctrl', 'alt' }, 'O', self.adjustWindow(21, 0, 9, 20))
-- left wide
bind({'cmd', 'ctrl'}, 'U', self.adjustWindow(0, 0, 18, 20))
-- right wide
bind({'cmd', 'ctrl'}, 'O', self.adjustWindow(12, 0, 18, 20))
-- Left wide
bind({ 'cmd', 'ctrl' }, 'U', self.adjustWindow(0, 0, 18, 20))
-- Right wide
bind({ 'cmd', 'ctrl' }, 'O', self.adjustWindow(12, 0, 18, 20))
-- center super narrow
bind({'cmd', 'ctrl', 'alt'}, '\\', self.adjustWindow(10, 0, 10, 20))
-- center narrow small
bind({'ctrl', 'alt'}, '\\', self.adjustWindow(9, 0, 12, 20))
-- center narrow
bind({'cmd', 'ctrl'}, '\\', self.adjustWindow(7, 0, 16, 20))
-- Center super narrow
bind({ 'cmd', 'ctrl', 'alt' }, '\\', self.adjustWindow(10, 0, 10, 20))
-- Center narrow small
bind({ 'ctrl', 'alt' }, '\\', self.adjustWindow(9, 0, 12, 20))
-- Center narrow
bind({ 'cmd', 'ctrl' }, '\\', self.adjustWindow(7, 0, 16, 20))
-- center medium small
bind({'ctrl', 'alt'}, '\'', self.adjustWindow(6, 0, 18, 20))
-- center medium
bind({'cmd', 'ctrl'}, '\'', self.adjustWindow(5, 0, 20, 20))
-- Center medium small
bind({ 'ctrl', 'alt' }, '\'', self.adjustWindow(6, 0, 18, 20))
-- Center medium
bind({ 'cmd', 'ctrl' }, '\'', self.adjustWindow(5, 0, 20, 20))
-- center wide small
bind({'ctrl', 'alt'}, ';', self.adjustWindow(4, 0, 22, 20))
-- center wide
bind({'cmd', 'ctrl'}, ';', self.adjustWindow(3, 0, 24, 20))
-- Center wide small
bind({ 'ctrl', 'alt' }, ';', self.adjustWindow(4, 0, 22, 20))
-- Center wide
bind({ 'cmd', 'ctrl' }, ';', self.adjustWindow(3, 0, 24, 20))
-- maximized
bind({'cmd', 'ctrl'}, 'H', grid.maximizeWindow)
-- Maximized
bind({ 'cmd', 'ctrl' }, 'H', grid.maximizeWindow)
--
-- move and resize windows
-- Move and resize windows
--
bind({'cmd', 'ctrl', 'alt'}, 'F', self.resizeWindow(770, 634))
bind({'cmd', 'ctrl', 'alt'}, 'X', self.adjustWindow(0, 3, 10, 14))
bind({ 'cmd', 'ctrl', 'alt' }, 'F', self.resizeWindow(900, 642))
bind({ 'cmd', 'ctrl', 'alt' }, 'X', self.adjustWindow(0, 3, 10, 14))
-- resize windows
bindAndRepeat({'cmd', 'ctrl', 'alt'}, 'J', self.resizeWindowOnGrid(-1, 0))
bindAndRepeat({'cmd', 'ctrl', 'alt'}, 'L', self.resizeWindowOnGrid(1, 0))
bindAndRepeat({'cmd', 'ctrl', 'alt'}, 'I', self.resizeWindowOnGrid(0, -1))
bindAndRepeat({'cmd', 'ctrl', 'alt'}, 'K', self.resizeWindowOnGrid(0, 1))
-- Resize windows
bindAndRepeat({ 'cmd', 'ctrl', 'alt' }, 'J', self.resizeWindowOnGrid(-1, 0))
bindAndRepeat({ 'cmd', 'ctrl', 'alt' }, 'L', self.resizeWindowOnGrid(1, 0))
bindAndRepeat({ 'cmd', 'ctrl', 'alt' }, 'I', self.resizeWindowOnGrid(0, -1))
bindAndRepeat({ 'cmd', 'ctrl', 'alt' }, 'K', self.resizeWindowOnGrid(0, 1))
-- move window relative
bindAndRepeat({'ctrl', 'alt'}, 'J', self.moveWindowOnGrid(-1, 0))
bindAndRepeat({'ctrl', 'alt'}, 'L', self.moveWindowOnGrid(1, 0))
bindAndRepeat({'ctrl', 'alt'}, 'I', self.moveWindowOnGrid(0, -1))
bindAndRepeat({'ctrl', 'alt'}, 'K', self.moveWindowOnGrid(0, 1))
-- Move window relative
bindAndRepeat({ 'ctrl', 'alt' }, 'J', self.moveWindowOnGrid(-1, 0))
bindAndRepeat({ 'ctrl', 'alt' }, 'L', self.moveWindowOnGrid(1, 0))
bindAndRepeat({ 'ctrl', 'alt' }, 'I', self.moveWindowOnGrid(0, -1))
bindAndRepeat({ 'ctrl', 'alt' }, 'K', self.moveWindowOnGrid(0, 1))
-- enlarge horizontally
bindAndRepeat({'cmd', 'ctrl', 'shift'}, '\\',
-- Enlarge horizontally
bindAndRepeat({ 'cmd', 'ctrl', 'shift' }, '\\',
self.resizeWindowOnGridSymmetrically(1, 0))
-- shrink horizontally
bindAndRepeat({'cmd', 'ctrl', 'shift'}, '\'',
-- Shrink horizontally
bindAndRepeat({ 'cmd', 'ctrl', 'shift' }, '\'',
self.resizeWindowOnGridSymmetrically(-1, 0))
--
-- move windows between spaces
-- Move windows between spaces
--
bind({'ctrl', 'alt'}, 'left', self.moveWindowToSpace('left'))
bind({'ctrl', 'alt'}, 'right', self.moveWindowToSpace('right'))
bind({'ctrl', 'alt'}, '1', self.moveWindowToSpace('1'))
bind({'ctrl', 'alt'}, '2', self.moveWindowToSpace('2'))
bind({'ctrl', 'alt'}, '3', self.moveWindowToSpace('3'))
bind({'ctrl', 'alt'}, '4', self.moveWindowToSpace('4'))
bind({'ctrl', 'alt'}, '5', self.moveWindowToSpace('5'))
bind({'ctrl', 'alt'}, '6', self.moveWindowToSpace('6'))
bind({'ctrl', 'alt'}, '7', self.moveWindowToSpace('7'))
bind({'ctrl', 'alt'}, '8', self.moveWindowToSpace('8'))
bind({'ctrl', 'alt'}, '9', self.moveWindowToSpace('9'))
bind({'ctrl', 'alt'}, '0', self.moveWindowToSpace('0'))
bind({ 'ctrl', 'alt' }, 'left', self.moveWindowToSpace('left'))
bind({ 'ctrl', 'alt' }, 'right', self.moveWindowToSpace('right'))
bind({ 'ctrl', 'alt' }, '1', self.moveWindowToSpace('1'))
bind({ 'ctrl', 'alt' }, '2', self.moveWindowToSpace('2'))
bind({ 'ctrl', 'alt' }, '3', self.moveWindowToSpace('3'))
bind({ 'ctrl', 'alt' }, '4', self.moveWindowToSpace('4'))
bind({ 'ctrl', 'alt' }, '5', self.moveWindowToSpace('5'))
bind({ 'ctrl', 'alt' }, '6', self.moveWindowToSpace('6'))
bind({ 'ctrl', 'alt' }, '7', self.moveWindowToSpace('7'))
bind({ 'ctrl', 'alt' }, '8', self.moveWindowToSpace('8'))
bind({ 'ctrl', 'alt' }, '9', self.moveWindowToSpace('9'))
bind({ 'ctrl', 'alt' }, '0', self.moveWindowToSpace('0'))
--
-- move windows between displays
-- Move windows between displays
--
-- move to screen to the left
bind({'cmd', 'ctrl'}, ',',
function ()
local win = hs.window.focusedWindow()
-- Move to screen to the left
bind({ 'cmd', 'ctrl' }, ',',
function()
local win = window.focusedWindow()
win:moveOneScreenWest()
grid.snap(win)
end
)
-- move to screen to the right
bind({'cmd', 'ctrl'}, '.',
function ()
local win = hs.window.focusedWindow()
-- Move to screen to the right
bind({ 'cmd', 'ctrl' }, '.',
function()
local win = window.focusedWindow()
win:moveOneScreenEast()
grid.snap(win)
end
)
-- move to screen above
bind({'cmd', 'ctrl', 'alt'}, '.',
function ()
local win = hs.window.focusedWindow()
-- Move to screen above
bind({ 'cmd', 'ctrl', 'alt' }, '.',
function()
local win = window.focusedWindow()
win:moveOneScreenNorth()
grid.snap(win)
end
)
-- move to screen bellow
bind({'cmd', 'ctrl', 'alt'}, ',',
function ()
local win = hs.window.focusedWindow()
-- Move to screen below
bind({ 'cmd', 'ctrl', 'alt' }, ',',
function()
local win = window.focusedWindow()
win:moveOneScreenSouth()
grid.snap(win)
end
)
end
--
-- private methods
-- Private methods
--
wm.bindAndRepeat = function (mod, key, fn)
hs.hotkey.bind(mod, key, fn, nil, fn)
wm.bindAndRepeat = function(mod, key, fn)
hotkey.bind(mod, key, fn, nil, fn)
end
wm.adjustWindow = function (x, y, w, h)
return function ()
wm.adjustWindow = function(x, y, w, h)
return function()
grid.adjustWindow(
function (cell)
function(cell)
cell.x = x
cell.y = y
cell.w = w
@@ -186,9 +193,9 @@ wm.adjustWindow = function (x, y, w, h)
end
end
wm.resizeWindow = function (w, h)
return function ()
local win = hs.window.focusedWindow()
wm.resizeWindow = function(w, h)
return function()
local win = window.focusedWindow()
local f = win:frame()
f.w = w
@@ -198,8 +205,8 @@ wm.resizeWindow = function (w, h)
end
wm.moveWindow = function(x, y)
return function ()
local win = hs.window.focusedWindow()
return function()
local win = window.focusedWindow()
local f = win:frame()
f.x = x
@@ -208,9 +215,9 @@ wm.moveWindow = function(x, y)
end
end
wm.moveWindowRelative = function (x, y)
return function ()
local win = hs.window.focusedWindow()
wm.moveWindowRelative = function(x, y)
return function()
local win = window.focusedWindow()
local f = win:frame()
f.x = f.x + x
@@ -219,10 +226,10 @@ wm.moveWindowRelative = function (x, y)
end
end
wm.moveWindowOnGrid = function (x, y)
return function ()
wm.moveWindowOnGrid = function(x, y)
return function()
grid.adjustWindow(
function (cell)
function(cell)
local max = grid.getGrid()
if ((cell.x + x) + cell.w) <= max.w then
@@ -237,10 +244,10 @@ wm.moveWindowOnGrid = function (x, y)
end
end
wm.resizeWindowOnGrid = function (w, h)
return function ()
wm.resizeWindowOnGrid = function(w, h)
return function()
grid.adjustWindow(
function (cell)
function(cell)
local max = grid.getGrid()
if cell.x == 0 and cell.w == max.w then
@@ -281,10 +288,10 @@ wm.resizeWindowOnGrid = function (w, h)
end
end
wm.resizeWindowOnGridSymmetrically = function (w, h)
return function ()
wm.resizeWindowOnGridSymmetrically = function(w, h)
return function()
grid.adjustWindow(
function (cell)
function(cell)
local max = grid.getGrid()
if w ~= 0 and cell.w + (w * 2) >= 2 then
@@ -316,45 +323,43 @@ end
-- Requires ctrl+<left>/<right> and ctrl+<num> system keybindings, originally
-- from:
-- https://github.com/Hammerspoon/hammerspoon/issues/235#issuecomment-101069303
wm.moveWindowToSpace = function (direction)
wm.moveWindowToSpace = function(direction)
return function()
local mouseOrigin = mouse.absolutePosition()
local win = hs.window.focusedWindow()
local app = win:application()
local win = window.focusedWindow()
local clickPoint = win:zoomButtonRect()
-- click and hold next to the zoom button close to the top of the window
clickPoint.x = clickPoint.x + clickPoint.w + 5
clickPoint.x = clickPoint.x + clickPoint.w + 2
clickPoint.y = win:frame().y + 7
local mouseClickEvent = hs.eventtap.event.newMouseEvent(
hs.eventtap.event.types.leftMouseDown, clickPoint
local mouseClickEvent = eventtap.event.newMouseEvent(
eventtap.event.types.leftMouseDown, clickPoint
)
mouseClickEvent:post()
hs.timer.usleep(150000)
timer.usleep(150000)
local nextSpaceDownEvent = hs.eventtap.event.newKeyEvent(
{"ctrl"}, direction, true
local nextSpaceDownEvent = eventtap.event.newKeyEvent(
{ "ctrl" }, direction, true
)
nextSpaceDownEvent:post()
hs.timer.usleep(150000)
timer.usleep(150000)
local nextSpaceUpEvent = hs.eventtap.event.newKeyEvent(
{"ctrl"}, direction, false
local nextSpaceUpEvent = eventtap.event.newKeyEvent(
{ "ctrl" }, direction, false
)
nextSpaceUpEvent:post()
hs.timer.usleep(150000)
timer.usleep(150000)
local mouseReleaseEvent = hs.eventtap.event.newMouseEvent(
hs.eventtap.event.types.leftMouseUp, clickPoint
local mouseReleaseEvent = eventtap.event.newMouseEvent(
eventtap.event.types.leftMouseUp, clickPoint
)
mouseReleaseEvent:post()
hs.timer.usleep(150000)
timer.usleep(150000)
mouse.setAbsolutePosition(mouseOrigin)
mouse.absolutePosition(mouseOrigin)
end
end
-- the end
return wm

View File

@@ -11,30 +11,41 @@ PRIVATE_PATH="private"
SYMLINKS=(
Brewfile
ackrc
alacritty.yml
alacritty.toml
bitbar
coffeelint.json
config/kitty/kitty.conf
config/ghostty
config/kitty
config/mise/config.toml
config/mise/settings.toml
config/nix/nix.conf
config/solargraph/config.yml
config/starship.toml
config/tlrc/config.toml
config/xkeysnail/config.py
config/k9s
erlang
eslintrc.js
gemrc
gitattributes
gitconfig
gitignore
hammerspoon
hgrc
hyper.js
irbrc
logrotate.d
markdownlint.yaml
peco
powconfig
prettierrc.js
pryrc
reek.yml
rspec
rubocop.yml
rustfmt.toml
tmux
tmux.conf
warp
zprofile
zshenv
zshrc
)
@@ -76,6 +87,11 @@ install_private() {
"$ROOT_PATH/$PRIVATE_PATH"
}
install_agentic() {
git_clone "git@github.com:jimeh/agentic.git" \
"$HOME/.config/agentic"
}
install_launch_agents() {
mkdir -p "$HOME/Library/LaunchAgents"
for file in $ROOT_PATH/launch_agents/*.plist; do
@@ -117,6 +133,10 @@ install_emacs_config() {
git_clone 'git@github.com:jimeh/.emacs.d.git' "$TARGET/.config/emacs-siren"
}
install_rustup() {
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
}
#
# Helper functions
#
@@ -207,6 +227,7 @@ dot_symlink() {
done
fi
mkdir -p "$(dirname "$target")"
symlink "$source" "$target"
}
@@ -240,9 +261,11 @@ display_help() {
echo ' info: Display target and source directory information.'
echo ' emacs_config: Install Emacs configuration.'
echo ' private: Install private dotfiles.'
echo ' agentic: Clone agentic repo to ~/.config/agentic.'
echo ' homebrew: Install Homebrew (Mac OS X only).'
echo ' rbenv: Install rbenv, a Ruby version manager.'
echo ' launch_agents: Install launchd plists to ~/Library/LaunchAgents/'
echo ' terminfo: Install terminfo.'
echo ' help: Display this message.'
}
@@ -261,12 +284,18 @@ case "$1" in
private)
install_private
;;
agentic)
install_agentic
;;
homebrew | brew)
install_homebrew
;;
rbenv)
install_rbenv
;;
rustup | rust)
install_rustup
;;
launch_agents | launch-agents | agents)
install_launch_agents
;;

6
irbrc
View File

@@ -1,4 +1,4 @@
require 'irb/completion'
require 'irb/ext/save-history'
IRB.conf[:SAVE_HISTORY] = 1000
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-history"
IRB.conf[:USE_AUTOCOMPLETE] = false
IRB.conf[:USE_MULTILINE] = false if ENV['INSIDE_EMACS']
IRB.conf[:USE_READLINE] = false if ENV['INSIDE_EMACS']

View File

@@ -0,0 +1,835 @@
{
"description": "Emacs key bindings [control+keys] (rev 11) [custom]",
"manipulators": [
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^com\\.mitchellh\\.ghostty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$",
"^com\\.todesktop\\.",
"^dev\\.warp\\.Warp"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "d",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"option"
]
}
},
"to": [
{
"key_code": "delete_forward"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^com\\.mitchellh\\.ghostty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$",
"^com\\.todesktop\\.",
"^dev\\.warp\\.Warp"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "h",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"option"
]
}
},
"to": [
{
"key_code": "delete_or_backspace"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^com\\.mitchellh\\.ghostty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$",
"^com\\.todesktop\\.",
"^dev\\.warp\\.Warp"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "i",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "tab"
}
],
"type": "basic"
},
{
"conditions": [
{
"keyboard_types": [
"ansi",
"iso"
],
"type": "keyboard_type_if"
}
],
"from": {
"key_code": "open_bracket",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock"
]
}
},
"to": [
{
"key_code": "escape"
}
],
"type": "basic"
},
{
"conditions": [
{
"keyboard_types": [
"jis"
],
"type": "keyboard_type_if"
}
],
"from": {
"key_code": "close_bracket",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock"
]
}
},
"to": [
{
"key_code": "escape"
}
],
"type": "basic"
},
{
"from": {
"key_code": "m",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift",
"option"
]
}
},
"to": [
{
"key_code": "return_or_enter"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^com\\.mitchellh\\.ghostty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$",
"^com\\.todesktop\\.",
"^dev\\.warp\\.Warp"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "b",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift",
"option"
]
}
},
"to": [
{
"key_code": "left_arrow"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^com\\.mitchellh\\.ghostty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$",
"^com\\.todesktop\\.",
"^dev\\.warp\\.Warp"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "f",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift",
"option"
]
}
},
"to": [
{
"key_code": "right_arrow"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^com\\.mitchellh\\.ghostty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$",
"^com\\.todesktop\\.",
"^dev\\.warp\\.Warp"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "n",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift",
"option"
]
}
},
"to": [
{
"key_code": "down_arrow"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^com\\.mitchellh\\.ghostty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$",
"^com\\.todesktop\\.",
"^dev\\.warp\\.Warp"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "p",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift",
"option"
]
}
},
"to": [
{
"key_code": "up_arrow"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^com\\.mitchellh\\.ghostty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$",
"^com\\.todesktop\\.",
"^dev\\.warp\\.Warp"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "v",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "page_down"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.microsoft\\.Excel$",
"^com\\.microsoft\\.Powerpoint$",
"^com\\.microsoft\\.Word$"
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "a",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "home"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.microsoft\\.Excel$",
"^com\\.microsoft\\.Powerpoint$",
"^com\\.microsoft\\.Word$"
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "e",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "end"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.microsoft\\.Excel$",
"^com\\.microsoft\\.Powerpoint$",
"^com\\.microsoft\\.Word$"
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "k",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "end",
"modifiers": [
"left_shift"
]
},
{
"key_code": "delete_forward"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.eclipse\\.platform\\.ide$"
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "a",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "left_arrow",
"modifiers": [
"left_command"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.eclipse\\.platform\\.ide$"
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "e",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "right_arrow",
"modifiers": [
"left_command"
]
}
],
"type": "basic"
}
]
}

View File

@@ -0,0 +1,75 @@
{
"description": "Emacs style ctrl+g to esc (rev 1)",
"manipulators": [
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "g",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock"
]
}
},
"to": [
{
"key_code": "escape"
}
],
"type": "basic"
}
]
}

View File

@@ -0,0 +1,309 @@
{
"description": "Emacs key bindings [option+keys] (rev 5) [custom]",
"manipulators": [
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^com\\.mitchellh\\.ghostty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$",
"^com\\.todesktop\\.",
"^dev\\.warp\\.Warp"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "v",
"modifiers": {
"mandatory": [
"option"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "page_up"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^com\\.mitchellh\\.ghostty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$",
"^com\\.todesktop\\.",
"^dev\\.warp\\.Warp"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "b",
"modifiers": {
"mandatory": [
"option"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "left_arrow",
"modifiers": [
"left_option"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^com\\.mitchellh\\.ghostty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$",
"^com\\.todesktop\\.",
"^dev\\.warp\\.Warp"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "f",
"modifiers": {
"mandatory": [
"option"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "right_arrow",
"modifiers": [
"left_option"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^org\\.gnu\\.Emacs$",
"^org\\.gnu\\.AquamacsEmacs$",
"^org\\.gnu\\.Aquamacs$",
"^org\\.pqrs\\.unknownapp\\.conkeror$",
"^com\\.microsoft\\.rdc$",
"^com\\.microsoft\\.rdc\\.",
"^net\\.sf\\.cord$",
"^com\\.thinomenon\\.RemoteDesktopConnection$",
"^com\\.itap-mobile\\.qmote$",
"^com\\.nulana\\.remotixmac$",
"^com\\.p5sys\\.jump\\.mac\\.viewer$",
"^com\\.p5sys\\.jump\\.mac\\.viewer\\.",
"^com\\.teamviewer\\.TeamViewer$",
"^com\\.vmware\\.horizon$",
"^com\\.2X\\.Client\\.Mac$",
"^com\\.OpenText\\.Exceed-TurboX-Client$",
"^com\\.realvnc\\.vncviewer$",
"^com\\.citrix\\.receiver\\.icaviewer",
"^com\\.apple\\.Terminal$",
"^com\\.googlecode\\.iterm2$",
"^co\\.zeit\\.hyperterm$",
"^co\\.zeit\\.hyper$",
"^io\\.alacritty$",
"^org\\.alacritty$",
"^net\\.kovidgoyal\\.kitty$",
"^com\\.mitchellh\\.ghostty$",
"^org\\.vim\\.",
"^com\\.qvacua\\.VimR$",
"^com\\.vmware\\.fusion$",
"^com\\.vmware\\.horizon$",
"^com\\.vmware\\.view$",
"^com\\.parallels\\.desktop$",
"^com\\.parallels\\.vm$",
"^com\\.parallels\\.desktop\\.console$",
"^org\\.virtualbox\\.app\\.VirtualBoxVM$",
"^com\\.citrix\\.XenAppViewer$",
"^com\\.vmware\\.proxyApp\\.",
"^com\\.parallels\\.winapp\\.",
"^com\\.utmapp\\.UTM$",
"^org\\.x\\.X11$",
"^com\\.apple\\.x11$",
"^org\\.macosforge\\.xquartz\\.X11$",
"^org\\.macports\\.X11$",
"^com\\.sublimetext\\.",
"^com\\.microsoft\\.VSCode$",
"^com\\.todesktop\\.",
"^dev\\.warp\\.Warp"
],
"type": "frontmost_application_unless"
}
],
"from": {
"key_code": "d",
"modifiers": {
"mandatory": [
"option"
],
"optional": [
"caps_lock"
]
}
},
"to": [
{
"key_code": "delete_forward",
"modifiers": [
"left_option"
]
}
],
"type": "basic"
}
]
}

View File

@@ -0,0 +1,489 @@
{
"description": "VSCode keybinding workarounds",
"manipulators": [
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "i",
"modifiers": {
"mandatory": [
"option"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "up_arrow",
"modifiers": [
"left_option",
"left_command"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "n",
"modifiers": {
"mandatory": [
"option"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "down_arrow",
"modifiers": [
"left_option",
"left_command"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "e",
"modifiers": {
"mandatory": [
"option"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "right_arrow",
"modifiers": [
"left_option",
"left_command"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "f",
"modifiers": {
"mandatory": [
"option"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "right_arrow",
"modifiers": [
"left_option"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "b",
"modifiers": {
"mandatory": [
"option"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "left_arrow",
"modifiers": [
"left_option"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "d",
"modifiers": {
"mandatory": [
"option"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "delete_forward",
"modifiers": [
"left_option"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "hyphen",
"modifiers": {
"mandatory": [
"option"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "z",
"modifiers": [
"left_command"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "hyphen",
"modifiers": {
"mandatory": [
"option",
"shift"
],
"optional": [
"caps_lock"
]
}
},
"to": [
{
"key_code": "z",
"modifiers": [
"left_command",
"left_shift"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "d",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "delete_forward"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "p",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "up_arrow"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "n",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "down_arrow"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "b",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "left_arrow"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "f",
"modifiers": {
"mandatory": [
"control"
],
"optional": [
"caps_lock",
"shift"
]
}
},
"to": [
{
"key_code": "right_arrow"
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "comma",
"modifiers": {
"mandatory": [
"option",
"shift"
],
"optional": [
"caps_lock"
]
}
},
"to": [
{
"key_code": "up_arrow",
"modifiers": [
"left_command"
]
}
],
"type": "basic"
},
{
"conditions": [
{
"bundle_identifiers": [
"^com\\.exafunction\\.windsurf",
"^com\\.microsoft\\.VSCode",
"^com\\.todesktop\\."
],
"type": "frontmost_application_if"
}
],
"from": {
"key_code": "period",
"modifiers": {
"mandatory": [
"option",
"shift"
],
"optional": [
"caps_lock"
]
}
},
"to": [
{
"key_code": "down_arrow",
"modifiers": [
"left_command"
]
}
],
"type": "basic"
}
]
}

View File

@@ -17,7 +17,7 @@
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin</string>
<string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin:/opt/homebrew/bin:/opt/homebrew/sbin</string>
</dict>
<key>StartCalendarInterval</key>
<dict>

View File

@@ -10,14 +10,14 @@
<array>
<string>sh</string>
<string>-c</string>
<string>$HOME/.dotfiles/bin/macos_battery_exporter > $HOME/.node_metrics/battery.prom</string>
<string>macos-battery-exporter -n node -o $HOME/.node_metrics/battery.prom</string>
</array>
<key>RunAtLoad</key>
<false/>
<true/>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin</string>
<string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin:/opt/homebrew/bin:/opt/homebrew/sbin</string>
</dict>
<key>StartInterval</key>
<integer>30</integer>

1
logrotate.d/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.conf

View File

@@ -0,0 +1,9 @@
$(find "$(brew --prefix)/var/log" -name '*.log' -exec echo '"{}"' \;) {
daily
size 5115k
missingok
rotate 7
compress
copytruncate
notifempty
}

View File

@@ -0,0 +1,9 @@
$(find ~/Projects -name '*.log' -path '*/log/*' ! -path '*/vendor/bundle/ruby/*' ! -path '*/node_modules/*' -exec echo '"{}"' \;) {
daily
size 5115k
missingok
rotate 7
compress
copytruncate
notifempty
}

20
markdownlint.yaml Normal file
View File

@@ -0,0 +1,20 @@
---
# yaml-language-server: $schema=https://raw.githubusercontent.com/DavidAnson/markdownlint/main/schema/markdownlint-config-schema.json
no-hard-tabs: # MD010
# Allow hard-tabs in code blocks in the below languages.
ignore_code_languages:
- caddyfile
- go
- makefile
blanks-around-fences: # MD031
# Don't force blank lines around code blocks in lists.
list_items: false
line-length: # MD013
# Allow lines up to 120 characters.
line_length: 80
code_blocks: true
ignore_code_blocks: true
code_block_line_length: 280
tables: false

View File

@@ -20,7 +20,7 @@
domain: com.apple.dock
key: largesize
type: float
value: 70
value: 65
notify: restart Dock
- name: "Dock: Do not show recent apps"
osx_defaults:

View File

@@ -1,3 +1,3 @@
export POW_DST_PORT=88
export HOME=~
export PATH="$HOME/.rbenv/shims:$HOME/.rbenv/bin:/usr/local/bin:$PATH"
export PATH="$HOME/.rbenv/shims:$HOME/.rbenv/bin:/usr/local/bin:/opt/homebrew/bin:$PATH"

View File

@@ -1,50 +0,0 @@
const homedir = require('os').homedir();
const fs = require("fs");
function voltaPrettierSearchDirs(voltaDir) {
const packagesDir = `${voltaDir}/tools/image/packages`;
let paths = []
let parents = [];
fs.readdirSync(packagesDir).forEach((item) => {
if (/^prettier-plugin-[^/]+$/.test(item)) {
paths.push(`${packagesDir}/${item}/lib`);
}
if (item == '@prettier') {
paths = paths.concat(
findDirs(`${packagesDir}/@prettier`, /^plugin-[^/]+$/, "lib")
);
} else if (/^@[^/]+$/.test(item)) {
parents.push(`${packagesDir}/${item}`);
}
return [];
})
parents.forEach((parent) => {
paths = paths.concat(findDirs(parent, /^prettier-plugin-[^/]+$/, "lib"));
})
return paths;
}
function findDirs(parent, pattern, suffix) {
return fs.readdirSync(parent).flatMap((item) => {
const fp = `${parent}/${item}`;
if (pattern.test(item) && fs.statSync(fp).isDirectory()) {
if (suffix) {
return `${fp}/${suffix}`;
}
return fp;
}
return [];
});
}
module.exports = {
pluginSearchDirs: voltaPrettierSearchDirs(`${homedir}/.volta`),
};

Some files were not shown because too many files have changed in this diff Show More