diff --git a/bin/gc b/bin/gc index 70ab993..9a898dc 100755 --- a/bin/gc +++ b/bin/gc @@ -2,6 +2,7 @@ # Conventional commit helper # Usage: gc [type] [message] [-s scope] [-b] [-B body] +# gc -e [type] (open in editor with template) # gc (interactive mode) set -o errexit @@ -10,14 +11,23 @@ set -o nounset TYPES="feat fix docs style refactor test chore perf ci build" usage() { - echo "Usage: gc [type] [message] [-s scope] [-b] [-B body]" + echo "Usage: gc [type] [message] [--scope scope] [-b] [-B body]" + echo ' gc -e [type] # open in $EDITOR' echo " gc (interactive mode)" echo "" echo "Options:" - echo " -s, --scope Scope of the change" + echo ' -e, --edit Open commit message in $EDITOR' + echo " --scope Scope of the change" echo " -b, --breaking Mark as breaking change (!)" echo " -B, --body Commit body (longer description)" echo "" + echo "Git passthrough flags:" + echo " -s, --signoff Add Signed-off-by trailer" + echo " -a, --all Stage all modified files" + echo " -S, --gpg-sign GPG sign the commit" + echo " -v, --verbose Show diff in editor" + echo " --amend Amend previous commit" + echo "" echo "Types: $TYPES" echo "" echo "Examples:" @@ -26,9 +36,46 @@ usage() { echo " gc feat 'new api' -b # feat!: new api" echo " gc feat 'new api' -s auth -b # feat(auth)!: new api" echo " gc feat 'big change' -B 'Details here'" + echo ' gc -e feat # edit feat commit in $EDITOR' exit 1 } +editor_mode() { + local type="$1" + shift + local git_args=("$@") + local template + + template=$(mktemp) + trap "rm -f $template" EXIT + + cat >"$template" <<'EOF' + +# Conventional Commit Format: +# ()!: +# +# [optional body] +# +# [optional footer(s)] +# +# Types: feat fix docs style refactor test chore perf ci build +# Add ! before : for breaking changes (e.g., feat!: or feat(api)!:) +# +# Examples: +# feat: add user authentication +# fix(api): resolve null pointer exception +# feat(auth)!: change token format +# docs: update API documentation +EOF + + # Prepend type if provided + if [ -n "$type" ]; then + sed -i.bak "1s/^/$type: /" "$template" && rm -f "$template.bak" + fi + + git commit -e -t "$template" "${git_args[@]}" +} + build_message() { local type="$1" local scope="$2" @@ -85,10 +132,16 @@ msg="" scope="" breaking="false" body="" +use_editor="false" +git_args=() while [ $# -gt 0 ]; do case "$1" in - -s | --scope) + -e | --edit) + use_editor="true" + shift + ;; + --scope) scope="$2" shift 2 ;; @@ -103,6 +156,15 @@ while [ $# -gt 0 ]; do -h | --help) usage ;; + # Pass through git commit flags + -s | --signoff | -a | --all | -v | --verbose | -n | --no-verify | --amend | --no-edit) + git_args+=("$1") + shift + ;; + -S | --gpg-sign) + git_args+=("$1") + shift + ;; *) if [ -z "$type" ]; then type="$1" @@ -114,6 +176,12 @@ while [ $# -gt 0 ]; do esac done +# Editor mode +if [ "$use_editor" = "true" ]; then + editor_mode "$type" "${git_args[@]}" + exit 0 +fi + # Validate type if ! echo "$TYPES" | grep -qw "$type"; then echo "Invalid type: $type" @@ -127,4 +195,4 @@ if [ -z "$msg" ]; then fi commit_msg=$(build_message "$type" "$scope" "$breaking" "$msg" "$body") -git commit -m "$commit_msg" +git commit -m "$commit_msg" "${git_args[@]}" diff --git a/config/git/config b/config/git/config new file mode 100644 index 0000000..60d5eba --- /dev/null +++ b/config/git/config @@ -0,0 +1,39 @@ +[user] + name = Ray Andrew + email = rs@rs.ht + signingkey = ~/.ssh/id_ed25519.pub + +[gpg] + format = ssh + +[core] + editor = nvim + +[credential] + helper = store --file ~/.git-credentials + +[pull] + rebase = true + +[github] + user = rayandrew + +[commit] + gpgsign = true + +[format] + signoff = true + +[lfs] + repositoryformatversion = 0 + +[filter "lfs"] + clean = git-lfs clean -- %f + smudge = git-lfs smudge -- %f + process = git-lfs filter-process + required = true + +[alias] + c = !~/dotfiles/bin/gc -s + cc = !~/dotfiles/bin/gc-check + cl = !~/dotfiles/bin/gc-changelog diff --git a/config/git/ignore b/config/git/ignore new file mode 100644 index 0000000..6fd41c0 --- /dev/null +++ b/config/git/ignore @@ -0,0 +1,3 @@ +.DS_Store +*~ +*.swp diff --git a/config/nvim/init.lua b/config/nvim/init.lua index 8045d11..d7a7b1a 100644 --- a/config/nvim/init.lua +++ b/config/nvim/init.lua @@ -57,6 +57,9 @@ add 'aserowy/tmux.nvim' add 'NMAC427/guess-indent.nvim' add 'wakatime/vim-wakatime' add 'OXY2DEV/markview.nvim' +add { source = 'R-nvim/R.nvim', depends = { 'nvim-treesitter/nvim-treesitter' } } +add 'nvim-treesitter/nvim-treesitter' +add 'nvim-treesitter/nvim-treesitter-textobjects' -- color themes add 'EdenEast/nightfox.nvim' @@ -279,6 +282,112 @@ later(function() } require('tmux').setup {} + + -- Ensure treesitter parsers are installed + require('nvim-treesitter.configs').setup { + ensure_installed = { + 'r', + 'rnoweb', + 'lua', + 'python', + 'rust', + 'c', + 'cpp', + 'javascript', + 'typescript', + 'tsx', + 'json', + 'yaml', + 'html', + 'css', + 'markdown', + 'markdown_inline', + 'nix', + 'bash', + 'vim', + 'vimdoc', + 'query', + 'diff', + 'git_rebase', + 'gitcommit', + }, + auto_install = true, + highlight = { enable = true }, + incremental_selection = { + enable = true, + keymaps = { + init_selection = 'vv', + node_incremental = '', + scope_incremental = '', + node_decremental = '', + }, + }, + textobjects = { + select = { + enable = true, + lookahead = true, + keymaps = { + ['af'] = { query = '@function.outer', desc = 'Select outer function' }, + ['if'] = { query = '@function.inner', desc = 'Select inner function' }, + ['ac'] = { query = '@class.outer', desc = 'Select outer class' }, + ['ic'] = { query = '@class.inner', desc = 'Select inner class' }, + ['aa'] = { query = '@parameter.outer', desc = 'Select outer argument' }, + ['ia'] = { query = '@parameter.inner', desc = 'Select inner argument' }, + ['ai'] = { query = '@conditional.outer', desc = 'Select outer conditional' }, + ['ii'] = { query = '@conditional.inner', desc = 'Select inner conditional' }, + ['al'] = { query = '@loop.outer', desc = 'Select outer loop' }, + ['il'] = { query = '@loop.inner', desc = 'Select inner loop' }, + ['ab'] = { query = '@block.outer', desc = 'Select outer block' }, + ['ib'] = { query = '@block.inner', desc = 'Select inner block' }, + }, + }, + move = { + enable = true, + set_jumps = true, + goto_next_start = { + [']f'] = { query = '@function.outer', desc = 'Next function start' }, + [']c'] = { query = '@class.outer', desc = 'Next class start' }, + [']a'] = { query = '@parameter.inner', desc = 'Next argument' }, + }, + goto_next_end = { + [']F'] = { query = '@function.outer', desc = 'Next function end' }, + [']C'] = { query = '@class.outer', desc = 'Next class end' }, + }, + goto_previous_start = { + ['[f'] = { query = '@function.outer', desc = 'Previous function start' }, + ['[c'] = { query = '@class.outer', desc = 'Previous class start' }, + ['[a'] = { query = '@parameter.inner', desc = 'Previous argument' }, + }, + goto_previous_end = { + ['[F'] = { query = '@function.outer', desc = 'Previous function end' }, + ['[C'] = { query = '@class.outer', desc = 'Previous class end' }, + }, + }, + swap = { + enable = true, + swap_next = { + ['a'] = { query = '@parameter.inner', desc = 'Swap with next argument' }, + }, + swap_previous = { + ['A'] = { query = '@parameter.inner', desc = 'Swap with previous argument' }, + }, + }, + }, + } + + require('r').setup { + hook = { + on_filetype = function() + vim.keymap.set('n', 'rs', 'RStart', { buffer = true, desc = 'Start R' }) + vim.keymap.set('n', 'rq', 'RClose', { buffer = true, desc = 'Close R' }) + vim.keymap.set('n', 'rl', 'RSendLine', { buffer = true, desc = 'Send line to R' }) + vim.keymap.set('v', 'rs', 'RSendSelection', { buffer = true, desc = 'Send selection to R' }) + vim.keymap.set('n', 'rf', 'RSendFile', { buffer = true, desc = 'Send file to R' }) + vim.keymap.set('n', 'ro', 'RShowArgs', { buffer = true, desc = 'Show function args' }) + vim.keymap.set('n', 'rh', 'RHelp', { buffer = true, desc = 'R help' }) + end, + }, + } end) later(function() @@ -530,8 +639,15 @@ later(function() root_markers = { '.marksman.toml', '.git', '.editorconfig' }, } + -- R + vim.lsp.config.r_language_server = { + cmd = { 'R', '--slave', '-e', 'languageserver::run()' }, + filetypes = { 'r', 'rmd' }, + root_markers = { '.Rproj', 'DESCRIPTION', '.git' }, + } + -- Enable language servers - vim.lsp.enable { 'lua_ls', 'ts_ls', 'pyright', 'rust_analyzer', 'clangd', 'copilot', 'nixd', 'marksman' } + vim.lsp.enable { 'lua_ls', 'ts_ls', 'pyright', 'rust_analyzer', 'clangd', 'copilot', 'nixd', 'marksman', 'r_language_server' } end) local map = function(mode, lhs, rhs, opts) @@ -650,3 +766,22 @@ vim.api.nvim_create_autocmd({ 'FocusGained', 'BufEnter' }, { command = "if mode() != 'c' | checktime | endif", pattern = '*', }) + +-- Restore gitrebase keybindings (overridden by clipboard=unnamedplus) +vim.api.nvim_create_autocmd('FileType', { + pattern = 'gitrebase', + callback = function() + local opts = { buffer = true, silent = true } + vim.keymap.set('n', 'p', 'Pick', opts) + vim.keymap.set('n', 'r', 'Reword', opts) + vim.keymap.set('n', 'e', 'Edit', opts) + vim.keymap.set('n', 's', 'Squash', opts) + vim.keymap.set('n', 'f', 'Fixup', opts) + vim.keymap.set('n', 'd', 'Drop', opts) + vim.keymap.set('n', 'x', 'Exec', opts) + vim.keymap.set('n', '', 'move +1', opts) + vim.keymap.set('n', '', 'move -2', opts) + vim.keymap.set('n', '', 'move +1', opts) + vim.keymap.set('n', '', 'move -2', opts) + end, +}) diff --git a/config/nvim/lua/raytheme.lua b/config/nvim/lua/raytheme.lua index 1a75387..dcb18f8 100644 --- a/config/nvim/lua/raytheme.lua +++ b/config/nvim/lua/raytheme.lua @@ -439,7 +439,7 @@ local function apply_highlights(c, opts) hi('MarkviewCode', { bg = c.bg_dark }) hi('MarkviewCodeInfo', { fg = c.comment }) hi('MarkviewCodeFg', { fg = c.fg }) - hi('MarkviewInlineCode', { fg = c.string, bg = c.bg_dark }) + hi('MarkviewInlineCode', { fg = c.string, bg = c.bg_float }) -- Lists hi('MarkviewListItemStar', { fg = c.tag }) diff --git a/docs/nvim.md b/docs/nvim.md new file mode 100644 index 0000000..59acca2 --- /dev/null +++ b/docs/nvim.md @@ -0,0 +1,244 @@ +# Neovim Configuration + +Leader key: `` + +## Files & Buffers + + +| Key | Action | +|-----|--------| +| `ex` | Toggle file tree | +| `fs` | Save file | +| `ff` | Find file | +| `fr` | Find recent file | +| `sf` | Search git files | +| `bb` | List buffers | +| `` | List buffers | +| `bd` | Delete buffer | +| `.` | Scratch buffer | + + +## Search + + +| Key | Action | +|-----|--------| +| `sg` | Search grep | +| `sw` | Search grep word | +| `sb` | Buffer lines | +| `sB` | Grep open buffers | +| `:` | Command history | + + +## LSP + + +| Key | Action | +|-----|--------| +| `gd` | Goto definition | +| `gD` | Goto declaration | +| `gr` | References | +| `gI` | Goto implementation | +| `gy` | Goto type definition | +| `gai` | Incoming calls | +| `gao` | Outgoing calls | +| `K` | Hover documentation | +| `rn` | Rename symbol | +| `ca` | Code action | +| `d` | Show diagnostic | +| `[d` / `]d` | Previous / next diagnostic | +| `ls` | LSP symbols | +| `lS` | LSP workspace symbols | + + +## Git + + +| Key | Action | +|-----|--------| +| `gg` | Open Neogit | +| `gb` | Git branches | +| `gl` | Git log | +| `gL` | Git log (file) | +| `gs` | Git status | +| `gS` | Git stash | +| `gd` | Git diff (hunks) | +| `gi` | GitHub issues (open) | +| `gI` | GitHub issues (all) | +| `gp` | GitHub PRs (open) | +| `gP` | GitHub PRs (all) | + + +## Grapple (Harpoon-style marks) + + +| Key | Action | +|-----|--------| +| `ma` | Toggle tag | +| `mm` | Tags menu | +| `m1-4` | Select tag 1-4 | +| `mn` | Next tag | +| `mp` | Previous tag | +| `mga` | Add global tag | +| `mgm` | Global tags menu | + + +## Claude Code + + +| Key | Action | +|-----|--------| +| `ac` | Toggle Claude | +| `af` | Focus Claude | +| `ar` | Resume Claude | +| `aC` | Continue Claude | +| `am` | Select Claude model | +| `ab` | Add current buffer | +| `as` | Send to Claude (visual) | +| `aa` | Accept diff | +| `ad` | Deny diff | + + +## Code / Config + + +| Key | Action | +|-----|--------| +| `cc` | Compile | +| `cC` | Recompile | +| `cf` | Format buffer | +| `cr` | Reload config | +| `ce` | Edit config | +| `ch` | Check health | + + +## Windows + + +| Key | Action | +|-----|--------| +| `wh/j/k/l` | Navigate windows | +| `ws` | Split horizontal | +| `wv` | Split vertical | +| `wq` | Close window | +| `` | Move to tmux pane | + + +## Quickfix + + +| Key | Action | +|-----|--------| +| `qq` | Open/focus quickfix | +| `ql` | Toggle loclist | +| `>` | Expand quickfix context | +| `<` | Collapse quickfix context | + + +## Utilities + + +| Key | Action | +|-----|--------| +| `ut` | Toggle undotree | +| `zz` | Toggle zen mode | +| `zm` | Toggle zoom | + + +## R (in R files) + + +| Key | Action | +|-----|--------| +| `rs` | Start R | +| `rq` | Close R | +| `rl` | Send line to R | +| `rs` | Send selection to R (visual) | +| `rf` | Send file to R | +| `ro` | Show function args | +| `rh` | R help | + + +## Treesitter + +### Incremental Selection + +Select code based on AST nodes - expand or shrink selection intelligently. + + +| Key | Action | +|-----|--------| +| `vv` | Start treesitter selection | +| `` | Expand to larger node | +| `` | Shrink to smaller node | +| `` | Expand to full scope | + + +### Text Objects + +Use with operators like `v` (visual), `d` (delete), `c` (change), `y` (yank). + + +| Key | Description | +|-----|-------------| +| `af` / `if` | Outer / inner function | +| `ac` / `ic` | Outer / inner class | +| `aa` / `ia` | Outer / inner argument | +| `ai` / `ii` | Outer / inner conditional | +| `al` / `il` | Outer / inner loop | +| `ab` / `ib` | Outer / inner block | + + +Examples: + +- `vaf` - select entire function +- `dif` - delete function body +- `caa` - change an argument +- `yic` - yank inner class + +### Movement + +Jump between functions, classes, and arguments. + + +| Key | Action | +|-----|--------| +| `]f` / `[f` | Next / previous function start | +| `]F` / `[F` | Next / previous function end | +| `]c` / `[c` | Next / previous class start | +| `]C` / `[C` | Next / previous class end | +| `]a` / `[a` | Next / previous argument | + + +### Swap + + +| Key | Action | +|-----|--------| +| `a` | Swap argument with next | +| `A` | Swap argument with previous | + + +## Git Rebase (in gitrebase files) + + +| Key | Action | +|-----|--------| +| `p` | Pick | +| `r` | Reword | +| `e` | Edit | +| `s` | Squash | +| `f` | Fixup | +| `d` | Drop | +| `x` | Exec | +| `` / `` | Move line down | +| `` / `` | Move line up | + + +## Terminal + + +| Key | Action | +|-----|--------| +| `` | Exit terminal mode | + diff --git a/flake.nix b/flake.nix index a1655b8..fa9ef13 100644 --- a/flake.nix +++ b/flake.nix @@ -148,6 +148,7 @@ treefmtEval.${system}.config.build.wrapper ]; packages = with pkgs; [ + git sops age ssh-to-age diff --git a/home/git.nix b/home/git.nix index 46d411d..b8c3901 100644 --- a/home/git.nix +++ b/home/git.nix @@ -1,52 +1,16 @@ { pkgs, config, - user, + dots, ... }: -let - home = config.home.homeDirectory; -in { home.packages = with pkgs; [ gh + git-lfs ]; - programs.git = { - enable = true; - lfs = { - enable = true; - }; - settings = { - user = { - name = "Ray Andrew"; - email = "rs@rs.ht"; - }; - gpg.format = "ssh"; - core.editor = "emacs"; - credential.helper = "store --file ${home}/.git-credentials"; - pull.rebase = true; - github.user = user; - }; - signing = { - key = "${home}/.ssh/id_ed25519.pub"; - signByDefault = true; - signer = ""; - }; - ignores = [ - ".DS_Store" - "*~" - "*.swp" - ]; + xdg.configFile = { + "git".source = config.lib.file.mkOutOfStoreSymlink "${dots}/config/git"; }; - - # programs.gh = { - # enable = true; - # settings = { - # git_protocol = "ssh"; - # }; - # extensions = with pkgs; [ - # gh-copilot - # ]; - # }; }