diff --git a/config/nvim/lua/plugins/ui.lua b/config/nvim/lua/plugins/ui.lua index c8098cc..94d202a 100644 --- a/config/nvim/lua/plugins/ui.lua +++ b/config/nvim/lua/plugins/ui.lua @@ -1,74 +1,49 @@ return { - { - 'harivansh-afk/cozybox.nvim', - lazy = false, - priority = 1000, - config = function() - local function apply_cozybox_overrides() - local links = { - { 'DiffsAdd', 'DiffAdd' }, - { 'DiffsDelete', 'DiffDelete' }, - { 'DiffsChange', 'DiffChange' }, - { 'DiffsText', 'DiffText' }, - { 'DiffsClear', 'Normal' }, - } - for _, pair in ipairs(links) do - vim.api.nvim_set_hl(0, pair[1], { link = pair[2], default = false }) - end - end - - vim.api.nvim_create_augroup('cozybox_fallback_highlights', { clear = true }) - vim.api.nvim_create_autocmd('ColorScheme', { - group = 'cozybox_fallback_highlights', - callback = function() - if vim.g.colors_name == 'cozybox' then - apply_cozybox_overrides() - end - end, - }) - - vim.cmd.colorscheme('cozybox') - apply_cozybox_overrides() - end, - }, - { - 'nvim-lualine/lualine.nvim', - dependencies = { 'nvim-tree/nvim-web-devicons' }, - config = function() - local theme = { - normal = { - a = { gui = 'bold' }, - }, - visual = { - a = { gui = 'bold' }, - }, - replace = { - a = { gui = 'bold' }, - }, - command = { - a = { gui = 'bold' }, - }, - } - require('lualine').setup({ - options = { - icons_enabled = false, - component_separators = '', - section_separators = { left = '', right = '' }, - theme = theme, - }, - sections = { - lualine_a = { 'mode' }, - lualine_b = { 'FugitiveHead', 'diff' }, - lualine_c = { { 'filename', path = 0 } }, - lualine_x = { 'diagnostics' }, - lualine_y = { 'filetype' }, - lualine_z = { 'progress' }, - }, - }) - end, - }, - { - 'barrettruth/nonicons.nvim', - dependencies = { 'nvim-tree/nvim-web-devicons' }, - }, + { + dir = vim.fn.expand "~/Documents/GitHub/cozybox.nvim", + name = "cozybox.nvim", + lazy = false, + priority = 1000, + config = function() require("theme").setup() end, + }, + { + "nvim-lualine/lualine.nvim", + dependencies = { "nvim-tree/nvim-web-devicons" }, + config = function() + local theme = { + normal = { + a = { gui = "bold" }, + }, + visual = { + a = { gui = "bold" }, + }, + replace = { + a = { gui = "bold" }, + }, + command = { + a = { gui = "bold" }, + }, + } + require("lualine").setup { + options = { + icons_enabled = false, + component_separators = "", + section_separators = { left = "", right = "" }, + theme = theme, + }, + sections = { + lualine_a = { "mode" }, + lualine_b = { "FugitiveHead", "diff" }, + lualine_c = { { "filename", path = 0 } }, + lualine_x = { "diagnostics" }, + lualine_y = { "filetype" }, + lualine_z = { "progress" }, + }, + } + end, + }, + { + "barrettruth/nonicons.nvim", + dependencies = { "nvim-tree/nvim-web-devicons" }, + }, } diff --git a/config/nvim/lua/theme.lua b/config/nvim/lua/theme.lua new file mode 100644 index 0000000..8f90d8c --- /dev/null +++ b/config/nvim/lua/theme.lua @@ -0,0 +1,88 @@ +local M = {} + +local theme_state_file = vim.fn.stdpath "state" .. "/theme/current" +local active_schemes = { + cozybox = true, + ["cozybox-light"] = true, +} + +local function ensure_server_socket() + if vim.v.servername ~= nil and vim.v.servername ~= "" then + return + end + + local socket_path = ("/tmp/nvim-%d.sock"):format(vim.fn.getpid()) + vim.fn.serverstart(socket_path) +end + +local function apply_cozybox_overrides() + local links = { + { "DiffsAdd", "DiffAdd" }, + { "DiffsDelete", "DiffDelete" }, + { "DiffsChange", "DiffChange" }, + { "DiffsText", "DiffText" }, + { "DiffsClear", "Normal" }, + } + + for _, pair in ipairs(links) do + vim.api.nvim_set_hl(0, pair[1], { link = pair[2], default = false }) + end +end + +local function read_mode() + local ok, lines = pcall(vim.fn.readfile, theme_state_file) + if not ok or not lines or not lines[1] then return "dark" end + + local mode = vim.trim(lines[1]) + if mode == "light" then return "light" end + + return "dark" +end + +local function colorscheme_for_mode(mode) + if mode == "light" then return "cozybox-light" end + + return "cozybox" +end + +function M.apply(mode) + local next_mode = mode or read_mode() + local next_scheme = colorscheme_for_mode(next_mode) + + if vim.o.background ~= next_mode then vim.o.background = next_mode end + + if vim.g.cozybox_theme_mode ~= next_mode or vim.g.colors_name ~= next_scheme then vim.cmd.colorscheme(next_scheme) end + + vim.g.cozybox_theme_mode = next_mode + apply_cozybox_overrides() +end + +function M.setup() + local group = vim.api.nvim_create_augroup("cozybox_theme_sync", { clear = true }) + + ensure_server_socket() + + vim.api.nvim_create_autocmd("ColorScheme", { + group = group, + callback = function() + if active_schemes[vim.g.colors_name] then apply_cozybox_overrides() end + end, + }) + + vim.api.nvim_create_autocmd({ "VimEnter", "FocusGained" }, { + group = group, + callback = function() M.apply() end, + }) + + vim.api.nvim_create_user_command("ThemeSync", function(opts) + local mode = opts.args ~= "" and opts.args or nil + M.apply(mode) + end, { + nargs = "?", + complete = function() return { "dark", "light" } end, + }) + + M.apply() +end + +return M diff --git a/home/default.nix b/home/default.nix index cada377..542f450 100644 --- a/home/default.nix +++ b/home/default.nix @@ -13,6 +13,7 @@ ./migration.nix ./nvim.nix ./rectangle.nix + ./scripts.nix ./tmux.nix ./zsh.nix ]; diff --git a/home/ghostty.nix b/home/ghostty.nix index c2a9e50..150f608 100644 --- a/home/ghostty.nix +++ b/home/ghostty.nix @@ -1,11 +1,11 @@ -{ pkgs, ... }: -let +{ + config, + pkgs, + ... +}: let + theme = import ../lib/theme.nix {inherit config;}; ghosttyConfig = '' - theme = "Gruvbox Material Dark" - background = #181818 - cursor-color = #ddc7a1 - selection-background = #504945 - selection-foreground = #ebdbb2 + theme = "dark:cozybox-dark,light:cozybox-light" font-family = Berkeley Mono font-codepoint-map = U+f101-U+f25c=nonicons background-opacity = 1 @@ -38,12 +38,6 @@ let keybind = vim/i=deactivate_key_table keybind = vim/catch_all=ignore mouse-hide-while-typing = true - palette = 2=#8ec97c - palette = 10=#8ec97c - palette = 4=#4672d4 - palette = 12=#4672d4 - palette = 6=#8ec07c - palette = 14=#8ec07c macos-titlebar-style = hidden macos-option-as-alt = true confirm-close-surface = true @@ -67,6 +61,9 @@ in { force = true; }; + xdg.configFile."ghostty/themes/cozybox-dark".text = theme.renderGhostty "dark"; + xdg.configFile."ghostty/themes/cozybox-light".text = theme.renderGhostty "light"; + home.file."Library/Application Support/com.mitchellh.ghostty/config.ghostty" = { text = ghosttyConfig; force = true; diff --git a/home/scripts.nix b/home/scripts.nix new file mode 100644 index 0000000..070b2ff --- /dev/null +++ b/home/scripts.nix @@ -0,0 +1,33 @@ +{ + config, + lib, + pkgs, + ... +}: let + customScripts = import ../scripts {inherit config lib pkgs;}; +in { + home.packages = builtins.attrValues customScripts.packages; + + home.activation.initializeThemeState = lib.hm.dag.entryAfter ["writeBoundary"] '' + mkdir -p "${customScripts.theme.paths.stateDir}" "${customScripts.theme.paths.tmuxDir}" + + if [[ -f "${customScripts.theme.paths.stateFile}" ]]; then + mode=$(tr -d '[:space:]' < "${customScripts.theme.paths.stateFile}") + else + mode="${customScripts.theme.defaultMode}" + printf '%s\n' "$mode" > "${customScripts.theme.paths.stateFile}" + fi + + case "$mode" in + light) + tmux_target="${customScripts.tmuxConfigs.light}" + ;; + *) + printf '%s\n' "${customScripts.theme.defaultMode}" > "${customScripts.theme.paths.stateFile}" + tmux_target="${customScripts.tmuxConfigs.dark}" + ;; + esac + + ln -sfn "$tmux_target" "${customScripts.theme.paths.tmuxCurrentFile}" + ''; +} diff --git a/home/tmux.nix b/home/tmux.nix index 5dcccae..f9d7e31 100644 --- a/home/tmux.nix +++ b/home/tmux.nix @@ -1,4 +1,11 @@ -{lib, pkgs, ...}: { +{ + config, + lib, + pkgs, + ... +}: let + theme = import ../lib/theme.nix {inherit config;}; +in { programs.tmux = { enable = true; plugins = with pkgs.tmuxPlugins; [ @@ -75,54 +82,14 @@ # Disable waiting time when pressing escape, for smoother Neovim usage. set-option -s escape-time 0 - # Styling - RED="#ea6962" - GREEN="#8ec97c" - YELLOW="#d8a657" - BLUE="#4672d4" - MAGENTA="#d3869b" - CYAN="#89b482" - BLACK="#1d2021" - DARK_GRAY="#181818" - LIGHT_GRAY="#4F4946" - # Match Ghostty theme (cozybox-override palette + Gruvbox Material Dark base) - BG="#181818" - FG="#d4be98" - - HALF_ROUND_OPEN="#(printf '\uE0B2')" - HALF_ROUND_CLOSE="#(printf '\uE0B0')" - TRIANGLE_OPEN="#(printf '\uE0B2')" - TRIANGLE_CLOSE="#(printf '\uE0B0')" - + set-option -g prompt-cursor-colour default set-option -g status-position bottom - set-option -g status-style bg=''${BG},fg=''${FG} set-option -g status-justify left set-option -g status-left "" set-option -g status-right "#(~/.config/tmux/session-list.sh)" set-option -g status-left-length 100 set-option -g status-right-length 100 - - set-option -g window-status-format "\ - \ - #I\ - #[fg=''${MAGENTA}]:\ - #[fg=default]#W\ - \ - " - - set-option -g window-status-current-format "\ - \ - #[fg=''${MAGENTA}]*#[fg=default]#I\ - #[fg=''${MAGENTA}]:\ - #[fg=default]#W\ - \ - " - - set-option -g window-status-separator "" - - set-option -g pane-border-style fg=''${BG} - set-option -g pane-active-border-style fg=''${BG} - + source-file "${theme.paths.tmuxCurrentFile}" ''; }; @@ -131,9 +98,10 @@ text = '' #!/bin/sh current=$(tmux display-message -p '#S') + accent=$(tmux show -gv @cozybox-accent 2>/dev/null || printf '#d3869b') tmux list-sessions -F '#S' | while IFS= read -r s; do if [ "$s" = "$current" ]; then - printf ' #[fg=#d3869b]*#[fg=default]%s ' "$s" + printf ' #[fg=%s]*#[fg=default]%s ' "$accent" "$s" else printf ' %s ' "$s" fi diff --git a/home/zsh.nix b/home/zsh.nix index 70d78b5..358e442 100644 --- a/home/zsh.nix +++ b/home/zsh.nix @@ -41,11 +41,11 @@ tailscale = "/Applications/Tailscale.app/Contents/MacOS/Tailscale"; # nix helpers - nr = "nix profile remove"; # nr - remove from profile - ns = "nix search nixpkgs"; # ns - search packages - nls = "nix profile list"; # nls - list installed profile packages - nrb = "sudo darwin-rebuild switch --flake ~/Documents/GitHub/nix"; # nrb - rebuild declarative config - nup = "nix flake update ~/Documents/GitHub/nix && sudo darwin-rebuild switch --flake ~/Documents/GitHub/nix"; # nup - update flake + rebuild + nr = "nix profile remove"; # nr - remove from profile + ns = "nix search nixpkgs"; # ns - search packages + nls = "nix profile list"; # nls - list installed profile packages + nrb = "sudo darwin-rebuild switch --flake ~/Documents/GitHub/nix"; # nrb - rebuild declarative config + nup = "nix flake update ~/Documents/GitHub/nix && sudo darwin-rebuild switch --flake ~/Documents/GitHub/nix"; # nup - update flake + rebuild }; envExtra = '' @@ -108,16 +108,7 @@ $path ) - ni() { nix profile add "nixpkgs#$1"; } - unalias ga 2>/dev/null - ga() { - if [[ $# -eq 0 ]]; then - git add . - else - git add "$@" - fi - } git() { command git "$@" @@ -169,136 +160,9 @@ _codex_set_cursor beam } - iosrun() { - local project=$(find . -maxdepth 1 -name "*.xcodeproj" | head -1) - local scheme=$(basename "$project" .xcodeproj) - local derived=".derived-data" - local sim_name="''${1:-iPhone 16e}" - - if [[ -z "$project" ]]; then - echo "No .xcodeproj found in current directory" - return 1 - fi - - echo "Building $scheme..." - if ! xcodebuild -project "$project" -scheme "$scheme" \ - -destination "platform=iOS Simulator,name=$sim_name" \ - -derivedDataPath "$derived" build -quiet; then - echo "Build failed" - return 1 - fi - - echo "Build succeeded. Launching simulator..." - - xcrun simctl boot "$sim_name" 2>/dev/null - open -a Simulator - - local app_path="$derived/Build/Products/Debug-iphonesimulator/$scheme.app" - local bundle_id=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$app_path/Info.plist") - - echo "Installing $scheme..." - while ! xcrun simctl install "$sim_name" "$app_path" 2>/dev/null; do - sleep 0.5 - done - - echo "Launching $bundle_id..." - while ! xcrun simctl launch "$sim_name" "$bundle_id" 2>&1 | grep -q "$bundle_id"; do - sleep 0.5 - done - - echo "Launched $bundle_id - streaming logs (Ctrl+C to stop)" - echo "----------------------------------------" - - xcrun simctl spawn "$sim_name" log stream \ - --predicate "(subsystem CONTAINS '$bundle_id' OR process == '$scheme') AND NOT subsystem BEGINSWITH 'com.apple'" \ - --style compact \ - --color always 2>/dev/null | while read -r line; do - if [[ "$line" == *"error"* ]] || [[ "$line" == *"Error"* ]]; then - echo "\033[31m$line\033[0m" - elif [[ "$line" == *"warning"* ]] || [[ "$line" == *"Warning"* ]]; then - echo "\033[33m$line\033[0m" - else - echo "$line" - fi - done - } - - mdview() { - markserv "$1" - } - if command -v wt >/dev/null 2>&1; then eval "$(command wt config shell init zsh)" fi - - wtc() { wt switch --create --base @ "$@"; } - - unalias gpr 2>/dev/null - gpr() { - while true; do - local pr=$(gh pr list --limit 50 \ - --json number,title,author,headRefName \ - --template '{{range .}}#{{.number}} {{.title}} ({{.author.login}}) [{{.headRefName}}]{{"\n"}}{{end}}' \ - | fzf --preview 'gh pr view {1} --comments' \ - --preview-window=right:60%:wrap \ - --header 'enter: view | ctrl-m: merge | ctrl-x: close | ctrl-o: checkout | ctrl-b: browser' \ - --bind 'ctrl-o:execute(gh pr checkout {1})' \ - --bind 'ctrl-b:execute(gh pr view {1} --web)' \ - --expect=ctrl-m,ctrl-x,enter) - - [[ -z "$pr" ]] && return - - local key=$(echo "$pr" | head -1) - local selection=$(echo "$pr" | tail -1) - local num=$(echo "$selection" | grep -o '#[0-9]*' | tr -d '#') - - [[ -z "$num" ]] && return - - case "$key" in - ctrl-m) - echo "Merge PR #$num? (y/n)" - read -q && gh pr merge "$num" --merge - echo - ;; - ctrl-x) - echo "Close PR #$num? (y/n)" - read -q && gh pr close "$num" - echo - ;; - enter|"") - gh pr view "$num" - ;; - esac - done - } - - ghpr() { - local base=$(git rev-parse --abbrev-ref HEAD) - local upstream="''${1:-main}" - local remote_ref="origin/$upstream" - local unpushed=$(git log "$remote_ref"..HEAD --oneline 2>/dev/null) - - if [[ -z "$unpushed" ]]; then - if git diff --cached --quiet; then - echo "No unpushed commits and no staged changes" - return 1 - fi - echo "No unpushed commits, but staged changes found. Opening commit dialog..." - git commit || return 1 - fi - - local msg=$(git log "$remote_ref"..HEAD --format='%s' --reverse | head -1) - local branch=$(echo "$msg" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//') - - git checkout -b "$branch" - git checkout "$base" - git reset --hard "$remote_ref" - git checkout "$branch" - - git push -u origin "$branch" - gh pr create --base "$upstream" --fill --web 2>/dev/null || gh pr create --base "$upstream" --fill - gh pr view "$branch" --json url -q '.url' - } '') (lib.mkAfter '' diff --git a/lib/theme.nix b/lib/theme.nix new file mode 100644 index 0000000..1e1b2fb --- /dev/null +++ b/lib/theme.nix @@ -0,0 +1,88 @@ +{config, ...}: let + defaultMode = "dark"; + paths = { + stateDir = "${config.xdg.stateHome}/theme"; + stateFile = "${config.xdg.stateHome}/theme/current"; + ghosttyDir = "${config.xdg.configHome}/ghostty/themes"; + ghosttyCurrentFile = "${config.xdg.configHome}/ghostty/themes/current.conf"; + tmuxDir = "${config.xdg.configHome}/tmux/theme"; + tmuxCurrentFile = "${config.xdg.configHome}/tmux/theme/current.conf"; + }; + + themes = { + dark = { + ghosttyTheme = "Gruvbox Material Dark"; + background = "#181818"; + surface = "#1e1e1e"; + selectionBackground = "#504945"; + selectionForeground = "#ebdbb2"; + cursorColor = "#ddc7a1"; + text = "#d4be98"; + mutedText = "#7c6f64"; + red = "#ea6962"; + green = "#8ec97c"; + yellow = "#d8a657"; + blue = "#5b84de"; + aqua = "#8ec07c"; + purple = "#d3869b"; + orange = "#e78a4e"; + border = "#181818"; + }; + + light = { + ghosttyTheme = "Gruvbox Material Light"; + background = "#e7e7e7"; + surface = "#e1e1e1"; + selectionBackground = "#c3c7c9"; + selectionForeground = "#1d2021"; + cursorColor = "#282828"; + text = "#282828"; + mutedText = "#665c54"; + red = "#ea6962"; + green = "#8ec97c"; + yellow = "#d8a657"; + blue = "#5b84de"; + aqua = "#8ec07c"; + purple = "#d3869b"; + orange = "#e78a4e"; + border = "#c3c7c9"; + }; + }; + + renderGhostty = mode: let + theme = themes.${mode}; + in '' + theme = "${theme.ghosttyTheme}" + background = ${theme.background} + cursor-color = ${theme.cursorColor} + selection-background = ${theme.selectionBackground} + selection-foreground = ${theme.selectionForeground} + palette = 1=${theme.red} + palette = 2=${theme.green} + palette = 3=${theme.yellow} + palette = 4=${theme.blue} + palette = 5=${theme.purple} + palette = 6=${theme.aqua} + palette = 9=${theme.red} + palette = 10=${theme.green} + palette = 11=${theme.yellow} + palette = 12=${theme.blue} + palette = 13=${theme.purple} + palette = 14=${theme.aqua} + ''; + + renderTmux = mode: let + theme = themes.${mode}; + in '' + set-option -g @cozybox-mode '${mode}' + set-option -g @cozybox-accent '${theme.purple}' + set-option -g status-style bg=${theme.background},fg=${theme.text} + set-option -g window-status-format " #I#[fg=${theme.purple}]:#[fg=default]#W " + set-option -g window-status-current-format " #[fg=${theme.purple}]*#[fg=default]#I#[fg=${theme.purple}]:#[fg=default]#W " + set-option -g window-status-separator "" + set-option -g pane-border-style fg=${theme.border} + set-option -g pane-active-border-style fg=${theme.border} + ''; +in { + inherit defaultMode paths renderGhostty renderTmux themes; +} diff --git a/scripts/default.nix b/scripts/default.nix new file mode 100644 index 0000000..83e5ea9 --- /dev/null +++ b/scripts/default.nix @@ -0,0 +1,87 @@ +{ + config, + lib, + pkgs, +}: let + theme = import ../lib/theme.nix {inherit config;}; + + tmuxConfigs = { + dark = pkgs.writeText "tmux-theme-dark.conf" (theme.renderTmux "dark"); + light = pkgs.writeText "tmux-theme-light.conf" (theme.renderTmux "light"); + }; + + mkScript = { + file, + name, + runtimeInputs ? [], + replacements ? {}, + }: + pkgs.writeShellApplication { + inherit name runtimeInputs; + text = + lib.replaceStrings + (builtins.attrNames replacements) + (builtins.attrValues replacements) + (builtins.readFile file); + }; + + packages = { + ga = mkScript { + name = "ga"; + file = ./ga.sh; + runtimeInputs = with pkgs; [git]; + }; + + ghpr = mkScript { + name = "ghpr"; + file = ./ghpr.sh; + runtimeInputs = with pkgs; [gh git gnugrep gnused coreutils]; + }; + + gpr = mkScript { + name = "gpr"; + file = ./gpr.sh; + runtimeInputs = with pkgs; [gh fzf gnugrep coreutils]; + }; + + iosrun = mkScript { + name = "iosrun"; + file = ./iosrun.sh; + runtimeInputs = with pkgs; [findutils gnugrep coreutils]; + }; + + mdview = mkScript { + name = "mdview"; + file = ./mdview.sh; + }; + + ni = mkScript { + name = "ni"; + file = ./ni.sh; + runtimeInputs = with pkgs; [nix]; + }; + + theme = mkScript { + name = "theme"; + file = ./theme.sh; + runtimeInputs = with pkgs; [coreutils neovim tmux]; + replacements = { + "@DEFAULT_MODE@" = theme.defaultMode; + "@STATE_DIR@" = theme.paths.stateDir; + "@STATE_FILE@" = theme.paths.stateFile; + "@TMUX_DIR@" = theme.paths.tmuxDir; + "@TMUX_CURRENT_FILE@" = theme.paths.tmuxCurrentFile; + "@TMUX_DARK_FILE@" = "${tmuxConfigs.dark}"; + "@TMUX_LIGHT_FILE@" = "${tmuxConfigs.light}"; + "@TMUX_CONFIG@" = "${config.xdg.configHome}/tmux/tmux.conf"; + }; + }; + + wtc = mkScript { + name = "wtc"; + file = ./wtc.sh; + }; + }; +in { + inherit packages theme tmuxConfigs; +} diff --git a/scripts/ga.sh b/scripts/ga.sh new file mode 100644 index 0000000..6594482 --- /dev/null +++ b/scripts/ga.sh @@ -0,0 +1,9 @@ +if [[ $# -eq 0 ]]; then + git add . +else + git add "$@" +fi + +if command -v critic >/dev/null 2>&1; then + ( critic review 2>/dev/null & ) +fi diff --git a/scripts/ghpr.sh b/scripts/ghpr.sh new file mode 100644 index 0000000..db8e7cd --- /dev/null +++ b/scripts/ghpr.sh @@ -0,0 +1,26 @@ +base=$(git rev-parse --abbrev-ref HEAD) +upstream="${1:-main}" +remote_ref="origin/$upstream" +unpushed=$(git log "$remote_ref"..HEAD --oneline 2>/dev/null) + +if [[ -z "$unpushed" ]]; then + if git diff --cached --quiet; then + echo "No unpushed commits and no staged changes" + exit 1 + fi + + echo "No unpushed commits, but staged changes found. Opening commit dialog..." + git commit +fi + +msg=$(git log "$remote_ref"..HEAD --format='%s' --reverse | head -1) +branch=$(echo "$msg" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//') + +git checkout -b "$branch" +git checkout "$base" +git reset --hard "$remote_ref" +git checkout "$branch" + +git push -u origin "$branch" +gh pr create --base "$upstream" --fill --web 2>/dev/null || gh pr create --base "$upstream" --fill +gh pr view "$branch" --json url -q '.url' diff --git a/scripts/gpr.sh b/scripts/gpr.sh new file mode 100644 index 0000000..44db53d --- /dev/null +++ b/scripts/gpr.sh @@ -0,0 +1,39 @@ +while true; do + pr=$( + gh pr list --limit 50 \ + --json number,title,author,headRefName \ + --template '{{range .}}#{{.number}} {{.title}} ({{.author.login}}) [{{.headRefName}}]{{"\n"}}{{end}}' \ + | fzf --preview 'gh pr view {1} --comments' \ + --preview-window=right:60%:wrap \ + --header 'enter: view | ctrl-m: merge | ctrl-x: close | ctrl-o: checkout | ctrl-b: browser' \ + --bind 'ctrl-o:execute(gh pr checkout {1})' \ + --bind 'ctrl-b:execute(gh pr view {1} --web)' \ + --expect=ctrl-m,ctrl-x,enter + ) + + [[ -z "$pr" ]] && exit 0 + + key=$(echo "$pr" | head -1) + selection=$(echo "$pr" | tail -1) + num=$(echo "$selection" | grep -o '#[0-9]*' | tr -d '#') + + [[ -z "$num" ]] && exit 0 + + case "$key" in + ctrl-m) + read -r -p "Merge PR #$num? [y/N] " response + if [[ "$response" =~ ^[Yy]$ ]]; then + gh pr merge "$num" --merge + fi + ;; + ctrl-x) + read -r -p "Close PR #$num? [y/N] " response + if [[ "$response" =~ ^[Yy]$ ]]; then + gh pr close "$num" + fi + ;; + enter|"") + gh pr view "$num" + ;; + esac +done diff --git a/scripts/iosrun.sh b/scripts/iosrun.sh new file mode 100644 index 0000000..d469694 --- /dev/null +++ b/scripts/iosrun.sh @@ -0,0 +1,51 @@ +project=$(find . -maxdepth 1 -name "*.xcodeproj" | head -1) +scheme=$(basename "$project" .xcodeproj) +derived=".derived-data" +sim_name="${1:-iPhone 16e}" + +if [[ -z "$project" ]]; then + echo "No .xcodeproj found in current directory" + exit 1 +fi + +echo "Building $scheme..." +if ! xcodebuild -project "$project" -scheme "$scheme" \ + -destination "platform=iOS Simulator,name=$sim_name" \ + -derivedDataPath "$derived" build -quiet; then + echo "Build failed" + exit 1 +fi + +echo "Build succeeded. Launching simulator..." + +xcrun simctl boot "$sim_name" 2>/dev/null || true +open -a Simulator + +app_path="$derived/Build/Products/Debug-iphonesimulator/$scheme.app" +bundle_id=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$app_path/Info.plist") + +echo "Installing $scheme..." +while ! xcrun simctl install "$sim_name" "$app_path" 2>/dev/null; do + sleep 0.5 +done + +echo "Launching $bundle_id..." +while ! xcrun simctl launch "$sim_name" "$bundle_id" 2>&1 | grep -q "$bundle_id"; do + sleep 0.5 +done + +echo "Launched $bundle_id - streaming logs (Ctrl+C to stop)" +echo "----------------------------------------" + +xcrun simctl spawn "$sim_name" log stream \ + --predicate "(subsystem CONTAINS '$bundle_id' OR process == '$scheme') AND NOT subsystem BEGINSWITH 'com.apple'" \ + --style compact \ + --color always 2>/dev/null | while read -r line; do + if [[ "$line" == *"error"* ]] || [[ "$line" == *"Error"* ]]; then + printf '\033[31m%s\033[0m\n' "$line" + elif [[ "$line" == *"warning"* ]] || [[ "$line" == *"Warning"* ]]; then + printf '\033[33m%s\033[0m\n' "$line" + else + echo "$line" + fi +done diff --git a/scripts/mdview.sh b/scripts/mdview.sh new file mode 100644 index 0000000..b0352fd --- /dev/null +++ b/scripts/mdview.sh @@ -0,0 +1 @@ +exec markserv "$@" diff --git a/scripts/ni.sh b/scripts/ni.sh new file mode 100644 index 0000000..1cc8338 --- /dev/null +++ b/scripts/ni.sh @@ -0,0 +1,6 @@ +if [[ $# -ne 1 ]]; then + echo "usage: ni " + exit 1 +fi + +exec nix profile add "nixpkgs#$1" diff --git a/scripts/theme.sh b/scripts/theme.sh new file mode 100644 index 0000000..05ff252 --- /dev/null +++ b/scripts/theme.sh @@ -0,0 +1,71 @@ +usage() { + echo "usage: theme " +} + +read_mode() { + if [[ -f "@STATE_FILE@" ]]; then + mode=$(tr -d '[:space:]' < "@STATE_FILE@") + if [[ "$mode" == "dark" || "$mode" == "light" ]]; then + echo "$mode" + return + fi + fi + + echo "@DEFAULT_MODE@" +} + +link_mode_assets() { + local mode="$1" + local tmux_target + + case "$mode" in + dark) + tmux_target="@TMUX_DARK_FILE@" + ;; + light) + tmux_target="@TMUX_LIGHT_FILE@" + ;; + *) + echo "invalid mode: $mode" >&2 + exit 1 + ;; + esac + + mkdir -p "@STATE_DIR@" "@TMUX_DIR@" + printf '%s\n' "$mode" > "@STATE_FILE@" + ln -sfn "$tmux_target" "@TMUX_CURRENT_FILE@" + + if command -v tmux >/dev/null 2>&1 && tmux start-server >/dev/null 2>&1; then + tmux source-file "@TMUX_CONFIG@" >/dev/null 2>&1 || true + fi + + for socket in /tmp/nvim-*.sock; do + [[ -S "$socket" ]] || continue + nvim --server "$socket" --remote-send "ThemeSync $mode" >/dev/null 2>&1 || true + done +} + +mode="${1:-current}" + +case "$mode" in + dark|light) + ;; + toggle) + if [[ "$(read_mode)" == "dark" ]]; then + mode="light" + else + mode="dark" + fi + ;; + current) + read_mode + exit 0 + ;; + *) + usage >&2 + exit 1 + ;; +esac + +link_mode_assets "$mode" +printf 'applied %s theme\n' "$mode" diff --git a/scripts/wtc.sh b/scripts/wtc.sh new file mode 100644 index 0000000..867c010 --- /dev/null +++ b/scripts/wtc.sh @@ -0,0 +1 @@ +exec wt switch --create --base @ "$@"