diff --git a/config/nvim/lua/config/fzf_reload.lua b/config/nvim/lua/config/fzf_reload.lua new file mode 100644 index 0000000..0023717 --- /dev/null +++ b/config/nvim/lua/config/fzf_reload.lua @@ -0,0 +1,24 @@ +local M = {} +M.opts = nil + +function M.setup(opts) M.opts = vim.deepcopy(opts or {}) end + +function M.reload() + local path = vim.env.FZF_DEFAULT_OPTS_FILE or vim.fn.expand "~/.config/fzf/themes/theme" + if vim.fn.filereadable(path) == 0 then return end + + local lines = vim.fn.readfile(path) + if not lines or #lines == 0 or not M.opts then return end + + local colors = {} + for color_spec in table.concat(lines, "\n"):gmatch "%-%-color=([^%s]+)" do + for key, value in color_spec:gmatch "([^:,]+):([^,]+)" do + colors[key] = value + end + end + + M.opts.fzf_colors = colors + require("fzf-lua").setup(M.opts) +end + +return M diff --git a/config/nvim/lua/plugins/fzf.lua b/config/nvim/lua/plugins/fzf.lua index d95a03f..c4a6635 100644 --- a/config/nvim/lua/plugins/fzf.lua +++ b/config/nvim/lua/plugins/fzf.lua @@ -1,74 +1,76 @@ ---@param kind 'issue'|'pr' ---@param state 'all'|'open'|'closed' local function gh_picker(kind, state) - if vim.fn.executable('gh') ~= 1 then - vim.notify('gh CLI not found', vim.log.levels.WARN) - return - end - local next_state = ({ all = 'open', open = 'closed', closed = 'all' })[state] - local label = kind == 'pr' and 'PRs' or 'Issues' - require('fzf-lua').fzf_exec(('gh %s list --limit 100 --state %s'):format(kind, state), { - prompt = ('%s (%s)> '):format(label, state), - header = ':: to toggle all/open/closed', - actions = { - ['default'] = function(selected) - local num = selected[1]:match('^#?(%d+)') - if num then - vim.system({ 'gh', kind, 'view', num, '--web' }) - end - end, - ['ctrl-o'] = function() - gh_picker(kind, next_state) - end, - }, - }) + if vim.fn.executable "gh" ~= 1 then + vim.notify("gh CLI not found", vim.log.levels.WARN) + return + end + local next_state = ({ all = "open", open = "closed", closed = "all" })[state] + local label = kind == "pr" and "PRs" or "Issues" + require("fzf-lua").fzf_exec(("gh %s list --limit 100 --state %s"):format(kind, state), { + prompt = ("%s (%s)> "):format(label, state), + header = ":: to toggle all/open/closed", + actions = { + ["default"] = function(selected) + local num = selected[1]:match "^#?(%d+)" + if num then vim.system { "gh", kind, "view", num, "--web" } end + end, + ["ctrl-o"] = function() gh_picker(kind, next_state) end, + }, + }) end return { - 'ibhagwan/fzf-lua', - dependencies = { 'nvim-tree/nvim-web-devicons' }, - config = function(_, opts) - require('fzf-lua').setup(opts) + "ibhagwan/fzf-lua", + dependencies = { "nvim-tree/nvim-web-devicons" }, + config = function(_, opts) + local fzf = require "fzf-lua" + fzf.setup(opts) - map('n', '', function() - local fzf = require('fzf-lua') - local git_dir = vim.fn.system('git rev-parse --git-dir 2>/dev/null'):gsub('\n', '') - if vim.v.shell_error == 0 and git_dir ~= '' then - fzf.git_files() - else - fzf.files() - end - end) - map('n', 'ff', 'FzfLua files') - map('n', 'fg', 'FzfLua live_grep') - map('n', 'fb', 'FzfLua buffers') - map('n', 'fh', 'FzfLua help_tags') - map('n', 'fr', 'FzfLua resume') - map('n', 'fo', 'FzfLua oldfiles') - map('n', 'fc', 'FzfLua commands') - map('n', 'fk', 'FzfLua keymaps') - map('n', 'f/', 'FzfLua search_history') - map('n', 'f:', 'FzfLua command_history') - map('n', 'fe', 'FzfLua files cwd=~/.config') - map('n', 'gq', 'FzfLua quickfix') - map('n', 'gl', 'FzfLua loclist') - map('n', 'GB', 'FzfLua git_branches') - map('n', 'Gc', 'FzfLua git_commits') - map('n', 'Gs', 'FzfLua git_status') - map('n', 'Gp', function() gh_picker('pr', 'open') end) - map('n', 'Gi', function() gh_picker('issue', 'open') end) - end, - opts = { - 'default-title', - winopts = { - border = 'single', - preview = { - layout = 'vertical', - vertical = 'down:50%', - }, - }, - fzf_opts = { - ['--layout'] = 'reverse', - }, + local ok, fzf_reload = pcall(require, "config.fzf_reload") + if ok then + fzf_reload.setup(opts) + fzf_reload.reload() + end + + map("n", "", function() + local git_dir = vim.fn.system("git rev-parse --git-dir 2>/dev/null"):gsub("\n", "") + if vim.v.shell_error == 0 and git_dir ~= "" then + fzf.git_files() + else + fzf.files() + end + end) + map("n", "ff", "FzfLua files") + map("n", "fg", "FzfLua live_grep") + map("n", "fb", "FzfLua buffers") + map("n", "fh", "FzfLua help_tags") + map("n", "fr", "FzfLua resume") + map("n", "fo", "FzfLua oldfiles") + map("n", "fc", "FzfLua commands") + map("n", "fk", "FzfLua keymaps") + map("n", "f/", "FzfLua search_history") + map("n", "f:", "FzfLua command_history") + map("n", "fe", "FzfLua files cwd=~/.config") + map("n", "gq", "FzfLua quickfix") + map("n", "gl", "FzfLua loclist") + map("n", "GB", "FzfLua git_branches") + map("n", "Gc", "FzfLua git_commits") + map("n", "Gs", "FzfLua git_status") + map("n", "Gp", function() gh_picker("pr", "open") end) + map("n", "Gi", function() gh_picker("issue", "open") end) + end, + opts = { + "default-title", + winopts = { + border = "single", + preview = { + layout = "vertical", + vertical = "down:50%", + }, }, + fzf_opts = { + ["--layout"] = "reverse", + }, + }, } diff --git a/config/nvim/lua/plugins/ui.lua b/config/nvim/lua/plugins/ui.lua index c8098cc..08f704c 100644 --- a/config/nvim/lua/plugins/ui.lua +++ b/config/nvim/lua/plugins/ui.lua @@ -1,74 +1,50 @@ 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_status = function() return require("theme").statusline_label() end + 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 = { theme_status, "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..3799192 --- /dev/null +++ b/config/nvim/lua/theme.lua @@ -0,0 +1,111 @@ +local M = {} + +local xdg_state_home = vim.env.XDG_STATE_HOME or (vim.env.HOME .. "/.local/state") +local theme_state_file = xdg_state_home .. "/theme/current" +local active_schemes = { + cozybox = true, + ["cozybox-light"] = true, +} + +local function ensure_server_socket() + local socket_path = ("/tmp/nvim-%d.sock"):format(vim.fn.getpid()) + local active_servers = vim.fn.serverlist() + + for _, server in ipairs(active_servers) do + if server == socket_path then return end + end + + local stat = vim.uv.fs_stat(socket_path) + if stat and stat.type == "socket" then vim.fn.delete(socket_path) end + + 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.statusline_label() + local mode = vim.g.cozybox_theme_mode or read_mode() + local scheme = vim.g.colors_name or colorscheme_for_mode(mode) + local expected_scheme = colorscheme_for_mode(mode) + + if scheme == expected_scheme then return "theme:" .. mode end + + return ("theme:%s/%s"):format(mode, scheme) +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() + local ok_reload, fzf_reload = pcall(require, "config.fzf_reload") + if ok_reload then pcall(fzf_reload.reload) end + vim.schedule(function() + local ok, lualine = pcall(require, "lualine") + if ok then pcall(lualine.refresh, { place = { "statusline" } }) end + pcall(vim.cmd, "redraw!") + end) +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..84eed47 100644 --- a/home/default.nix +++ b/home/default.nix @@ -3,6 +3,7 @@ ./bat.nix ./claude.nix ./codex.nix + ./fzf.nix ./gcloud.nix ./gh.nix ./ghostty.nix @@ -13,6 +14,7 @@ ./migration.nix ./nvim.nix ./rectangle.nix + ./scripts.nix ./tmux.nix ./zsh.nix ]; diff --git a/home/fzf.nix b/home/fzf.nix new file mode 100644 index 0000000..c0f5ee4 --- /dev/null +++ b/home/fzf.nix @@ -0,0 +1,10 @@ +{config, ...}: let + theme = import ../lib/theme.nix {inherit config;}; +in { + home.sessionVariables = { + FZF_DEFAULT_OPTS_FILE = theme.paths.fzfCurrentFile; + }; + + xdg.configFile."fzf/themes/cozybox-dark".text = theme.renderFzf "dark"; + xdg.configFile."fzf/themes/cozybox-light".text = theme.renderFzf "light"; +} diff --git a/home/ghostty.nix b/home/ghostty.nix index c2a9e50..13e359b 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 = "cozybox-current" 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..bccc35b --- /dev/null +++ b/home/scripts.nix @@ -0,0 +1,39 @@ +{ + 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.fzfDir}" "${customScripts.theme.paths.ghosttyDir}" "${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) + fzf_target="${customScripts.theme.paths.fzfDir}/cozybox-light" + ghostty_target="${customScripts.theme.paths.ghosttyDir}/cozybox-light" + tmux_target="${customScripts.tmuxConfigs.light}" + ;; + *) + printf '%s\n' "${customScripts.theme.defaultMode}" > "${customScripts.theme.paths.stateFile}" + fzf_target="${customScripts.theme.paths.fzfDir}/cozybox-dark" + ghostty_target="${customScripts.theme.paths.ghosttyDir}/cozybox-dark" + tmux_target="${customScripts.tmuxConfigs.dark}" + ;; + esac + + ln -sfn "$fzf_target" "${customScripts.theme.paths.fzfCurrentFile}" + ln -sfn "$ghostty_target" "${customScripts.theme.paths.ghosttyCurrentFile}" + 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..effccd1 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,92 @@ $path ) - ni() { nix profile add "nixpkgs#$1"; } + _codex_read_theme_mode() { + local mode_file="$HOME/.local/state/theme/current" + if [[ -f "$mode_file" ]]; then + local mode + mode=$(tr -d '[:space:]' < "$mode_file") + if [[ "$mode" == light || "$mode" == dark ]]; then + printf '%s' "$mode" + return + fi + fi + + printf 'dark' + } + + _codex_apply_highlight_styles() { + local mode="$(_codex_read_theme_mode)" + if [[ "$mode" == "''${_CODEX_LAST_HIGHLIGHT_THEME:-}" ]]; then + return + fi + + typeset -gA ZSH_HIGHLIGHT_STYLES + + if [[ "$mode" == light ]]; then + ZSH_HIGHLIGHT_STYLES[arg0]='fg=#427b58' + ZSH_HIGHLIGHT_STYLES[autodirectory]='fg=#427b58,underline' + ZSH_HIGHLIGHT_STYLES[back-dollar-quoted-argument]='fg=#076678' + ZSH_HIGHLIGHT_STYLES[back-double-quoted-argument]='fg=#076678' + ZSH_HIGHLIGHT_STYLES[back-quoted-argument-delimiter]='fg=#8f3f71' + ZSH_HIGHLIGHT_STYLES[bracket-error]='fg=#ea6962,bold' + ZSH_HIGHLIGHT_STYLES[bracket-level-1]='fg=#076678,bold' + ZSH_HIGHLIGHT_STYLES[bracket-level-2]='fg=#427b58,bold' + ZSH_HIGHLIGHT_STYLES[bracket-level-3]='fg=#8f3f71,bold' + ZSH_HIGHLIGHT_STYLES[bracket-level-4]='fg=#b57614,bold' + ZSH_HIGHLIGHT_STYLES[bracket-level-5]='fg=#076678,bold' + ZSH_HIGHLIGHT_STYLES[comment]='fg=#928374' + ZSH_HIGHLIGHT_STYLES[command-substitution-delimiter]='fg=#8f3f71' + ZSH_HIGHLIGHT_STYLES[dollar-double-quoted-argument]='fg=#076678' + ZSH_HIGHLIGHT_STYLES[dollar-quoted-argument]='fg=#b57614' + ZSH_HIGHLIGHT_STYLES[double-quoted-argument]='fg=#b57614' + ZSH_HIGHLIGHT_STYLES[global-alias]='fg=#076678' + ZSH_HIGHLIGHT_STYLES[globbing]='fg=#076678' + ZSH_HIGHLIGHT_STYLES[history-expansion]='fg=#076678' + ZSH_HIGHLIGHT_STYLES[path]='fg=#3c3836,underline' + ZSH_HIGHLIGHT_STYLES[precommand]='fg=#427b58,underline' + ZSH_HIGHLIGHT_STYLES[process-substitution-delimiter]='fg=#8f3f71' + ZSH_HIGHLIGHT_STYLES[rc-quote]='fg=#076678' + ZSH_HIGHLIGHT_STYLES[redirection]='fg=#b57614' + ZSH_HIGHLIGHT_STYLES[reserved-word]='fg=#b57614' + ZSH_HIGHLIGHT_STYLES[single-quoted-argument]='fg=#b57614' + ZSH_HIGHLIGHT_STYLES[suffix-alias]='fg=#427b58,underline' + ZSH_HIGHLIGHT_STYLES[unknown-token]='fg=#ea6962,bold' + else + ZSH_HIGHLIGHT_STYLES[arg0]='fg=#8ec97c' + ZSH_HIGHLIGHT_STYLES[autodirectory]='fg=#8ec97c,underline' + ZSH_HIGHLIGHT_STYLES[back-dollar-quoted-argument]='fg=#8ec07c' + ZSH_HIGHLIGHT_STYLES[back-double-quoted-argument]='fg=#8ec07c' + ZSH_HIGHLIGHT_STYLES[back-quoted-argument-delimiter]='fg=#d3869b' + ZSH_HIGHLIGHT_STYLES[bracket-error]='fg=#ea6962,bold' + ZSH_HIGHLIGHT_STYLES[bracket-level-1]='fg=#5b84de,bold' + ZSH_HIGHLIGHT_STYLES[bracket-level-2]='fg=#8ec97c,bold' + ZSH_HIGHLIGHT_STYLES[bracket-level-3]='fg=#d3869b,bold' + ZSH_HIGHLIGHT_STYLES[bracket-level-4]='fg=#d8a657,bold' + ZSH_HIGHLIGHT_STYLES[bracket-level-5]='fg=#8ec07c,bold' + ZSH_HIGHLIGHT_STYLES[comment]='fg=#7c6f64' + ZSH_HIGHLIGHT_STYLES[command-substitution-delimiter]='fg=#d3869b' + ZSH_HIGHLIGHT_STYLES[dollar-double-quoted-argument]='fg=#8ec07c' + ZSH_HIGHLIGHT_STYLES[dollar-quoted-argument]='fg=#d8a657' + ZSH_HIGHLIGHT_STYLES[double-quoted-argument]='fg=#d8a657' + ZSH_HIGHLIGHT_STYLES[global-alias]='fg=#8ec07c' + ZSH_HIGHLIGHT_STYLES[globbing]='fg=#5b84de' + ZSH_HIGHLIGHT_STYLES[history-expansion]='fg=#5b84de' + ZSH_HIGHLIGHT_STYLES[path]='fg=#d4be98,underline' + ZSH_HIGHLIGHT_STYLES[precommand]='fg=#8ec97c,underline' + ZSH_HIGHLIGHT_STYLES[process-substitution-delimiter]='fg=#d3869b' + ZSH_HIGHLIGHT_STYLES[rc-quote]='fg=#8ec07c' + ZSH_HIGHLIGHT_STYLES[redirection]='fg=#d8a657' + ZSH_HIGHLIGHT_STYLES[reserved-word]='fg=#d8a657' + ZSH_HIGHLIGHT_STYLES[single-quoted-argument]='fg=#d8a657' + ZSH_HIGHLIGHT_STYLES[suffix-alias]='fg=#8ec97c,underline' + ZSH_HIGHLIGHT_STYLES[unknown-token]='fg=#ea6962,bold' + fi + + typeset -g _CODEX_LAST_HIGHLIGHT_THEME="$mode" + } unalias ga 2>/dev/null - ga() { - if [[ $# -eq 0 ]]; then - git add . - else - git add "$@" - fi - } git() { command git "$@" @@ -162,6 +238,7 @@ zle -N zle-line-finish precmd() { + _codex_apply_highlight_styles _codex_set_cursor beam } @@ -169,136 +246,11 @@ _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" - } + _codex_apply_highlight_styles 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..8123e56 --- /dev/null +++ b/lib/theme.nix @@ -0,0 +1,135 @@ +{config, ...}: let + defaultMode = "dark"; + sharedPalette = { + red = "#ea6962"; + green = "#8ec97c"; + yellow = "#d79921"; + yellowBright = "#fabd2f"; + blue = "#5b84de"; + purple = "#d3869b"; + purpleNeutral = "#b16286"; + aqua = "#8ec07c"; + aquaNeutral = "#689d6a"; + gray = "#928374"; + }; + paths = { + stateDir = "${config.xdg.stateHome}/theme"; + stateFile = "${config.xdg.stateHome}/theme/current"; + fzfDir = "${config.xdg.configHome}/fzf/themes"; + fzfCurrentFile = "${config.xdg.configHome}/fzf/themes/theme"; + ghosttyDir = "${config.xdg.configHome}/ghostty/themes"; + ghosttyCurrentFile = "${config.xdg.configHome}/ghostty/themes/cozybox-current"; + tmuxDir = "${config.xdg.configHome}/tmux/theme"; + tmuxCurrentFile = "${config.xdg.configHome}/tmux/theme/current.conf"; + }; + + themes = { + dark = { + background = "#181818"; + surface = "#1e1e1e"; + selectionBackground = "#504945"; + selectionForeground = "#ebdbb2"; + cursorColor = "#ddc7a1"; + cursorText = "#181818"; + foreground = "#ebdbb2"; + text = "#d4be98"; + mutedText = "#7c6f64"; + blue = sharedPalette.blue; + green = sharedPalette.green; + purple = sharedPalette.purple; + border = "#181818"; + palette = [ + "#1d2021" + sharedPalette.red + sharedPalette.green + sharedPalette.yellow + sharedPalette.blue + sharedPalette.purpleNeutral + sharedPalette.aquaNeutral + "#a89984" + sharedPalette.gray + sharedPalette.red + sharedPalette.green + sharedPalette.yellowBright + sharedPalette.blue + sharedPalette.purple + sharedPalette.aqua + "#ebdbb2" + ]; + }; + + light = { + background = "#e7e7e7"; + surface = "#e1e1e1"; + selectionBackground = "#c3c7c9"; + selectionForeground = "#3c3836"; + cursorColor = "#282828"; + cursorText = "#e7e7e7"; + foreground = "#3c3836"; + text = "#3c3836"; + mutedText = "#665c54"; + blue = sharedPalette.blue; + green = sharedPalette.green; + purple = sharedPalette.purple; + border = "#e7e7e7"; + palette = [ + "#f9f5d7" + "#923f3a" + sharedPalette.green + sharedPalette.yellow + "#4261a5" + sharedPalette.purpleNeutral + sharedPalette.aquaNeutral + "#7c6f64" + sharedPalette.gray + "#923f3a" + sharedPalette.green + sharedPalette.yellowBright + "#4261a5" + sharedPalette.purple + sharedPalette.aqua + "#3c3836" + ]; + }; + }; + + renderGhostty = mode: let + theme = themes.${mode}; + paletteLines = + builtins.concatStringsSep "\n" + (builtins.genList + (index: "palette = ${toString index}=${builtins.elemAt theme.palette index}") + (builtins.length theme.palette)); + in '' + background = ${theme.background} + foreground = ${theme.foreground} + cursor-color = ${theme.cursorColor} + cursor-text = ${theme.cursorText} + selection-background = ${theme.selectionBackground} + selection-foreground = ${theme.selectionForeground} + ${paletteLines} + ''; + + 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} + ''; + + renderFzf = mode: let + theme = themes.${mode}; + in '' + --color=fg:${theme.text},bg:${theme.background},hl:${theme.blue} + --color=fg+:${theme.text},bg+:${theme.surface},hl+:${theme.blue} + --color=info:${theme.green},prompt:${theme.blue},pointer:${theme.text},marker:${theme.green},spinner:${theme.text} + ''; +in { + inherit defaultMode paths renderFzf renderGhostty renderTmux themes; +} diff --git a/scripts/default.nix b/scripts/default.nix new file mode 100644 index 0000000..7735e03 --- /dev/null +++ b/scripts/default.nix @@ -0,0 +1,95 @@ +{ + 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 findutils neovim tmux]; + replacements = { + "@DEFAULT_MODE@" = theme.defaultMode; + "@STATE_DIR@" = theme.paths.stateDir; + "@STATE_FILE@" = theme.paths.stateFile; + "@FZF_DIR@" = theme.paths.fzfDir; + "@FZF_CURRENT_FILE@" = theme.paths.fzfCurrentFile; + "@FZF_DARK_FILE@" = "${theme.paths.fzfDir}/cozybox-dark"; + "@FZF_LIGHT_FILE@" = "${theme.paths.fzfDir}/cozybox-light"; + "@GHOSTTY_DIR@" = theme.paths.ghosttyDir; + "@GHOSTTY_CURRENT_FILE@" = theme.paths.ghosttyCurrentFile; + "@GHOSTTY_DARK_FILE@" = "${theme.paths.ghosttyDir}/cozybox-dark"; + "@GHOSTTY_LIGHT_FILE@" = "${theme.paths.ghosttyDir}/cozybox-light"; + "@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..2a1c4fa --- /dev/null +++ b/scripts/theme.sh @@ -0,0 +1,103 @@ +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 fzf_target + local ghostty_target + local tmux_target + local apple_dark_mode + + case "$mode" in + dark) + fzf_target="@FZF_DARK_FILE@" + ghostty_target="@GHOSTTY_DARK_FILE@" + tmux_target="@TMUX_DARK_FILE@" + apple_dark_mode=true + ;; + light) + fzf_target="@FZF_LIGHT_FILE@" + ghostty_target="@GHOSTTY_LIGHT_FILE@" + tmux_target="@TMUX_LIGHT_FILE@" + apple_dark_mode=false + ;; + *) + echo "invalid mode: $mode" >&2 + exit 1 + ;; + esac + + mkdir -p "@STATE_DIR@" "@FZF_DIR@" "@GHOSTTY_DIR@" "@TMUX_DIR@" + printf '%s\n' "$mode" > "@STATE_FILE@" + ln -sfn "$fzf_target" "@FZF_CURRENT_FILE@" + ln -sfn "$ghostty_target" "@GHOSTTY_CURRENT_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 + + if [[ "$(uname -s)" == "Darwin" ]] && command -v osascript >/dev/null 2>&1; then + osascript -e "tell application \"System Events\" to tell appearance preferences to set dark mode to ${apple_dark_mode}" >/dev/null 2>&1 || true + + osascript <<'EOF' >/dev/null 2>&1 || true +tell application "System Events" + if not (exists process "Ghostty") then + return + end if + + tell process "Ghostty" + click menu item "Reload Configuration" of menu 1 of menu bar item "Ghostty" of menu bar 1 + end tell +end tell +EOF + fi + + while IFS= read -r socket; do + [[ -S "$socket" ]] || continue + nvim --server "$socket" --remote-expr "execute('ThemeSync $mode')" >/dev/null 2>&1 || true + done < <( + { + find /tmp -maxdepth 1 -type s -name 'nvim-*.sock' 2>/dev/null + find "${TMPDIR:-/tmp}" -type s -path "*/nvim.${USER}/*/nvim.*" 2>/dev/null + } | sort -u + ) +} + +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 @ "$@"