Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cursor/rules/greg-workflow-preferences.mdc
Original file line number Diff line number Diff line change
@@ -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
---

Expand Down
34 changes: 32 additions & 2 deletions alacritty/.local/bin/alacritty-theme-select
Original file line number Diff line number Diff line change
@@ -1,18 +1,48 @@
#!/bin/sh

NVIM_STATE="$HOME/.local/share/nvim/theme.json"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make theme.json writes resilient (ensure directory + atomic replace).

Line 22 and Line 35 write directly to "$NVIM_STATE" with >. If ~/.local/share/nvim doesn’t exist, sync fails; and truncate-in-place can briefly expose invalid JSON to Neovim’s file watcher.

Suggested fix
 NVIM_STATE="$HOME/.local/share/nvim/theme.json"
+mkdir -p "$(dirname "$NVIM_STATE")"
+
+write_nvim_state() {
+  _tmp="$(mktemp "${NVIM_STATE}.tmp.XXXXXX")" || return 1
+  printf '{"colorscheme":"%s"}' "$1" > "$_tmp" && mv "$_tmp" "$NVIM_STATE"
+}
@@
-                printf '{"colorscheme":"%s"}' "$_s" > "$NVIM_STATE";;
+                write_nvim_state "$_s";;
@@
-      printf '{"colorscheme":"%s"}' "$_s" > "$NVIM_STATE"
+      write_nvim_state "$_s"
       ;;

Also applies to: 22-23, 35-35

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@alacritty/.local/bin/alacritty-theme-select` at line 3, The NVIM_STATE writes
at lines 22-23 and 35 are not resilient because they directly write to the file
without ensuring the parent directory exists, and they use non-atomic
truncation-in-place writes with `>`. To fix this, before any write operations to
NVIM_STATE, ensure the parent directory ~/.local/share/nvim exists by creating
it with mkdir -p. Then replace the direct `>` redirections with atomic writes:
write to a temporary file in the same directory, then use mv to atomically
replace the original file. This prevents failures when the directory doesn't
exist and ensures Neovim's file watcher never sees invalid 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"
33 changes: 33 additions & 0 deletions claude/.claude/skills/nvim-ctx/SKILL.md
Original file line number Diff line number Diff line change
@@ -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 <name>] [instructions...]`

- If args begins with `--session <name>`, extract `<name>` 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 [<session>]
```

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.
2 changes: 1 addition & 1 deletion nvim/.config/nvim/after/plugin/starter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
237 changes: 150 additions & 87 deletions nvim/.config/nvim/after/plugin/theme.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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", "<C-n>", nav_next)
map("i", "<Down>", nav_next)
map("n", "j", nav_next)
map("n", "<Down>", nav_next)
map("i", "<C-p>", nav_prev)
map("i", "<Up>", nav_prev)
map("n", "k", nav_prev)
map("n", "<Up>", nav_prev)
map("i", "<CR>", confirm)
map("n", "<CR>", confirm)
map("i", "<Esc>", cancel)
map("n", "<Esc>", cancel)
map("i", "<C-c>", 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", "<leader>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", "<leader>t", open_picker, { desc = "Theme picker" })
1 change: 0 additions & 1 deletion nvim/.config/nvim/lua/greg/pack.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
12 changes: 11 additions & 1 deletion zsh/.local/bin/nvim-ctx
Original file line number Diff line number Diff line change
Expand Up @@ -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