Skip to content

Commit

Permalink
cleanup!: Remove prompt buffer implementation for ui.input
Browse files Browse the repository at this point in the history
The `prompt_buffer = true` option will no longer function. The only
input modal implementation will use a normal scratch buffer.
  • Loading branch information
stevearc committed Jan 12, 2022
1 parent 0ad4d1e commit f487c89
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 189 deletions.
8 changes: 0 additions & 8 deletions autoload/dressing.vim
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
func! dressing#prompt_confirm(text) abort
call luaeval("require('dressing.input').confirm(_A)", a:text)
endfunc

func! dressing#prompt_cancel() abort
lua require('dressing.input').confirm()
endfunc

function! dressing#fzf_run(labels, options, window) abort
call fzf#run({
\ 'source': a:labels,
Expand Down
15 changes: 0 additions & 15 deletions doc/dressing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -119,21 +119,6 @@ dressing.get_config() *dressing_get_config()
}
})
===============================================================================
*dressing-prompt*
Vim has a mechanism that is built for getting input from the user: the
|prompt-buffer|. This is a specific |buftype| and comes with a lot of special
handling within vim. Neovim 0.6 and earlier has some bugs with the prompt
buffer (see https://github.com/stevearc/dressing.nvim/issues/2 and
https://github.com/neovim/neovim/issues/13715). For this reason, the default
implementation of |vim.ui.input| does NOT use the prompt buffer, and instead
mimics its behavior through other means. If you don't mind the bugs, or if
you're on a version of Neovim after 0.6 (nightly has the fixes now), you can
pass `prompt_buffer = true` to use that implementation.

There are slight visual differences in where the "prompt" text in placed, but
otherwise they should be functionally identical.

===============================================================================
*dressing-format*
Sometimes you may wish to change how choices are displayed for
Expand Down
1 change: 0 additions & 1 deletion doc/tags
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ Dressing dressing.txt /*Dressing*
dressing dressing.txt /*dressing*
dressing-configuration dressing.txt /*dressing-configuration*
dressing-format dressing.txt /*dressing-format*
dressing-prompt dressing.txt /*dressing-prompt*
dressing.nvim dressing.txt /*dressing.nvim*
dressing.txt dressing.txt /*dressing.txt*
dressing_get_config() dressing.txt /*dressing_get_config()*
10 changes: 0 additions & 10 deletions lua/dressing/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@ local M = {}

M.setup = function(opts)
config.update(opts)
-- Wait until vim has fully started up before we show deprecation warnings
vim.defer_fn(function()
if config.input.prompt_buffer then
vim.notify(
"dressing.nvim option 'input.prompt_buffer = true' is deprecated and will be removed in a future version. If you want it to continue to be supported please file an issue with your use case: https://github.com/stevearc/dressing.nvim/issues/new",
vim.log.levels.WARN,
{}
)
end
end, 100)
end

local original_input
Expand Down
55 changes: 16 additions & 39 deletions lua/dressing/input.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ local function close_completion_window()
end
end

M.confirm = function(text)
local function confirm(text)
if not context.on_confirm then
return
end
Expand All @@ -41,13 +41,13 @@ M.confirm = function(text)
end, 5)
end

M.confirm_non_prompt = function()
M.confirm = function()
local text = vim.api.nvim_buf_get_lines(0, 0, 1, true)[1]
M.confirm(text)
confirm(text)
end

M.close = function()
M.confirm()
confirm()
end

M.highlight = function()
Expand Down Expand Up @@ -88,11 +88,7 @@ M.completefunc = function(findstart, base)
return findstart == 1 and 0 or {}
end
if findstart == 1 then
if global_config.input.prompt_buffer then
return vim.api.nvim_strwidth(context.opts.prompt)
else
return 0
end
return 0
else
local completion = context.opts.completion
local pieces = split(completion, ",")
Expand Down Expand Up @@ -140,12 +136,7 @@ setmetatable(M, {

-- Create or update the window
local prompt = opts.prompt or config.default_prompt
local width
if config.prompt_buffer then
width = util.calculate_width(config.prefer_width + vim.api.nvim_strwidth(prompt), config)
else
width = util.calculate_width(config.prefer_width, config)
end
local width = util.calculate_width(config.prefer_width, config)
local winopt = {
relative = config.relative,
anchor = config.anchor,
Expand Down Expand Up @@ -187,27 +178,17 @@ setmetatable(M, {
vim.api.nvim_buf_set_keymap(bufnr, "i", "<Esc>", close_rhs, keyopts)
end

if config.prompt_buffer then
vim.api.nvim_buf_set_option(bufnr, "buftype", "prompt")
vim.fn.prompt_setprompt(bufnr, prompt)
-- Would prefer to use v:lua directly here, but it doesn't work :(
vim.fn.prompt_setcallback(bufnr, "dressing#prompt_confirm")
vim.fn.prompt_setinterrupt(bufnr, "dressing#prompt_cancel")
else
local confirm_rhs = "<cmd>lua require('dressing.input').confirm_non_prompt()<CR>"
-- If we're not using the prompt buffer, we need to put the prompt into a
-- separate title window that will appear in the input window border
vim.api.nvim_buf_set_keymap(bufnr, "i", "<C-c>", close_rhs, keyopts)
vim.api.nvim_buf_set_keymap(bufnr, "i", "<CR>", confirm_rhs, keyopts)
vim.api.nvim_buf_set_keymap(bufnr, "n", "<CR>", confirm_rhs, keyopts)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { opts.default or "" })
-- Disable nvim-cmp if installed
local ok, cmp = pcall(require, "cmp")
if ok then
cmp.setup.buffer({ enabled = false })
end
util.add_title_to_win(winid, string.gsub(prompt, "^%s*(.-)%s*$", "%1"), { align = "left" })
local confirm_rhs = "<cmd>lua require('dressing.input').confirm()<CR>"
vim.api.nvim_buf_set_keymap(bufnr, "i", "<C-c>", close_rhs, keyopts)
vim.api.nvim_buf_set_keymap(bufnr, "i", "<CR>", confirm_rhs, keyopts)
vim.api.nvim_buf_set_keymap(bufnr, "n", "<CR>", confirm_rhs, keyopts)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { opts.default or "" })
-- Disable nvim-cmp if installed
local ok, cmp = pcall(require, "cmp")
if ok then
cmp.setup.buffer({ enabled = false })
end
util.add_title_to_win(winid, string.gsub(prompt, "^%s*(.-)%s*$", "%1"), { align = "left" })

vim.cmd([[
aug DressingHighlight
Expand Down Expand Up @@ -237,10 +218,6 @@ setmetatable(M, {
]])

vim.cmd("startinsert!")
if opts.default and config.prompt_buffer then
vim.api.nvim_feedkeys(opts.default, "n", false)
end

close_completion_window()
M.highlight()
end,
Expand Down
220 changes: 104 additions & 116 deletions tests/input_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,133 +17,121 @@ local function run_input(keys, opts)
return rx()
end

for _, prompt_buffer in ipairs({ true, false }) do
a.describe(
string.format("input (%s)", prompt_buffer and "prompt buffer" or "normal floatwin"),
function()
before_each(function()
dressing.patch()
dressing.setup({
input = {
prompt_buffer = prompt_buffer,
},
})
end)
a.describe("input modal", function()
before_each(function()
dressing.patch()
dressing.setup()
end)

after_each(function()
-- Clean up all floating windows so one test failure doesn't cascade
for _, winid in ipairs(vim.api.nvim_list_wins()) do
if vim.api.nvim_win_get_config(winid).relative ~= "" then
vim.api.nvim_win_close(winid, true)
end
end
end)
after_each(function()
-- Clean up all floating windows so one test failure doesn't cascade
for _, winid in ipairs(vim.api.nvim_list_wins()) do
if vim.api.nvim_win_get_config(winid).relative ~= "" then
vim.api.nvim_win_close(winid, true)
end
end
end)

a.it("accepts input", function()
local ret = run_input({
"my text",
"<CR>",
})
assert(ret == "my text", string.format("Got '%s' expected 'my text'", ret))
end)
a.it("accepts input", function()
local ret = run_input({
"my text",
"<CR>",
})
assert(ret == "my text", string.format("Got '%s' expected 'my text'", ret))
end)

a.it("Cancels input on <C-c>", function()
local ret = run_input({
"my text",
"<C-c>",
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
end)
a.it("Cancels input on <C-c>", function()
local ret = run_input({
"my text",
"<C-c>",
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
end)

a.it("cancels input when leaving the window", function()
local ret = run_input({
"my text",
}, {
after_fn = function()
vim.cmd([[wincmd p]])
end,
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
end)
a.it("cancels input when leaving the window", function()
local ret = run_input({
"my text",
}, {
after_fn = function()
vim.cmd([[wincmd p]])
end,
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
end)

a.it("cancels on <Esc> when insert_only = true", function()
require("dressing.config").input.insert_only = true
local ret = run_input({
"my text",
"<Esc>",
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
end)
a.it("cancels on <Esc> when insert_only = true", function()
require("dressing.config").input.insert_only = true
local ret = run_input({
"my text",
"<Esc>",
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
end)

a.it("does not cancel on <Esc> when insert_only = false", function()
require("dressing.config").input.insert_only = false
local ret = run_input({
"my text",
"<Esc>",
"<CR>",
})
assert(ret == "my text", string.format("Got '%s' expected 'my text'", ret))
end)
a.it("does not cancel on <Esc> when insert_only = false", function()
require("dressing.config").input.insert_only = false
local ret = run_input({
"my text",
"<Esc>",
"<CR>",
})
assert(ret == "my text", string.format("Got '%s' expected 'my text'", ret))
end)

a.it("cancels first callback if second input is opened", function()
local tx, rx = channel.oneshot()
vim.ui.input({}, tx)
util.feedkeys({
"i", -- HACK have to do this because :startinsert doesn't work in tests,
"my text",
})
vim.ui.input({}, function() end)
local ret = rx()
assert(ret == nil, string.format("Got '%s' expected nil", ret))
end)
a.it("cancels first callback if second input is opened", function()
local tx, rx = channel.oneshot()
vim.ui.input({}, tx)
util.feedkeys({
"i", -- HACK have to do this because :startinsert doesn't work in tests,
"my text",
})
vim.ui.input({}, function() end)
local ret = rx()
assert(ret == nil, string.format("Got '%s' expected nil", ret))
end)

-- TODO can't get this working in the prompt buffer test
if not prompt_buffer then
a.it("supports completion", function()
vim.opt.completeopt = { "menu", "menuone", "noselect" }
vim.cmd([[
a.it("supports completion", function()
vim.opt.completeopt = { "menu", "menuone", "noselect" }
vim.cmd([[
function! CustomComplete(arglead, cmdline, cursorpos)
return "first\nsecond\nthird"
endfunction
]])
local ret = run_input({
"<Tab><Tab><C-n><CR>", -- Using tab twice to test both versions of the mapping
}, {
completion = "custom,CustomComplete",
})
assert(ret == "second", string.format("Got '%s' expected 'second'", ret))
assert(vim.fn.pumvisible() == 0, "Popup menu should not be visible after leaving modal")
end)
local ret = run_input({
"<Tab><Tab><C-n><CR>", -- Using tab twice to test both versions of the mapping
}, {
completion = "custom,CustomComplete",
})
assert(ret == "second", string.format("Got '%s' expected 'second'", ret))
assert(vim.fn.pumvisible() == 0, "Popup menu should not be visible after leaving modal")
end)

a.it("can cancel out when popup menu is open", function()
vim.opt.completeopt = { "menu", "menuone", "noselect" }
local ret = run_input({
"<Tab>",
"<C-c>",
}, {
completion = "command",
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
assert(vim.fn.pumvisible() == 0, "Popup menu should not be visible after leaving modal")
end)
a.it("can cancel out when popup menu is open", function()
vim.opt.completeopt = { "menu", "menuone", "noselect" }
local ret = run_input({
"<Tab>",
"<C-c>",
}, {
completion = "command",
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
assert(vim.fn.pumvisible() == 0, "Popup menu should not be visible after leaving modal")
end)

a.it("doesn't delete text in original buffer", function()
-- This is a regression test for weird behavior I was seeing with the
-- completion popup menu
vim.api.nvim_buf_set_lines(0, 0, 1, true, { "some text" })
vim.api.nvim_win_set_cursor(0, { 1, 4 })
vim.opt.completeopt = { "menu", "menuone", "noselect" }
local ret = run_input({
"<Tab>",
"<C-c>",
}, {
completion = "command",
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
local line = vim.api.nvim_buf_get_lines(0, 0, 1, true)[1]
assert(line == "some text", "Doing <C-c> with popup menu open deleted buffer text o.0")
end)
end
end
)
end
a.it("doesn't delete text in original buffer", function()
-- This is a regression test for weird behavior I was seeing with the
-- completion popup menu
vim.api.nvim_buf_set_lines(0, 0, 1, true, { "some text" })
vim.api.nvim_win_set_cursor(0, { 1, 4 })
vim.opt.completeopt = { "menu", "menuone", "noselect" }
local ret = run_input({
"<Tab>",
"<C-c>",
}, {
completion = "command",
})
assert(ret == nil, string.format("Got '%s' expected nil", ret))
local line = vim.api.nvim_buf_get_lines(0, 0, 1, true)[1]
assert(line == "some text", "Doing <C-c> with popup menu open deleted buffer text o.0")
end)
end)

0 comments on commit f487c89

Please sign in to comment.