Merge #6480 from ZyX-I/colored-cmdline'/input-dict

This commit is contained in:
Justin M. Keyes 2017-05-13 21:17:33 +02:00 committed by GitHub
commit 17531ed082
6 changed files with 541 additions and 79 deletions

View File

@ -4665,10 +4665,23 @@ index({list}, {expr} [, {start} [, {ic}]]) *index()*
input({prompt} [, {text} [, {completion}]]) *input()*
input({opts})
The result is a String, which is whatever the user typed on
the command-line. The {prompt} argument is either a prompt
string, or a blank string (for no prompt). A '\n' can be used
in the prompt to start a new line.
In the second form it accepts a single dictionary with the
following keys, any of which may be omitted:
Key Default Description ~
prompt "" Same as {prompt} in the first form.
default "" Same as {text} in the first form.
completion nothing Same as {completion} in the first form.
cancelreturn "" Same as {cancelreturn} from
|inputdialog()|. Also works with
input().
The highlighting set with |:echohl| is used for the prompt.
The input is entered just like a command-line, with the same
editing commands and mappings. There is a separate history
@ -4710,6 +4723,7 @@ input({prompt} [, {text} [, {completion}]]) *input()*
:endfunction
inputdialog({prompt} [, {text} [, {cancelreturn}]]) *inputdialog()*
inputdialog({opts})
Like |input()|, but when the GUI is running and text dialogs
are supported, a dialog window pops up to input the text.
Example: >
@ -4721,7 +4735,6 @@ inputdialog({prompt} [, {text} [, {cancelreturn}]]) *inputdialog()*
omitted an empty string is returned.
Hitting <Enter> works like pressing the OK button. Hitting
<Esc> works like pressing the Cancel button.
NOTE: Command-line completion is not supported.
inputlist({textlist}) *inputlist()*
{textlist} must be a |List| of strings. This |List| is

View File

@ -244,6 +244,10 @@ Lua interface (|if_lua.txt|):
- Lua has direct access to Nvim |API| via `vim.api`.
- Currently, most legacy Vim features are missing.
|input()| and |inputdialog()| gained support for each others features (return
on cancel and completion respectively) via dictionary argument (replaces all
other arguments if used).
==============================================================================
5. Missing legacy features *nvim-features-missing*

View File

@ -10980,81 +10980,122 @@ void get_user_input(const typval_T *const argvars,
typval_T *const rettv, const bool inputdialog)
FUNC_ATTR_NONNULL_ALL
{
const char *prompt = tv_get_string_chk(&argvars[0]);
int cmd_silent_save = cmd_silent;
int xp_type = EXPAND_NOTHING;
char_u *xp_arg = NULL;
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
cmd_silent = FALSE; /* Want to see the prompt. */
if (prompt != NULL) {
// Only the part of the message after the last NL is considered as
// prompt for the command line.
const char *p = strrchr(prompt, '\n');
if (p == NULL) {
p = prompt;
} else {
p++;
msg_start();
msg_clr_eos();
msg_puts_attr_len(prompt, p - prompt, echo_attr);
msg_didout = false;
msg_starthere();
}
cmdline_row = msg_row;
const char *defstr = "";
char buf[NUMBUFLEN];
const char *prompt = "";
const char *defstr = "";
const char *cancelreturn = NULL;
const char *xp_name = NULL;
char prompt_buf[NUMBUFLEN];
char defstr_buf[NUMBUFLEN];
char cancelreturn_buf[NUMBUFLEN];
char xp_name_buf[NUMBUFLEN];
if (argvars[0].v_type == VAR_DICT) {
if (argvars[1].v_type != VAR_UNKNOWN) {
defstr = tv_get_string_buf_chk(&argvars[1], buf);
if (defstr != NULL) {
stuffReadbuffSpec(defstr);
emsgf(_("E5050: {opts} must be the only argument"));
return;
}
const dict_T *const dict = argvars[0].vval.v_dict;
prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, "");
if (prompt == NULL) {
return;
}
defstr = tv_dict_get_string_buf_chk(dict, S_LEN("default"), defstr_buf, "");
if (defstr == NULL) {
return;
}
char def[1] = { 0 };
cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"),
cancelreturn_buf, def);
if (cancelreturn == NULL) { // error
return;
}
if (*cancelreturn == NUL) {
cancelreturn = NULL;
}
xp_name = tv_dict_get_string_buf_chk(dict, S_LEN("completion"),
xp_name_buf, def);
if (xp_name == NULL) { // error
return;
}
if (xp_name == def) { // default to NULL
xp_name = NULL;
}
} else {
prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf);
if (prompt == NULL) {
return;
}
if (argvars[1].v_type != VAR_UNKNOWN) {
defstr = tv_get_string_buf_chk(&argvars[1], defstr_buf);
if (defstr == NULL) {
return;
}
if (!inputdialog && argvars[2].v_type != VAR_UNKNOWN) {
char buf2[NUMBUFLEN];
// input() with a third argument: completion
rettv->vval.v_string = NULL;
const char *const xp_name = tv_get_string_buf_chk(&argvars[2], buf2);
if (xp_name == NULL) {
if (argvars[2].v_type != VAR_UNKNOWN) {
const char *const arg2 = tv_get_string_buf_chk(&argvars[2],
cancelreturn_buf);
if (arg2 == NULL) {
return;
}
const int xp_namelen = (int)strlen(xp_name);
uint32_t argt;
if (parse_compl_arg((char_u *)xp_name, xp_namelen, &xp_type, &argt,
&xp_arg) == FAIL) {
return;
if (inputdialog) {
cancelreturn = arg2;
} else {
xp_name = arg2;
}
}
}
if (defstr != NULL) {
int save_ex_normal_busy = ex_normal_busy;
ex_normal_busy = 0;
rettv->vval.v_string =
getcmdline_prompt(inputsecret_flag ? NUL : '@', (char_u *)p, echo_attr,
xp_type, xp_arg);
ex_normal_busy = save_ex_normal_busy;
}
if (inputdialog && rettv->vval.v_string == NULL
&& argvars[1].v_type != VAR_UNKNOWN
&& argvars[2].v_type != VAR_UNKNOWN) {
char buf[NUMBUFLEN];
rettv->vval.v_string = (char_u *)xstrdup(tv_get_string_buf(
&argvars[2], buf));
}
xfree(xp_arg);
/* since the user typed this, no need to wait for return */
need_wait_return = FALSE;
msg_didout = FALSE;
}
int xp_type = EXPAND_NOTHING;
char *xp_arg = NULL;
if (xp_name != NULL) {
// input() with a third argument: completion
const int xp_namelen = (int)strlen(xp_name);
uint32_t argt;
if (parse_compl_arg((char_u *)xp_name, xp_namelen, &xp_type,
&argt, (char_u **)&xp_arg) == FAIL) {
return;
}
}
int cmd_silent_save = cmd_silent;
cmd_silent = false; // Want to see the prompt.
// Only the part of the message after the last NL is considered as
// prompt for the command line.
const char *p = strrchr(prompt, '\n');
if (p == NULL) {
p = prompt;
} else {
p++;
msg_start();
msg_clr_eos();
msg_puts_attr_len(prompt, p - prompt, echo_attr);
msg_didout = false;
msg_starthere();
}
cmdline_row = msg_row;
stuffReadbuffSpec(defstr);
int save_ex_normal_busy = ex_normal_busy;
ex_normal_busy = 0;
rettv->vval.v_string =
getcmdline_prompt(inputsecret_flag ? NUL : '@', (char_u *)p, echo_attr,
xp_type, (char_u *)xp_arg);
ex_normal_busy = save_ex_normal_busy;
if (rettv->vval.v_string == NULL && cancelreturn != NULL) {
rettv->vval.v_string = (char_u *)xstrdup(cancelreturn);
}
xfree(xp_arg);
// Since the user typed this, no need to wait for return.
need_wait_return = false;
msg_didout = false;
cmd_silent = cmd_silent_save;
}

View File

@ -1210,7 +1210,8 @@ char *tv_dict_get_string(const dict_T *const d, const char *const key,
///
/// @param[in] d Dictionary to get item from.
/// @param[in] key Dictionary key.
/// @param[in] numbuf Numbuf for.
/// @param[in] numbuf Buffer for non-string items converted to strings, at
/// least of #NUMBUFLEN length.
///
/// @return NULL if key does not exist, empty string in case of type error,
/// string item value otherwise.
@ -1225,6 +1226,32 @@ const char *tv_dict_get_string_buf(const dict_T *const d, const char *const key,
return tv_get_string_buf(&di->di_tv, numbuf);
}
/// Get a string item from a dictionary
///
/// @param[in] d Dictionary to get item from.
/// @param[in] key Dictionary key.
/// @param[in] key_len Key length.
/// @param[in] numbuf Buffer for non-string items converted to strings, at
/// least of #NUMBUFLEN length.
/// @param[in] def Default return when key does not exist.
///
/// @return `def` when key does not exist,
/// NULL in case of type error,
/// string item value in case of success.
const char *tv_dict_get_string_buf_chk(const dict_T *const d,
const char *const key,
const ptrdiff_t key_len,
char *const numbuf,
const char *const def)
FUNC_ATTR_WARN_UNUSED_RESULT
{
const dictitem_T *const di = tv_dict_find(d, key, key_len);
if (di == NULL) {
return def;
}
return tv_get_string_buf_chk(&di->di_tv, numbuf);
}
/// Get a function from a dictionary
///
/// @param[in] d Dictionary to get callback from.

View File

@ -1,9 +1,13 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local eq = helpers.eq
local feed = helpers.feed
local meths = helpers.meths
local clear = helpers.clear
local source = helpers.source
local command = helpers.command
local exc_exec = helpers.exc_exec
local screen
@ -11,28 +15,352 @@ before_each(function()
clear()
screen = Screen.new(25, 5)
screen:attach()
source([[
hi Test ctermfg=Red guifg=Red term=bold
function CustomCompl(...)
return 'TEST'
endfunction
function CustomListCompl(...)
return ['FOO']
endfunction
]])
screen:set_default_attr_ids({
EOB={bold = true, foreground = Screen.colors.Blue1},
T={foreground=Screen.colors.Red},
})
end)
describe('input()', function()
it('works correctly with multiline prompts', function()
it('works with multiline prompts', function()
feed([[:call input("Test\nFoo")<CR>]])
screen:expect([[
{1:~ }|
{1:~ }|
{1:~ }|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
Test |
Foo^ |
]], {{bold=true, foreground=Screen.colors.Blue}})
]])
end)
it('works correctly with multiline prompts and :echohl', function()
command('hi Test ctermfg=Red guifg=Red term=bold')
it('works with multiline prompts and :echohl', function()
feed([[:echohl Test | call input("Test\nFoo")<CR>]])
screen:expect([[
{1:~ }|
{1:~ }|
{1:~ }|
{2:Test} |
{2:Foo}^ |
]], {{bold=true, foreground=Screen.colors.Blue}, {foreground=Screen.colors.Red}})
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Test} |
{T:Foo}^ |
]])
command('redraw!')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo}^ |
]])
end)
it('allows unequal numeric arguments when using multiple args', function()
command('echohl Test')
feed([[:call input(1, 2)<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}2^ |
]])
feed('<BS>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}^ |
]])
end)
it('allows unequal numeric values when using {opts} dictionary', function()
command('echohl Test')
meths.set_var('opts', {prompt=1, default=2, cancelreturn=3})
feed([[:echo input(opts)<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}2^ |
]])
feed('<BS>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}^ |
]])
feed('<Esc>')
screen:expect([[
^ |
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:3} |
]])
end)
it('works with redraw', function()
command('echohl Test')
meths.set_var('opts', {prompt='Foo>', default='Bar'})
feed([[:echo inputdialog(opts)<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Bar^ |
]])
command('redraw!')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Bar^ |
]])
feed('<BS>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Ba^ |
]])
command('redraw!')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Ba^ |
]])
end)
it('allows omitting everything with dictionary argument', function()
command('echohl Test')
feed([[:call input({})<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
^ |
]])
end)
it('supports completion', function()
feed(':let var = input("", "", "custom,CustomCompl")<CR>')
feed('<Tab><CR>')
eq('TEST', meths.get_var('var'))
feed(':let var = input({"completion": "customlist,CustomListCompl"})<CR>')
feed('<Tab><CR>')
eq('FOO', meths.get_var('var'))
end)
it('supports cancelreturn', function()
feed(':let var = input({"cancelreturn": "BAR"})<CR>')
feed('<Esc>')
eq('BAR', meths.get_var('var'))
end)
it('supports default string', function()
feed(':let var = input("", "DEF1")<CR>')
feed('<CR>')
eq('DEF1', meths.get_var('var'))
feed(':let var = input({"default": "DEF2"})<CR>')
feed('<CR>')
eq('DEF2', meths.get_var('var'))
end)
it('errors out on invalid inputs', function()
eq('Vim(call):E730: using List as a String',
exc_exec('call input([])'))
eq('Vim(call):E730: using List as a String',
exc_exec('call input("", [])'))
eq('Vim(call):E730: using List as a String',
exc_exec('call input("", "", [])'))
eq('Vim(call):E730: using List as a String',
exc_exec('call input({"prompt": []})'))
eq('Vim(call):E730: using List as a String',
exc_exec('call input({"cancelreturn": []})'))
eq('Vim(call):E730: using List as a String',
exc_exec('call input({"default": []})'))
eq('Vim(call):E730: using List as a String',
exc_exec('call input({"completion": []})'))
eq('Vim(call):E5050: {opts} must be the only argument',
exc_exec('call input({}, "default")'))
eq('Vim(call):E118: Too many arguments for function: input',
exc_exec('call input("prompt> ", "default", "file", "extra")'))
end)
end)
describe('inputdialog()', function()
it('works with multiline prompts', function()
feed([[:call inputdialog("Test\nFoo")<CR>]])
screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
Test |
Foo^ |
]])
end)
it('works with multiline prompts and :echohl', function()
feed([[:echohl Test | call inputdialog("Test\nFoo")<CR>]])
screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Test} |
{T:Foo}^ |
]])
command('redraw!')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo}^ |
]])
end)
it('allows unequal numeric arguments when using multiple args', function()
command('echohl Test')
feed([[:call inputdialog(1, 2)<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}2^ |
]])
feed('<BS>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}^ |
]])
end)
it('allows unequal numeric values when using {opts} dictionary', function()
command('echohl Test')
meths.set_var('opts', {prompt=1, default=2, cancelreturn=3})
feed([[:echo input(opts)<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}2^ |
]])
feed('<BS>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}^ |
]])
feed('<Esc>')
screen:expect([[
^ |
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:3} |
]])
end)
it('works with redraw', function()
command('echohl Test')
meths.set_var('opts', {prompt='Foo>', default='Bar'})
feed([[:echo input(opts)<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Bar^ |
]])
command('redraw!')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Bar^ |
]])
feed('<BS>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Ba^ |
]])
command('redraw!')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Ba^ |
]])
end)
it('allows omitting everything with dictionary argument', function()
command('echohl Test')
feed(':echo inputdialog({})<CR>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
^ |
]])
end)
it('supports completion', function()
feed(':let var = inputdialog({"completion": "customlist,CustomListCompl"})<CR>')
feed('<Tab><CR>')
eq('FOO', meths.get_var('var'))
end)
it('supports cancelreturn', function()
feed(':let var = inputdialog("", "", "CR1")<CR>')
feed('<Esc>')
eq('CR1', meths.get_var('var'))
feed(':let var = inputdialog({"cancelreturn": "BAR"})<CR>')
feed('<Esc>')
eq('BAR', meths.get_var('var'))
end)
it('supports default string', function()
feed(':let var = inputdialog("", "DEF1")<CR>')
feed('<CR>')
eq('DEF1', meths.get_var('var'))
feed(':let var = inputdialog({"default": "DEF2"})<CR>')
feed('<CR>')
eq('DEF2', meths.get_var('var'))
end)
it('errors out on invalid inputs', function()
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog([])'))
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog("", [])'))
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog("", "", [])'))
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog({"prompt": []})'))
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog({"cancelreturn": []})'))
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog({"default": []})'))
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog({"completion": []})'))
eq('Vim(call):E5050: {opts} must be the only argument',
exc_exec('call inputdialog({}, "default")'))
eq('Vim(call):E118: Too many arguments for function: inputdialog',
exc_exec('call inputdialog("prompt> ", "default", "file", "extra")'))
end)
end)

View File

@ -1751,6 +1751,55 @@ describe('typval.c', function()
eq('2', s)
end)
end)
describe('get_string_buf_chk()', function()
local function tv_dict_get_string_buf_chk(d, key, len, buf, def, emsg)
buf = buf or ffi.gc(lib.xmalloc(lib.NUMBUFLEN), lib.xfree)
def = def or ffi.gc(lib.xstrdup('DEFAULT'), lib.xfree)
len = len or #key
alloc_log:clear()
local ret = check_emsg(function() return lib.tv_dict_get_string_buf_chk(d, key, len, buf, def) end,
emsg)
local s_ret = (ret ~= nil) and ffi.string(ret) or nil
if not emsg then
alloc_log:check({})
end
return s_ret, ret, buf, def
end
itp('works with NULL dict', function()
eq('DEFAULT', tv_dict_get_string_buf_chk(nil, 'test'))
end)
itp('works', function()
local lua_d = {
['']={},
t=1,
te=int(2),
tes=empty_list,
test='tset',
testt=5,
}
local d = dict(lua_d)
alloc_log:clear()
eq(lua_d, dct2tbl(d))
alloc_log:check({})
local s, r, b, def
s, r, b, def = tv_dict_get_string_buf_chk(d, 'test')
neq(r, b)
neq(r, def)
eq('tset', s)
s, r, b, def = tv_dict_get_string_buf_chk(d, 'test', 1, nil, nil, 'E806: using Float as a String')
neq(r, b)
neq(r, def)
eq(nil, s)
s, r, b, def = tv_dict_get_string_buf_chk(d, 'te')
eq(r, b)
neq(r, def)
eq('2', s)
s, r, b, def = tv_dict_get_string_buf_chk(d, 'TEST')
eq(r, def)
neq(r, b)
eq('DEFAULT', s)
end)
end)
describe('get_callback()', function()
local function tv_dict_get_callback(d, key, key_len, emsg)
key_len = key_len or #key