diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index 494854d781..19055c08e5 100644 --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -186,6 +186,11 @@ normally only do that in a newly created buffer: >vim :set buftype=prompt +Prompt buffers ignore modified checks for changes made to the buffer, so +interactive input does not make them hard to close. But if a plugin or user +explicitly sets 'modified', a modified prompt buffer can't be closed without +saving. + The user can edit and input text at the end of the buffer. Pressing Enter in the input section invokes the |prompt_setcallback()| callback, which is typically expected to process the prompt and show results by appending to the diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index ceefd0902a..4dc3057afe 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4468,8 +4468,11 @@ A jump table for the options with a short description can be found at |Q_op|. result of a BufNewFile, BufRead/BufReadPost, BufWritePost, FileAppendPost or VimLeave autocommand event. See |gzip-example| for an explanation. - When 'buftype' is "nowrite" or "nofile" this option may be set, but - will be ignored. + When 'buftype' is "nowrite" or "nofile", this option may be set, but + it is ignored and will not block closing the window. For "prompt" + buffers, changes made to the buffer do not make it count as modified, + but an explicit ":set modified" is respected and will block closing the + window. Note that the text may actually be the same, e.g. 'modified' is set when using "rA" on an "A". diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 4b532cf34e..2ae4fbc7ec 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -4565,8 +4565,11 @@ vim.bo.ma = vim.bo.modifiable --- result of a BufNewFile, BufRead/BufReadPost, BufWritePost, --- FileAppendPost or VimLeave autocommand event. See `gzip-example` for --- an explanation. ---- When 'buftype' is "nowrite" or "nofile" this option may be set, but ---- will be ignored. +--- When 'buftype' is "nowrite" or "nofile", this option may be set, but +--- it is ignored and will not block closing the window. For "prompt" +--- buffers, changes made to the buffer do not make it count as modified, +--- but an explicit ":set modified" is respected and will block closing the +--- window. --- Note that the text may actually be the same, e.g. 'modified' is set --- when using "rA" on an "A". --- diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 255d5f7f5c..20ec24d22d 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -5935,8 +5935,11 @@ local options = { result of a BufNewFile, BufRead/BufReadPost, BufWritePost, FileAppendPost or VimLeave autocommand event. See |gzip-example| for an explanation. - When 'buftype' is "nowrite" or "nofile" this option may be set, but - will be ignored. + When 'buftype' is "nowrite" or "nofile", this option may be set, but + it is ignored and will not block closing the window. For "prompt" + buffers, changes made to the buffer do not make it count as modified, + but an explicit ":set modified" is respected and will block closing the + window. Note that the text may actually be the same, e.g. 'modified' is set when using "rA" on an "A". ]=], diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 67c2921a8f..4a38a36dac 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -3090,6 +3090,8 @@ static char *u_save_line_buf(buf_T *buf, linenr_T lnum) /// Check if the 'modified' flag is set, or 'ff' has changed (only need to /// check the first character, because it can only be "dos", "unix" or "mac"). /// "nofile" and "scratch" type buffers are considered to always be unchanged. +/// Prompt buffers ignore implicit modifications by default, but an explicit +/// ":set modified" still makes them count as changed. /// /// @param buf The buffer to check /// @@ -3097,10 +3099,10 @@ static char *u_save_line_buf(buf_T *buf, linenr_T lnum) bool bufIsChanged(buf_T *buf) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - // In a "prompt" buffer we do respect 'modified', so that we can control - // closing the window by setting or resetting that option. - return (!bt_dontwrite(buf) || bt_prompt(buf)) - && (buf->b_changed || file_ff_differs(buf, true)); + // In a "prompt" buffer we respect 'modified' if the user or a plugin explicitly set it. + return bt_prompt(buf) + ? buf->b_modified_was_set + : (!bt_dontwrite(buf) && (buf->b_changed || file_ff_differs(buf, true))); } // Return true if any buffer has changes. Also buffers that are not written. diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua index 4986fdb0b5..81893d9067 100644 --- a/test/functional/legacy/prompt_buffer_spec.lua +++ b/test/functional/legacy/prompt_buffer_spec.lua @@ -27,15 +27,11 @@ describe('prompt buffer', function() source([[ func TextEntered(text) if a:text == "exit" - " Reset &modified to allow the buffer to be closed. - set nomodified stopinsert close else " Add the output above the current prompt. call append(line("$") - 1, split('Command: "' . a:text . '"', '\n')) - " Reset &modified to allow the buffer to be closed. - set nomodified call timer_start(20, {id -> TimerFunc(a:text)}) endif endfunc @@ -43,8 +39,6 @@ describe('prompt buffer', function() func TimerFunc(text) " Add the output above the current prompt. call append(line("$") - 1, split('Result: "' . a:text .'"', '\n')) - " Reset &modified to allow the buffer to be closed. - set nomodified endfunc func SwitchWindows() @@ -62,7 +56,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: ^ | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -89,6 +83,21 @@ describe('prompt buffer', function() {1:~ }|*8 | ]]) + + command('new') + command('set buftype=prompt') + feed('iabc') + eq('a', fn('prompt_getinput', fn('bufnr'))) + command('quit') + eq(1, #api.nvim_list_wins()) + + command('new') + command('set buftype=prompt modified') + eq( + 'Vim(quit):E37: No write since last change (add ! to override)', + t.pcall_err(command, 'quit') + ) + eq(2, #api.nvim_list_wins()) end) -- oldtest: Test_prompt_editing() @@ -98,7 +107,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: hel^ | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -107,7 +116,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: -^hel | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -116,7 +125,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: -hz^el | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -125,7 +134,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: -hzelx^ | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -149,7 +158,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: | {1:~ }|*3 - {2:[Prompt] [+] }| + {2:[Prompt] }| ^other buffer | {1:~ }|*3 | @@ -158,7 +167,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: ^ | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -167,7 +176,7 @@ describe('prompt buffer', function() screen:expect([[ cmd:^ | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 | @@ -281,7 +290,7 @@ describe('prompt buffer', function() line 2 | line 3^ | {1:~ }| - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -403,7 +412,7 @@ describe('prompt buffer', function() line 2 | line 3 | {1:~ }| - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 | @@ -433,7 +442,7 @@ describe('prompt buffer', function() line 2 | line 3^ | {1:~ }| - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -450,7 +459,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: tests-middle^-initial| {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 | @@ -460,7 +469,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: tests-mid^le-initial | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 | @@ -471,7 +480,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: tests-mid^dle-initial| {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 1 change; {MATCH:.*} | @@ -484,7 +493,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: tests-^initial | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 1 change; {MATCH:.*} | @@ -509,7 +518,7 @@ describe('prompt buffer', function() Command: "tests-initial" | cmd:^ | {1:~ }| - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 1 line {MATCH:.*} | @@ -522,7 +531,7 @@ describe('prompt buffer', function() Command: "tests-initial" | cmd: ^ | {1:~ }| - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -533,7 +542,7 @@ describe('prompt buffer', function() Command: "tests-initial" | ^cmd: hello | {1:~ }| - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 1 change; {MATCH:.*} | @@ -548,7 +557,7 @@ describe('prompt buffer', function() Command: "tests-initial" | c^md > hello | {1:~ }| - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 Already at oldest change | @@ -563,7 +572,7 @@ describe('prompt buffer', function() Command: "tests-initial" | cmd > hello there | cmd >^ | - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 Already at oldest change | @@ -580,7 +589,7 @@ describe('prompt buffer', function() line 2 | line 3^ | {1:~ }| - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -592,7 +601,7 @@ describe('prompt buffer', function() line 2 | after^ | line 3 | - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -608,7 +617,7 @@ describe('prompt buffer', function() before^ | line 2 | after | - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -618,6 +627,8 @@ describe('prompt buffer', function() eq('line 1\nbefore\nline 2\nafter\nline 3', fn('prompt_getinput', buf)) feed('') + vim.uv.sleep(20) + eq('', fn('prompt_getinput', buf)) screen:expect([[ line 2 | after | @@ -630,6 +641,16 @@ describe('prompt buffer', function() ]]) feed('line 4line 5') + screen:expect([[ + after | + line 3" | + cmd: line 4 | + line 5^ | + {3:[Prompt] }| + other buffer | + {1:~ }|*3 + {5:-- INSERT --} | + ]]) feed('k0oafter prompt') screen:expect([[ @@ -637,7 +658,7 @@ describe('prompt buffer', function() line 3" | cmd: line 4 | after prompt^ | - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -649,13 +670,15 @@ describe('prompt buffer', function() line 3" | cmd: at prompt^ | line 4 | - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | ]]) feed('') + vim.uv.sleep(20) + eq('', fn('prompt_getinput', buf)) screen:expect([[ line 4 | after prompt | @@ -674,7 +697,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: asdf^ | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -684,7 +707,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: ^ | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -694,7 +717,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: asdf^ | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -704,7 +727,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: ^ | {1:~ }|*3 - {3:[Prompt] [+] }| + {3:[Prompt] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -1021,7 +1044,7 @@ describe('prompt buffer', function() ooooooooooooooooooooong >| ^ | {1:~ }| - {3:[Prompt] [+] }| + {3:[Prompt] }| foo > hello | {1:~ }|*3 {5:-- INSERT --} |