Skip to content

Conversation

@Milly
Copy link
Contributor

@Milly Milly commented Oct 19, 2025

The rename buffer's write command handler now uses a timer to defer window closing, ensuring only the rename buffer window is closed and not any adjacent windows when using :wq command.

Summary by CodeRabbit

  • Bug Fixes
    • File save operations now complete asynchronously, reducing UI interruptions and allowing proper window state updates before finalization.
    • Improved error handling for save failures to avoid partial finalization and reduce confusing UI states.
    • Overall more reliable and stable behavior after saving files, especially when multiple windows or buffers are involved.

@coderabbitai
Copy link

coderabbitai bot commented Oct 19, 2025

Walkthrough

BufWriteCmd in autoload/fern/internal/replacer.vim now defers Resolve(result) via a timer to run post-write logic asynchronously; errors return early. A new local function s:post_write(resolve, result, winid, bufnr) closes the target window (if needed) then calls Resolve(result).

Changes

Cohort / File(s) Summary
Async result resolution & window management
autoload/fern/internal/replacer.vim
Replaced direct synchronous Resolve(result) with a timer-scheduled deferred path; added early return on error; introduced s:post_write(resolve, result, winid, bufnr) to close target window (if matching) and then invoke Resolve(result)

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant BufWriteCmd
    participant Timer
    participant post_write
    participant UI

    User->>BufWriteCmd: Trigger write
    alt Error
        BufWriteCmd->>BufWriteCmd: Log error
        BufWriteCmd-->>User: Return (early)
    else Success
        BufWriteCmd->>BufWriteCmd: Compute result, capture winid/bufnr
        BufWriteCmd->>Timer: Schedule s:post_write(resolve, result, winid, bufnr)
        BufWriteCmd-->>User: Return (async pending)
        Timer->>post_write: Fire callback
        post_write->>UI: Close window if winid contains bufnr
        post_write->>post_write: Call Resolve(result)
        post_write-->>User: Complete
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

Poem

🐇 A tiny timer hops in place,

it waits a beat, then leaves no trace.
A window shuts, the result takes flight,
async and tidy through the night. ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "fix: prevent closing adjacent window when writing rename buffer with :wq" accurately reflects the main change in the pull request. The modifications introduce a timer-based deferred approach to window closing via a new s:post_write() function, which directly addresses the issue described in the title. The title is specific and clear, identifying both the problem (adjacent window closure) and its context (rename buffer with :wq command), avoiding vague terminology while remaining concise and scannable in a commit history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
autoload/fern/internal/replacer.vim (1)

107-111: Async resolution approach correctly defers window closure.

The timer-based deferral allows :wq to complete before custom window management occurs, solving the issue of closing adjacent windows. Setting nomodified ensures no unsaved changes prompt.

Consider adding a comment explaining why the timer is necessary:

   endtry
   set nomodified
   let l:Resolve = b:fern_replacer_resolve
   let winnr = winnr()
   let bufnr = bufnr()
+  " Defer window closure via timer to prevent :wq from closing adjacent windows
   call timer_start(0, { -> s:post_write(l:Resolve, result, winnr, bufnr) })
 endfunction
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88f6d81 and 0a9ab54.

📒 Files selected for processing (1)
  • autoload/fern/internal/replacer.vim (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build (windows-latest, stable)
🔇 Additional comments (3)
autoload/fern/internal/replacer.vim (3)

105-105: Good addition of explicit return after error handling.

This ensures the function exits properly after logging the error, preventing any subsequent code from executing.


116-116: Based on my verification, I found that the syntax execute winnr.'wincmd c' is a documented pattern for closing a window by number, but it has important caveats. This pattern won't work if the window isn't in the current tab page.

Looking at the code context on line 116, the function s:post_write already validates that the buffer exists in the window via winbufnr(a:winnr) == a:bufnr before attempting to close it. This suggests the developer is aware of window validation, though it doesn't explicitly check tab page constraints.

The syntax used in line 116 appears to be a valid (if quirky) VimScript pattern that is documented in community discussions, but developers working with this code should be aware of its tab page limitation.

Syntax is valid but has tab-page constraints.

The execute winnr.'wincmd c' pattern is a recognized approach for closing a specific window, but it won't work if the window isn't in the current tab page. The existing buffer check on line 115 provides partial validation, but ensure this code path only operates on windows in the active tab.


114-120: No changes needed—the code is correctly designed for the replacer use case.

The concerns raised are based on generic window manipulation patterns, but don't apply here:

  1. Multiple windows showing the buffer: The replacer is a temporary, single-purpose buffer used only for this replacement operation. It's never intentionally displayed in multiple windows, so closing only the specified window is the intended behavior.

  2. Error handling on window close: The winbufnr guard on line 115 guarantees the window exists and contains the expected buffer. If the check passes, wincmd c will succeed. This pattern is consistent throughout the codebase and sufficient for the use case.

Comment on lines 115 to 117
if winbufnr(a:winnr) == a:bufnr
execute a:winnr . 'wincmd c'
endif
Copy link
Owner

Choose a reason for hiding this comment

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

How about using win_execute instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Use win_execute and window-ID instead winnr.

if winbufnr(a:winnr) == a:bufnr
execute a:winnr . 'wincmd c'
endif
let Resolve = a:resolve
Copy link
Owner

Choose a reason for hiding this comment

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

Well, I'm not sure, but I think we need l: prefix here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added

The rename buffer's write command handler now uses a timer to defer
window closing, ensuring only the rename buffer window is closed
and not any adjacent windows when using :wq command.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
autoload/fern/internal/replacer.vim (1)

107-107: Consider using setlocal instead of set.

While set nomodified works in this buffer context, setlocal nomodified would be more precise and avoid any potential side effects on the global setting.

Apply this diff:

-  set nomodified
+  setlocal nomodified
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a9ab54 and 12f38fc.

📒 Files selected for processing (1)
  • autoload/fern/internal/replacer.vim (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (macos-latest, v8.2.5136)
🔇 Additional comments (3)
autoload/fern/internal/replacer.vim (3)

100-105: LGTM! Error handling correctly prevents resolution.

The early return ensures that the resolve callback is not invoked when modifiers fail, which is the correct behavior. The user must either fix the error or explicitly cancel with :q!.


108-111: Excellent fix using timer deferral.

The timer-based deferral correctly addresses the issue where :wq would close adjacent windows. By deferring the window close and resolution to after the write command completes, only the rename buffer window is closed as intended.

The captured winid and bufnr enable safe validation in s:post_write(), and the l: prefix on Resolve addresses the past review comment.


114-120: Well-implemented post-write handler addressing past review comments.

The implementation correctly:

  • Validates the window still contains the expected buffer before closing (line 115), gracefully handling cases where the user manually closed the window
  • Uses win_execute() as suggested in the past review comment (line 116)
  • Applies the l: prefix pattern as suggested in the past review comment (line 118)
  • Ensures the resolve callback is invoked after window management completes

The order of operations is correct: the window is closed first (triggering buffer wipe due to bufhidden=wipe), then the resolve callback is invoked with the results.

@Milly Milly requested a review from lambdalisue October 19, 2025 13:24
@lambdalisue lambdalisue merged commit cb60188 into lambdalisue:main Oct 20, 2025
18 of 19 checks passed
@lambdalisue
Copy link
Owner

Thanks 🎉

@Milly Milly deleted the prevent-close branch October 21, 2025 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants