fix(filetype): empty 'filetype' skips :setfiletype #41958

Problem:
After `:restart` a restored buffer can have an empty 'filetype'. Lazy
LSP setup during the read replays FileType via `:doautoall`, which sets
`did_filetype` even though `filetype=''`.

    :e foo.md
    :restart
    :set ft?  " filetype=

Solution:
Set `did_filetype` only if 'filetype' is non-empty.
This commit is contained in:
Justin M. Keyes authored and GitHub committed 2026-09-17 16:56:45 -04:00
1 parent d1fd8158de
commit 6ad6dcd5d4
6 files changed
+33 -16

No files matched your search

+6 -5
View File
@@ -462,15 +462,16 @@ reads back as the function (from Lua) or a `"<Lua …>"` hint (Vimscript/RPC).
Setting the filetype
:setf[iletype] [FALLBACK] {filetype} *:setf* *:setfiletype*
Set the 'filetype' option to {filetype}, but only if
not done yet in a sequence of (nested) autocommands.
Set the 'filetype' option to {filetype}, unless it was
already set (non-empty) in a sequence of (nested)
autocommands.
This is short for: >
:if !did_filetype()
: setlocal filetype={filetype}
:endif
< This command is used in a filetype.vim file to avoid
setting the 'filetype' option twice, causing different
settings and syntax files to be loaded.
< Used in a filetype.vim file to avoid setting the
'filetype' option twice, causing different settings
and syntax files to be loaded.
When the optional FALLBACK argument is present, a
later :setfiletype command will override the
+4 -3
View File
@@ -1743,10 +1743,11 @@ dictwatcherdel({dict}, {pattern}, {callback}) *dictwatcherdel()*
did_filetype() *did_filetype()*
Returns |TRUE| when autocommands are being executed and the
FileType event has been triggered at least once. Can be used
to avoid triggering the FileType event again in the scripts
that detect the file type. |FileType|
FileType event triggered at least once for a buffer with
a non-empty 'filetype'. Can be used to avoid triggering
the FileType event again in the |ftdetect| scripts. |FileType|
Returns |FALSE| when `:setf FALLBACK` was used.
When editing another file, the counter is reset, thus this
really checks if the FileType event has been triggered for the
current buffer. This allows an autocommand that starts
+4 -3
View File
@@ -1528,10 +1528,11 @@ function vim.fn.dictwatcheradd(dict, pattern, callback) end
function vim.fn.dictwatcherdel(dict, pattern, callback) end
--- Returns |TRUE| when autocommands are being executed and the
--- FileType event has been triggered at least once. Can be used
--- to avoid triggering the FileType event again in the scripts
--- that detect the file type. |FileType|
--- FileType event triggered at least once for a buffer with
--- a non-empty 'filetype'. Can be used to avoid triggering
--- the FileType event again in the |ftdetect| scripts. |FileType|
--- Returns |FALSE| when `:setf FALLBACK` was used.
---
--- When editing another file, the counter is reset, thus this
--- really checks if the FileType event has been triggered for the
--- current buffer. This allows an autocommand that starts
+2 -2
View File
@@ -1710,8 +1710,8 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
filechangeshell_busy = (event == EVENT_FILECHANGEDSHELL);
nesting++; // see matching decrement below
// Remember that FileType was triggered. Used for did_filetype().
if (event == EVENT_FILETYPE) {
// Remember that FileType was triggered, if 'filetype' was actually set. For :setf.
if (event == EVENT_FILETYPE && *curbuf->b_p_ft != NUL) {
curbuf->b_did_filetype = true;
}
+4 -3
View File
@@ -1952,10 +1952,11 @@ M.funcs = {
did_filetype = {
desc = [=[
Returns |TRUE| when autocommands are being executed and the
FileType event has been triggered at least once. Can be used
to avoid triggering the FileType event again in the scripts
that detect the file type. |FileType|
FileType event triggered at least once for a buffer with
a non-empty 'filetype'. Can be used to avoid triggering
the FileType event again in the |ftdetect| scripts. |FileType|
Returns |FALSE| when `:setf FALLBACK` was used.
When editing another file, the counter is reset, thus this
really checks if the FileType event has been triggered for the
current buffer. This allows an autocommand that starts
+13
View File
@@ -16,4 +16,17 @@ describe('autocmd FileType', function()
command('help help')
t.eq(1, eval('g:foo'))
end)
it("empty 'filetype' does not prevent FileType event #41711", function()
local file = t.tmpname(false) .. '.md'
t.write_file(file, '# hi\n')
command('filetype on')
-- Like vim.lsp.enable() lazy-loaded while the buffer is being read.
command('autocmd FileType * :')
command('autocmd BufReadPre * ++once doautoall FileType')
-- Run :edit in a nested event (mimics :restart session-restore).
command(('autocmd User X ++nested edit %s'):format(vim.fn.fnameescape(file)))
command('doautocmd User X')
t.eq('markdown', eval('&filetype'))
end)
end)