From 0a1caa97962a48250eb5c368f74fc9b5442c7c78 Mon Sep 17 00:00:00 2001 From: Chiro Hiro Date: Fri, 29 May 2026 15:45:58 +0700 Subject: [PATCH 1/3] Parse . backup suffix in find_latest_backup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same-second re-installs append a . to the .bak. name to avoid collisions, but the restore path only parsed the bare epoch — so on a same-second collision it compared the whole "." string against the integer regex, rejected it, and could restore the wrong (or no) backup. Split the suffix into and , validate both as integers, and break ties on the counter so uninstall restores the truly newest backup. Co-Authored-By: Claude Opus 4.8 (1M context) --- get-ivim.sh | 14 +++++++++++--- install.sh | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/get-ivim.sh b/get-ivim.sh index a84bd74..7c44649 100755 --- a/get-ivim.sh +++ b/get-ivim.sh @@ -75,17 +75,25 @@ find_latest_backup() { # shared-HOME system. local latest="" local latest_ts=0 + local latest_ctr=0 shopt -s nullglob for f in "${target}.bak."*; do - local ts="${f##*.bak.}" - if ! [[ "$ts" =~ ^[0-9]+$ ]]; then + # Suffix is or, for same-second collisions, .. + # Both components must be integers — anything else is a crafted name. + local suffix="${f##*.bak.}" + local ts="${suffix%%.*}" + local ctr="${suffix#*.}" + [ "$ctr" = "$suffix" ] && ctr=0 + if ! [[ "$ts" =~ ^[0-9]+$ ]] || ! [[ "$ctr" =~ ^[0-9]+$ ]]; then continue fi if [ ! -O "$f" ]; then continue fi - if [ "$ts" -gt "$latest_ts" ]; then + if [ "$ts" -gt "$latest_ts" ] \ + || { [ "$ts" -eq "$latest_ts" ] && [ "$ctr" -gt "$latest_ctr" ]; }; then latest_ts="$ts" + latest_ctr="$ctr" latest="$f" fi done diff --git a/install.sh b/install.sh index 8cf6a56..d2b7fd2 100755 --- a/install.sh +++ b/install.sh @@ -72,17 +72,25 @@ find_latest_backup() { # on the next uninstall-and-restore). local latest="" local latest_ts=0 + local latest_ctr=0 shopt -s nullglob for f in "${target}.bak."*; do - local ts="${f##*.bak.}" - if ! [[ "$ts" =~ ^[0-9]+$ ]]; then + # Suffix is or, for same-second collisions, .. + # Both components must be integers — anything else is a crafted name. + local suffix="${f##*.bak.}" + local ts="${suffix%%.*}" + local ctr="${suffix#*.}" + [ "$ctr" = "$suffix" ] && ctr=0 + if ! [[ "$ts" =~ ^[0-9]+$ ]] || ! [[ "$ctr" =~ ^[0-9]+$ ]]; then continue fi if [ ! -O "$f" ]; then continue fi - if [ "$ts" -gt "$latest_ts" ]; then + if [ "$ts" -gt "$latest_ts" ] \ + || { [ "$ts" -eq "$latest_ts" ] && [ "$ctr" -gt "$latest_ctr" ]; }; then latest_ts="$ts" + latest_ctr="$ctr" latest="$f" fi done From a130703d8bd1fc5037293dc58817bf09715a4819 Mon Sep 17 00:00:00 2001 From: Chiro Hiro Date: Fri, 29 May 2026 15:46:25 +0700 Subject: [PATCH 2/3] Gate terminal cleanup on the last ordinary window via QuitPre MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VimLeavePre fires too late to pre-empt the E947 "job still running" check that blocks :qall when a terminal is open. Switch back to QuitPre, but guard it: count non-terminal ("ordinary") windows and only wipe terminal jobs when the window being quit is the last one. This threads between both failure modes — no spurious kills on a single-window or aborted :q, and no E947 on a real :qall exit. Update the CLAUDE.md Terminal note to match. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 2 +- plugin/keymaps.vim | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index acefe61..97a479b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -167,7 +167,7 @@ On Vim 8.2.1978+ the trigger uses `popup PopUp` so insert mode is prese - Uses `term_start(['bash', '--init-file', l:rcfile], {…})` with a list argument — NOT `:execute 'below terminal …'` — so paths containing spaces do not misparse (Vim's `:terminal` splits its command on whitespace regardless of quoting) - `terminal.bashrc` sources `/etc/profile` and `~/.bashrc` first (full user env inherited), then sets a green-user / cyan-host / blue-cwd / purple-branch prompt using a nerd-font branch glyph; detached HEAD renders as `detached:` - `terminal.bashrc` guards against recursive sourcing via `_IVIM_TERMINAL_SOURCED` so any `~/.bashrc` loop is a no-op on re-entry -- Auto-closes terminal buffers on quit (`QuitPre` autocommand) +- Auto-closes terminal buffers when Vim exits — `QuitPre` autocommand gated on the last non-terminal window, so a single-window or aborted `:q` never kills terminals, and `:qall` never trips `E947` (job still running) - `ivim_terminal_mouse` autogroup clears netrw mouse mappings that would otherwise leak into the terminal buffer - ANSI colors set via `g:terminal_ansi_colors` in colorscheme (guarded by `has('terminal')`) diff --git a/plugin/keymaps.vim b/plugin/keymaps.vim index c36ab27..7ba6d79 100644 --- a/plugin/keymaps.vim +++ b/plugin/keymaps.vim @@ -15,12 +15,27 @@ function! s:CloseTerminals() abort endfor endfunction -" VimLeavePre — only fires when Vim is actually exiting. QuitPre fired on -" every :q, so an aborted quit (e.g. unsaved changes in another window) -" would silently kill all running terminal jobs while Vim stayed open. +" Wipe terminal jobs only when Vim is actually about to exit — i.e. the +" window being quit is the last non-terminal ("ordinary") window. Cleaning +" unconditionally on QuitPre would kill terminals on a single-window or +" aborted quit while Vim stays open; VimLeavePre fires too late to pre-empt +" the E947 "job still running" check that blocks :qall. The last-ordinary- +" window gate threads between both: no spurious kills, no E947 on exit. +function! s:CloseTerminalsOnExit() abort + let l:ordinary = 0 + for l:win in getwininfo() + if getbufvar(l:win.bufnr, '&buftype') !=# 'terminal' + let l:ordinary += 1 + endif + endfor + if l:ordinary <= 1 + call s:CloseTerminals() + endif +endfunction + augroup ivim_terminal_cleanup autocmd! - autocmd VimLeavePre * call s:CloseTerminals() + autocmd QuitPre * call s:CloseTerminalsOnExit() augroup END " Exit insert mode when entering a non-modifiable buffer (e.g. via mouse From 8e1d8fcf858bef57167534d3768beb169128265e Mon Sep 17 00:00:00 2001 From: Chiro Hiro Date: Fri, 29 May 2026 15:46:33 +0700 Subject: [PATCH 3/3] Fix autocomplete trigger-char list in CLAUDE.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Autocomplete section illustrated triggers as ".", "::", "->", but the engine matches a single character against b:ivim_complete_triggers and no ftplugin registers multi-char strings — "::" was deliberately removed because it froze Rust/Lua/C++ completion. Replace the list with the actual single-char union across ftplugins: . > : < / $ and space. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 97a479b..693f7f3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -105,7 +105,7 @@ Other filetypes rely on Vim's default syntax → generic-group links (`String`, ## Autocomplete -IDE-style auto-completion via `plugin/autocomplete.vim`. As you type in insert mode, a popup appears automatically: word characters (≥2 prefix) trigger keyword completion (``) from the current file and other open buffers; filetype-configured trigger characters (`.`, `::`, `->`, etc.) trigger `omnifunc` (``). Disabled in prose filetypes (markdown, gitcommit, text, help) — those buffers get no `TextChangedI` autocmd attached at all. +IDE-style auto-completion via `plugin/autocomplete.vim`. As you type in insert mode, a popup appears automatically: word characters (≥2 prefix) trigger keyword completion (``) from the current file and other open buffers; filetype-configured trigger characters (single chars only — the union across ftplugins is `.`, `>`, `:`, `<`, `/`, `$`, and space) trigger `omnifunc` (``). Disabled in prose filetypes (markdown, gitcommit, text, help) — those buffers get no `TextChangedI` autocmd attached at all. ### Popup navigation