diff --git a/.cursor/rules/greg-workflow-preferences.mdc b/.cursor/rules/greg-workflow-preferences.mdc index 69d35d2..a79dd0d 100644 --- a/.cursor/rules/greg-workflow-preferences.mdc +++ b/.cursor/rules/greg-workflow-preferences.mdc @@ -1,5 +1,5 @@ --- -description: Preferred CLI tools (fd, rg, bat, …), git conventions, fff, and dex task tracking workflow +description: Workflow preferences for CLI tools, git, and task tracking alwaysApply: true --- diff --git a/alacritty/.local/bin/alacritty-theme-select b/alacritty/.local/bin/alacritty-theme-select index 508dbbd..b4726b1 100755 --- a/alacritty/.local/bin/alacritty-theme-select +++ b/alacritty/.local/bin/alacritty-theme-select @@ -1,18 +1,48 @@ #!/bin/sh +NVIM_STATE="$HOME/.local/share/nvim/theme.json" + current_theme=$(grep import "$ALACRITTY_PATH"/theme.toml | sed 's/.*\///;s/\.toml.*//') +# Alacritty and nvim use different names for some themes, so we translate before syncing. +# tokyonight_* -> tokyonight-* (alacritty uses underscores, nvim uses dashes) +# rose-pine -> rose-pine-moon (base variant has no nvim equivalent, moon is closest) new_theme=$( find "$ALACRITTY_THEME_DIR_PATH" -maxdepth 1 -type f -name '*.toml' -exec basename {} .toml \; \ | sort \ | fzf --tmux 60% \ - --preview "alacritty-theme {} > /dev/null && bat --color=always \"$ALACRITTY_THEME_DIR_PATH\"/{}.toml" + --preview " + alacritty-theme {} > /dev/null 2>&1 + _s=\$(echo {} | sed 's/tokyonight_/tokyonight-/; s/^rose-pine$/rose-pine-moon/') + case \"\$_s\" in + rose-pine-moon|rose-pine-dawn|\ + catppuccin-latte|catppuccin-frappe|catppuccin-macchiato|catppuccin-mocha|\ + nord|\ + tokyonight-night|tokyonight-storm|tokyonight-day|tokyonight-moon) + printf '{\"colorscheme\":\"%s\"}' \"\$_s\" > \"$NVIM_STATE\";; + esac + bat --color=always \"$ALACRITTY_THEME_DIR_PATH\"/{}.toml + " ) +sync_nvim() { + _s=$(echo "$1" | sed 's/tokyonight_/tokyonight-/; s/^rose-pine$/rose-pine-moon/') + case "$_s" in + rose-pine-moon|rose-pine-dawn|\ + catppuccin-latte|catppuccin-frappe|catppuccin-macchiato|catppuccin-mocha|\ + nord|\ + tokyonight-night|tokyonight-storm|tokyonight-day|tokyonight-moon) + _tmp=$(mktemp "${NVIM_STATE}.XXXXXX") + printf '{"colorscheme":"%s"}' "$_s" > "$_tmp" && mv "$_tmp" "$NVIM_STATE" + ;; + esac +} + if [ -z "$new_theme" ]; then - echo "Theme not selected." alacritty-theme "$current_theme" + sync_nvim "$current_theme" exit 1 fi alacritty-theme "$new_theme" +sync_nvim "$new_theme" diff --git a/claude/.claude/skills/nvim-ctx/SKILL.md b/claude/.claude/skills/nvim-ctx/SKILL.md new file mode 100644 index 0000000..5adaf78 --- /dev/null +++ b/claude/.claude/skills/nvim-ctx/SKILL.md @@ -0,0 +1,33 @@ +--- +name: nvim-ctx +description: Fetch the current neovim context (open file, cursor position, and active selection) from a running nvim instance, then act on any instructions the user passed alongside the invocation. Trigger when the user types `/nvim-ctx` with or without arguments. +--- + +# nvim-ctx + +## Parsing args + +Args follow this shape: `[--session ] [instructions...]` + +- If args begins with `--session `, extract `` as the tmux session to target and treat the remainder as instructions. +- Otherwise, use no session argument (defaults to current tmux session) and treat all args as instructions. + +## Fetching context + +Run: + +```sh +nvim-ctx [] +``` + +It outputs JSON with `file`, `start_line`, `end_line`, and `text` (active visual selection if one exists, otherwise the full buffer). + +**If `nvim-ctx` fails** (no nvim pane found, nvim not running, socket not found): tell the user briefly what failed and ask them to paste the relevant code instead. Do not proceed as if you have context you don't have. + +## After fetching + +If instructions were provided, use the context to address them directly — the fetched file/selection is the subject. + +If no instructions were provided, briefly surface what you found (file path, line range, whether it's a selection or full buffer) and wait for the user to tell you what they want. + +Do not re-read the file with the Read tool just because you have the path — the JSON output already contains the text. Only reach for Read if you need lines outside the reported range. diff --git a/nvim/.config/nvim/after/plugin/starter.lua b/nvim/.config/nvim/after/plugin/starter.lua index 2b68bd6..1bcbeee 100644 --- a/nvim/.config/nvim/after/plugin/starter.lua +++ b/nvim/.config/nvim/after/plugin/starter.lua @@ -38,7 +38,7 @@ require("mini.starter").setup({ section = "Explorer", }, -- Configuration - { name = "Theme", action = ":Themery", section = "Config" }, + { name = "Theme", action = ":lua vim.g.theme_picker()", section = "Config" }, { name = "Pack Update", action = ":lua vim.pack.update()", section = "Config" }, { name = "Check Health", action = ":checkhealth", section = "Config" }, -- Neovim diff --git a/nvim/.config/nvim/after/plugin/theme.lua b/nvim/.config/nvim/after/plugin/theme.lua index 1631ec0..af2a072 100644 --- a/nvim/.config/nvim/after/plugin/theme.lua +++ b/nvim/.config/nvim/after/plugin/theme.lua @@ -8,94 +8,157 @@ require("tokyonight") require("nord").setup({}) local themes = { - { - name = "rose-pine-moon", - colorscheme = "rose-pine-moon", - after = [[ - os.execute('alacritty-theme rose-pine-moon > /dev/null 2>&1') - ]], - }, - { - name = "rose-pine-dawn", - colorscheme = "rose-pine-dawn", - after = [[ - os.execute('alacritty-theme rose-pine-dawn > /dev/null 2>&1') - ]], - }, - { - name = "catppuccin-latte", - colorscheme = "catppuccin-latte", - after = [[ - os.execute('alacritty-theme catppuccin-latte > /dev/null 2>&1') - ]], - }, - { - name = "catppuccin-frappe", - colorscheme = "catppuccin-frappe", - after = [[ - os.execute('alacritty-theme catppuccin-frappe > /dev/null 2>&1') - ]], - }, - { - name = "catppuccin-macchiato", - colorscheme = "catppuccin-macchiato", - after = [[ - os.execute('alacritty-theme catppuccin-macchiato > /dev/null 2>&1') - ]], - }, - { - name = "catppuccin-mocha", - colorscheme = "catppuccin-mocha", - after = [[ - os.execute('alacritty-theme catppuccin-mocha > /dev/null 2>&1') - ]], - }, - { - name = "nord", - colorscheme = "nord", - after = [[ - os.execute('alacritty-theme nord > /dev/null 2>&1') - ]], - }, - { - name = "tokyonight-night", - colorscheme = "tokyonight-night", - after = [[ - vim.schedule(function() vim.cmd("colorscheme tokyonight-night") end) - os.execute('alacritty-theme tokyonight_night > /dev/null 2>&1') - ]], - }, - { - name = "tokyonight-storm", - colorscheme = "tokyonight-storm", - after = [[ - vim.schedule(function() vim.cmd("colorscheme tokyonight-storm") end) - os.execute('alacritty-theme tokyonight_storm > /dev/null 2>&1') - ]], - }, - { - name = "tokyonight-day", - colorscheme = "tokyonight-day", - after = [[ - vim.schedule(function() vim.cmd("colorscheme tokyonight-day") end) - os.execute('alacritty-theme tokyonight_day > /dev/null 2>&1') - ]], - }, - { - name = "tokyonight-moon", - colorscheme = "tokyonight-moon", - after = [[ - vim.schedule(function() vim.cmd("colorscheme tokyonight-moon") end) - os.execute('alacritty-theme tokyonight_moon > /dev/null 2>&1') - ]], - }, + "rose-pine-moon", + "rose-pine-dawn", + "catppuccin-latte", + "catppuccin-frappe", + "catppuccin-macchiato", + "catppuccin-mocha", + "nord", + "tokyonight-night", + "tokyonight-storm", + "tokyonight-day", + "tokyonight-moon", } -require("themery").setup({ - themes = themes, - livePreview = true, +-- Alacritty uses underscores for tokyonight; everything else matches nvim's name. +local alacritty_map = { + ["tokyonight-night"] = "tokyonight_night", + ["tokyonight-storm"] = "tokyonight_storm", + ["tokyonight-day"] = "tokyonight_day", + ["tokyonight-moon"] = "tokyonight_moon", +} + +local state_file = vim.fn.stdpath("data") .. "/theme.json" + +local function apply_alacritty(scheme) + local name = alacritty_map[scheme] or scheme + vim.fn.jobstart({ "alacritty-theme", name }, { detach = true }) +end + +local function save_theme(scheme) + local f = io.open(state_file, "w") + if f then + f:write(vim.fn.json_encode({ colorscheme = scheme })) + f:close() + end +end + +local function load_theme() + local f = io.open(state_file, "r") + if not f then + -- Migrate from themery's state file on first run. + f = io.open(vim.fn.stdpath("data") .. "/themery/state.json", "r") + end + if not f then + return + end + local content = f:read("*a") + f:close() + local ok, data = pcall(vim.fn.json_decode, content) + if ok and data and data.colorscheme then + pcall(vim.cmd, "colorscheme " .. data.colorscheme) + end +end + +local function open_picker() + local original = vim.g.colors_name + + local function restore() + if original then + pcall(vim.cmd, "colorscheme " .. original) + end + end + + local pickers = require("telescope.pickers") + local finders = require("telescope.finders") + local conf = require("telescope.config").values + local actions = require("telescope.actions") + local action_state = require("telescope.actions.state") + + local function apply_selected() + local sel = action_state.get_selected_entry() + if sel then + pcall(vim.cmd, "colorscheme " .. sel.value) + end + end + + pickers + .new({}, { + prompt_title = "Theme", + finder = finders.new_table({ results = themes }), + sorter = conf.generic_sorter({}), + attach_mappings = function(prompt_bufnr, map) + local function nav_next() + actions.move_selection_next(prompt_bufnr) + apply_selected() + end + + local function nav_prev() + actions.move_selection_previous(prompt_bufnr) + apply_selected() + end + + local function confirm() + local sel = action_state.get_selected_entry() + actions.close(prompt_bufnr) + if sel then + pcall(vim.cmd, "colorscheme " .. sel.value) + save_theme(sel.value) + else + restore() + end + end + + local function cancel() + actions.close(prompt_bufnr) + restore() + end + + map("i", "", nav_next) + map("i", "", nav_next) + map("n", "j", nav_next) + map("n", "", nav_next) + map("i", "", nav_prev) + map("i", "", nav_prev) + map("n", "k", nav_prev) + map("n", "", nav_prev) + map("i", "", confirm) + map("n", "", confirm) + map("i", "", cancel) + map("n", "", cancel) + map("i", "", cancel) + map("n", "q", cancel) + + return true + end, + }) + :find() +end + +-- Expose for mini.starter and other callers. +vim.g.theme_picker = open_picker + +vim.api.nvim_create_autocmd("ColorScheme", { + callback = function(ev) + apply_alacritty(ev.match) + end, }) -vim.keymap.set("n", "t", function() - vim.cmd("Themery") -end, { desc = "Theme picker (Themery)" }) +load_theme() + +-- Reload when alacritty-theme-select writes theme.json externally. +local watcher = vim.uv.new_fs_event() +if watcher then + watcher:start( + state_file, + {}, + vim.schedule_wrap(function(err) + if not err then + load_theme() + end + end) + ) +end + +vim.keymap.set("n", "t", open_picker, { desc = "Theme picker" }) diff --git a/nvim/.config/nvim/lua/greg/pack.lua b/nvim/.config/nvim/lua/greg/pack.lua index 2724fa6..f351027 100644 --- a/nvim/.config/nvim/lua/greg/pack.lua +++ b/nvim/.config/nvim/lua/greg/pack.lua @@ -61,7 +61,6 @@ vim.pack.add({ { src = gh("nvim-telescope/telescope.nvim"), version = "master" }, { src = gh("ThePrimeagen/harpoon"), version = "master" }, - { src = gh("zaldih/themery.nvim"), version = "main" }, { src = gh("xiyaowong/nvim-transparent"), version = "main" }, { src = gh("rose-pine/neovim"), name = "rose-pine", version = "main" }, diff --git a/zsh/.local/bin/nvim-ctx b/zsh/.local/bin/nvim-ctx index 0654420..0649b77 100755 --- a/zsh/.local/bin/nvim-ctx +++ b/zsh/.local/bin/nvim-ctx @@ -36,4 +36,14 @@ if [ -z "$socket" ]; then exit 1 fi -nvim --server "$socket" --remote-expr 'v:lua.NvimCtxJSON()' +nvim --server "$socket" --remote-expr 'v:lua.NvimCtxJSON()' & +_bg=$! +( sleep 5; kill "$_bg" 2>/dev/null ) & +_watcher=$! +wait "$_bg" +_exit=$? +kill "$_watcher" 2>/dev/null +if [ "$_exit" -ne 0 ]; then + printf 'nvim-ctx: timed out or failed connecting to nvim socket\n' >&2 + exit 1 +fi