fix(terminal): check file timestamps on terminal-job exit #41814

Problem:
When a `:!` command runs, file timestamps are checked and W12 or
`FileChangedShell` is triggered. But not when a `:terminal` job exits.

Only `enter_buffer()` checks, so whether you get the W12 warning depends
on how you happen to return to the file: `:term` works, `:tabnew | term`
does not.

    echo foo > testfile
    nvim --clean testfile
    " Edit the buffer, don't save it.
    :!mv testfile testfile2
    :tabnew | term sh
    touch testfile2 && mv testfile2 testfile
    exit

'autoread' is defeated because `:!mv` deletes the file, so
`should_watch()` fails and the watcher stops. `ensure_watcher()` only
re-runs on `BufReadPost`/`BufWritePost`/`OptionSet`, so nothing watches
the file when the terminal moves it back.

Solution:
Set `need_check_timestamps` on terminal-job exit, same as `:!cmd`
(`os_shell`).
This commit is contained in:
Justin M. Keyes
2026-09-09 15:14:26 -04:00
committed by GitHub
parent a55f134ee3
commit ac167f943a
4 changed files with 29 additions and 7 deletions

View File

@@ -779,7 +779,7 @@ FileChangedShell When Vim notices that the modification time of
change or when the size of the file changes.
|timestamp|
Triggered for each changed file, after:
- executing a shell command
- executing a shell command or |:terminal| job
- |:checktime|
- |FocusGained|
@@ -1169,8 +1169,9 @@ SessionWritePost After writing a session file by calling
ShellCmdPost After executing a shell command with |:!cmd|,
|:make| and |:grep|. Can be used to check for
any changed files.
For non-blocking shell commands, see
|job-control|.
|TermClose| |job-control|.
*ShellFilterPost*
ShellFilterPost After executing a shell command with

View File

@@ -1543,11 +1543,10 @@ Vim remembers the modification timestamp, mode and size of a file when you
begin editing it. This is used to avoid that you have two different versions
of the same file (without you knowing this).
After a shell command is run (|:!cmd| |suspend| |:read!| |K|) timestamps,
file modes and file sizes are compared for all buffers in a window. Vim will
run any associated |FileChangedShell| autocommands or display a warning for
any files that have changed. In the GUI this happens when Vim regains input
focus.
After a shell command (|:!cmd| |suspend| |:read!| |K|) or |:terminal| job
exits: timestamps, file modes and file sizes are compared for all buffers
in a window. Nvim will run |FileChangedShell| autocommands or display
a warning for any files that changed.
*E321* *E462*
If you want to automatically reload a file when it has been changed outside of

View File

@@ -729,6 +729,11 @@ void terminal_close(Terminal **termpp, int status)
return;
}
if (status >= 0) { // The job may have changed files on disk, like `do_shell` (":!cmd").
did_check_timestamps = false;
need_check_timestamps = true;
}
if (buf && !is_autocmd_blocked()) {
save_v_event_T save_v_event;
dict_T *dict = get_v_event(&save_v_event);

View File

@@ -639,6 +639,23 @@ end)
describe(':terminal buffer', function()
before_each(clear)
it('exit emits FileChangedShell #41759', function()
local path = t.tmpname()
write_file(path, 'foo\n')
command('edit ' .. path)
command('set noautoread') -- Disable 'autoread' to exercise :checktime specifically.
api.nvim_buf_set_lines(0, 0, -1, true, { 'local change' })
n.exec('let g:fcs = 0 | autocmd FileChangedShell * let g:fcs = 1')
write_file(path, 'external change\n')
-- Terminal in another tab, so returning to the buffer is not what triggers the check.
command('tabnew')
fn.jobstart({ testprg('shell-test'), 'EXIT', '0' }, { term = true })
retry(nil, 10000, function()
eq(1, api.nvim_get_var('fcs'))
end)
end)
it('can resume suspended PTY process running in fish', function()
skip(is_os('win'), 'N/A for Windows')
skip(fn.executable('fish') == 0, 'missing "fish" command')