eval,ex_getln: Add support for coloring input() prompts

This commit is contained in:
ZyX
2017-04-02 01:00:40 +03:00
parent d82741f8c0
commit 407abb3a6c
6 changed files with 169 additions and 57 deletions

View File

@@ -4699,6 +4699,7 @@ input({opts})
cancelreturn "" Same as {cancelreturn} from cancelreturn "" Same as {cancelreturn} from
|inputdialog()|. Also works with |inputdialog()|. Also works with
input(). input().
highlight nothing Highlight handler: |Funcref|.
The highlighting set with |:echohl| is used for the prompt. The highlighting set with |:echohl| is used for the prompt.
The input is entered just like a command-line, with the same The input is entered just like a command-line, with the same
@@ -4722,6 +4723,22 @@ input({opts})
more information. Example: > more information. Example: >
let fname = input("File: ", "", "file") let fname = input("File: ", "", "file")
< <
The optional highlight key allows specifying function which
will be used for highlighting. This function receives user
input as its only argument and must return a list of 3-tuples
[hl_start_byte, hl_end_byte + 1, hl_group] where
hl_start_byte is the first highlighted byte,
hl_end_byte is the last highlighted byte (+ 1!),
hl_group is |:hl| group used for highlighting.
*E5403* *E5404* *E5405* *E5406*
Both hl_start_byte and hl_end_byte + 1 must point to the start
of the multibyte character (highlighting must not break
multibyte characters), hl_end_byte + 1 may be equal to the
input length. Start column must be in range [0, len(input)),
end column must be in range (hl_start_byte, len(input)],
sections must be ordered so that next hl_start_byte is greater
then or equal to previous hl_end_byte.
NOTE: This function must not be used in a startup file, for NOTE: This function must not be used in a startup file, for
the versions that only run in GUI mode (e.g., the Win32 GUI). the versions that only run in GUI mode (e.g., the Win32 GUI).
Note: When input() is called from within a mapping it will Note: When input() is called from within a mapping it will

View File

@@ -11010,6 +11010,7 @@ void get_user_input(const typval_T *const argvars,
const char *defstr = ""; const char *defstr = "";
const char *cancelreturn = NULL; const char *cancelreturn = NULL;
const char *xp_name = NULL; const char *xp_name = NULL;
Callback input_callback = { .type = kCallbackNone };
char prompt_buf[NUMBUFLEN]; char prompt_buf[NUMBUFLEN];
char defstr_buf[NUMBUFLEN]; char defstr_buf[NUMBUFLEN];
char cancelreturn_buf[NUMBUFLEN]; char cancelreturn_buf[NUMBUFLEN];
@@ -11019,7 +11020,7 @@ void get_user_input(const typval_T *const argvars,
emsgf(_("E5050: {opts} must be the only argument")); emsgf(_("E5050: {opts} must be the only argument"));
return; return;
} }
const dict_T *const dict = argvars[0].vval.v_dict; dict_T *const dict = argvars[0].vval.v_dict;
prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, ""); prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, "");
if (prompt == NULL) { if (prompt == NULL) {
return; return;
@@ -11045,6 +11046,9 @@ void get_user_input(const typval_T *const argvars,
if (xp_name == def) { // default to NULL if (xp_name == def) { // default to NULL
xp_name = NULL; xp_name = NULL;
} }
if (!tv_dict_get_callback(dict, S_LEN("highlight"), &input_callback)) {
return;
}
} else { } else {
prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf); prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf);
if (prompt == NULL) { if (prompt == NULL) {
@@ -11103,12 +11107,16 @@ void get_user_input(const typval_T *const argvars,
stuffReadbuffSpec(defstr); stuffReadbuffSpec(defstr);
int save_ex_normal_busy = ex_normal_busy; const int save_ex_normal_busy = ex_normal_busy;
const Callback save_getln_input_callback = getln_input_callback;
ex_normal_busy = 0; ex_normal_busy = 0;
getln_input_callback = input_callback;
rettv->vval.v_string = rettv->vval.v_string =
getcmdline_prompt(inputsecret_flag ? NUL : '@', (char_u *)p, echo_attr, getcmdline_prompt(inputsecret_flag ? NUL : '@', (char_u *)p, echo_attr,
xp_type, (char_u *)xp_arg); xp_type, (char_u *)xp_arg);
getln_input_callback = save_getln_input_callback;
ex_normal_busy = save_ex_normal_busy; ex_normal_busy = save_ex_normal_busy;
callback_free(&input_callback);
if (rettv->vval.v_string == NULL && cancelreturn != NULL) { if (rettv->vval.v_string == NULL && cancelreturn != NULL) {
rettv->vval.v_string = (char_u *)xstrdup(cancelreturn); rettv->vval.v_string = (char_u *)xstrdup(cancelreturn);

View File

@@ -70,23 +70,26 @@
* structure. * structure.
*/ */
struct cmdline_info { struct cmdline_info {
char_u *cmdbuff; /* pointer to command line buffer */ char_u *cmdbuff; // pointer to command line buffer
int cmdbufflen; /* length of cmdbuff */ int cmdbufflen; // length of cmdbuff
int cmdlen; /* number of chars in command line */ int cmdlen; // number of chars in command line
int cmdpos; /* current cursor position */ int cmdpos; // current cursor position
int cmdspos; /* cursor column on screen */ int cmdspos; // cursor column on screen
int cmdfirstc; /* ':', '/', '?', '=', '>' or NUL */ int cmdfirstc; // ':', '/', '?', '=', '>' or NUL
int cmdindent; /* number of spaces before cmdline */ int cmdindent; // number of spaces before cmdline
char_u *cmdprompt; /* message in front of cmdline */ char_u *cmdprompt; // message in front of cmdline
int cmdattr; /* attributes for prompt */ int cmdattr; // attributes for prompt
int overstrike; /* Typing mode on the command line. Shared by int overstrike; // Typing mode on the command line. Shared by
getcmdline() and put_on_cmdline(). */ // getcmdline() and put_on_cmdline().
expand_T *xpc; /* struct being used for expansion, xp_pattern expand_T *xpc; // struct being used for expansion, xp_pattern
may point into cmdbuff */ // may point into cmdbuff
int xp_context; /* type of expansion */ int xp_context; // type of expansion
char_u *xp_arg; /* user-defined expansion arg */ char_u *xp_arg; // user-defined expansion arg
int input_fn; /* when TRUE Invoked for input() function */ int input_fn; // when TRUE Invoked for input() function
unsigned prompt_id; ///< Prompt number, used to disable coloring on errors.
}; };
/// Last value of prompt_id, incremented when doing new prompt
static unsigned last_prompt_id = 0;
typedef struct command_line_state { typedef struct command_line_state {
VimState state; VimState state;
@@ -135,6 +138,9 @@ typedef struct {
int attr; ///< Highlight attr. int attr; ///< Highlight attr.
} ColoredCmdlineChunk; } ColoredCmdlineChunk;
/// Callback used for coloring input() prompts
Callback getln_input_callback = { .type = kCallbackNone };
kvec_t(ColoredCmdlineChunk) ccline_colors; kvec_t(ColoredCmdlineChunk) ccline_colors;
/* The current cmdline_info. It is initialized in getcmdline() and after that /* The current cmdline_info. It is initialized in getcmdline() and after that
@@ -188,6 +194,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
cmd_hkmap = 0; cmd_hkmap = 0;
} }
ccline.prompt_id = last_prompt_id++;
ccline.overstrike = false; // always start in insert mode ccline.overstrike = false; // always start in insert mode
s->old_cursor = curwin->w_cursor; // needs to be restored later s->old_cursor = curwin->w_cursor; // needs to be restored later
s->old_curswant = curwin->w_curswant; s->old_curswant = curwin->w_curswant;
@@ -1703,6 +1710,7 @@ getcmdline_prompt (
int msg_col_save = msg_col; int msg_col_save = msg_col;
save_cmdline(&save_ccline); save_cmdline(&save_ccline);
ccline.prompt_id = last_prompt_id++;
ccline.cmdprompt = prompt; ccline.cmdprompt = prompt;
ccline.cmdattr = attr; ccline.cmdattr = attr;
ccline.xp_context = xp_context; ccline.xp_context = xp_context;
@@ -2178,7 +2186,7 @@ void free_cmdline_buf(void)
# endif # endif
enum { MAX_CB_ERRORS = 5 }; enum { MAX_CB_ERRORS = 1 };
/// Color command-line /// Color command-line
/// ///
@@ -2195,9 +2203,6 @@ static bool color_cmdline(void)
{ {
bool ret = true; bool ret = true;
kv_size(ccline_colors) = 0; kv_size(ccline_colors) = 0;
if (ccline.cmdfirstc != ':') {
return ret;
}
const int saved_force_abort = force_abort; const int saved_force_abort = force_abort;
force_abort = true; force_abort = true;
@@ -2208,24 +2213,32 @@ static bool color_cmdline(void)
}; };
typval_T tv = { .v_type = VAR_UNKNOWN }; typval_T tv = { .v_type = VAR_UNKNOWN };
static Callback prev_cb = { .type = kCallbackNone }; static unsigned prev_prompt_id = UINT_MAX;
static int prev_cb_errors = 0; static int prev_prompt_errors = 0;
Callback color_cb; Callback color_cb = { .type = kCallbackNone };
if (!tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_cmdline"), bool can_free_cb = false;
&color_cb)) {
goto color_cmdline_error; if (ccline.input_fn) {
color_cb = getln_input_callback;
} else if (ccline.cmdfirstc == ':') {
if (!tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_cmdline"),
&color_cb)) {
goto color_cmdline_error;
}
can_free_cb = true;
} else {
goto color_cmdline_end;
} }
if (color_cb.type == kCallbackNone) { if (color_cb.type == kCallbackNone) {
goto color_cmdline_end; goto color_cmdline_end;
} }
if (!tv_callback_equal(&prev_cb, &color_cb)) { if (ccline.prompt_id != prev_prompt_id) {
prev_cb_errors = 0; prev_prompt_errors = 0;
} else if (prev_cb_errors >= MAX_CB_ERRORS) { prev_prompt_id = ccline.prompt_id;
callback_free(&color_cb); } else if (prev_prompt_errors >= MAX_CB_ERRORS) {
goto color_cmdline_end; goto color_cmdline_end;
} }
callback_free(&prev_cb);
prev_cb = color_cb;
if (ccline.cmdbuff[ccline.cmdlen] != NUL) { if (ccline.cmdbuff[ccline.cmdlen] != NUL) {
arg_allocated = true; arg_allocated = true;
arg.vval.v_string = xmemdupz((const char *)ccline.cmdbuff, arg.vval.v_string = xmemdupz((const char *)ccline.cmdbuff,
@@ -2329,8 +2342,11 @@ static bool color_cmdline(void)
.attr = 0, .attr = 0,
})); }));
} }
prev_cb_errors = 0; prev_prompt_errors = 0;
color_cmdline_end: color_cmdline_end:
if (can_free_cb) {
callback_free(&color_cb);
}
force_abort = saved_force_abort; force_abort = saved_force_abort;
if (arg_allocated) { if (arg_allocated) {
tv_clear(&arg); tv_clear(&arg);
@@ -2338,7 +2354,7 @@ color_cmdline_end:
tv_clear(&tv); tv_clear(&tv);
return ret; return ret;
color_cmdline_error: color_cmdline_error:
prev_cb_errors++; prev_prompt_errors++;
const bool do_redraw = (did_emsg || got_int); const bool do_redraw = (did_emsg || got_int);
got_int = false; got_int = false;
did_emsg = false; did_emsg = false;
@@ -2350,9 +2366,9 @@ color_cmdline_error:
} }
kv_size(ccline_colors) = 0; kv_size(ccline_colors) = 0;
if (do_redraw) { if (do_redraw) {
prev_cb_errors += MAX_CB_ERRORS; prev_prompt_errors += MAX_CB_ERRORS;
redrawcmdline(); redrawcmdline();
prev_cb_errors -= MAX_CB_ERRORS; prev_prompt_errors -= MAX_CB_ERRORS;
ret = false; ret = false;
} }
goto color_cmdline_end; goto color_cmdline_end;

View File

@@ -52,6 +52,8 @@ typedef struct hist_entry {
list_T *additional_elements; ///< Additional entries from ShaDa file. list_T *additional_elements; ///< Additional entries from ShaDa file.
} histentry_T; } histentry_T;
extern Callback getln_input_callback;
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_getln.h.generated.h" # include "ex_getln.h.generated.h"
#endif #endif

View File

@@ -23,10 +23,41 @@ before_each(function()
function CustomListCompl(...) function CustomListCompl(...)
return ['FOO'] return ['FOO']
endfunction endfunction
highlight RBP1 guibg=Red
highlight RBP2 guibg=Yellow
highlight RBP3 guibg=Green
highlight RBP4 guibg=Blue
let g:NUM_LVLS = 4
function Redraw()
redraw!
return ''
endfunction
cnoremap <expr> {REDRAW} Redraw()
function RainBowParens(cmdline)
let ret = []
let i = 0
let lvl = 0
while i < len(a:cmdline)
if a:cmdline[i] is# '('
call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)])
let lvl += 1
elseif a:cmdline[i] is# ')'
let lvl -= 1
call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)])
endif
let i += 1
endwhile
return ret
endfunction
]]) ]])
screen:set_default_attr_ids({ screen:set_default_attr_ids({
EOB={bold = true, foreground = Screen.colors.Blue1}, EOB={bold = true, foreground = Screen.colors.Blue1},
T={foreground=Screen.colors.Red}, T={foreground=Screen.colors.Red},
RBP1={background=Screen.colors.Red},
RBP2={background=Screen.colors.Yellow},
RBP3={background=Screen.colors.Green},
RBP4={background=Screen.colors.Blue},
}) })
end) end)
@@ -196,6 +227,19 @@ describe('input()', function()
eq('Vim(call):E118: Too many arguments for function: input', eq('Vim(call):E118: Too many arguments for function: input',
exc_exec('call input("prompt> ", "default", "file", "extra")')) exc_exec('call input("prompt> ", "default", "file", "extra")'))
end) end)
it('supports highlighting', function()
feed([[:call input({"highlight": "RainBowParens"})<CR>]])
wait()
feed('(())')
wait()
screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
|
{RBP1:(}{RBP2:()}{RBP1:)}^ |
]])
end)
end) end)
describe('inputdialog()', function() describe('inputdialog()', function()
it('works with multiline prompts', function() it('works with multiline prompts', function()
@@ -363,4 +407,17 @@ describe('inputdialog()', function()
eq('Vim(call):E118: Too many arguments for function: inputdialog', eq('Vim(call):E118: Too many arguments for function: inputdialog',
exc_exec('call inputdialog("prompt> ", "default", "file", "extra")')) exc_exec('call inputdialog("prompt> ", "default", "file", "extra")'))
end) end)
it('supports highlighting', function()
feed([[:call inputdialog({"highlight": "RainBowParens"})<CR>]])
wait()
feed('(())')
wait()
screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
|
{RBP1:(}{RBP2:()}{RBP1:)}^ |
]])
end)
end) end)

View File

@@ -15,10 +15,10 @@ before_each(function()
screen = Screen.new(40, 8) screen = Screen.new(40, 8)
screen:attach() screen:attach()
source([[ source([[
highlight RBP1 guifg=Red highlight RBP1 guibg=Red
highlight RBP2 guifg=Yellow highlight RBP2 guibg=Yellow
highlight RBP3 guifg=Green highlight RBP3 guibg=Green
highlight RBP4 guifg=Blue highlight RBP4 guibg=Blue
let g:NUM_LVLS = 4 let g:NUM_LVLS = 4
function Redraw() function Redraw()
redraw! redraw!
@@ -103,12 +103,13 @@ before_each(function()
endfunction endfunction
]]) ]])
screen:set_default_attr_ids({ screen:set_default_attr_ids({
RBP1={foreground = Screen.colors.Red}, RBP1={background = Screen.colors.Red},
RBP2={foreground = Screen.colors.Yellow}, RBP2={background = Screen.colors.Yellow},
RBP3={foreground = Screen.colors.Green}, RBP3={background = Screen.colors.Green},
RBP4={foreground = Screen.colors.Blue}, RBP4={background = Screen.colors.Blue},
EOB={bold = true, foreground = Screen.colors.Blue1}, EOB={bold = true, foreground = Screen.colors.Blue1},
ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red}, ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red},
SK={foreground = Screen.colors.Blue},
}) })
end) end)
@@ -237,40 +238,50 @@ describe('Command-line coloring', function()
meths.set_var('Nvim_color_cmdline', 'SplittedMultibyteStart') meths.set_var('Nvim_color_cmdline', 'SplittedMultibyteStart')
feed(':echo "«') feed(':echo "«')
screen:expect([[ screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }| {EOB:~ }|
:echo " | :echo " |
{ERR:E5405: Chunk 0 start 7 splits multibyte }| {ERR:E5405: Chunk 0 start 7 splits multibyte }|
{ERR:character} | {ERR:character} |
:echo "« |
{ERR:E5405: Chunk 0 start 7 splits multibyte }|
{ERR:character} |
:echo "«^ | :echo "«^ |
]]) ]])
feed('»') feed('»')
-- FIXME Does not work well with too much error messages: they overwrite screen:expect([[
-- cmdline. {EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
:echo " |
{ERR:E5405: Chunk 0 start 7 splits multibyte }|
{ERR:character} |
:echo "«»^ |
]])
end) end)
it('does the right thing when hl end appears to split multibyte char', it('does the right thing when hl end appears to split multibyte char',
function() function()
meths.set_var('Nvim_color_cmdline', 'SplittedMultibyteEnd') meths.set_var('Nvim_color_cmdline', 'SplittedMultibyteEnd')
feed(':echo "«') feed(':echo "«')
screen:expect([[ screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }| {EOB:~ }|
:echo " | :echo " |
{ERR:E5406: Chunk 0 end 7 splits multibyte ch}| {ERR:E5406: Chunk 0 end 7 splits multibyte ch}|
{ERR:aracter} | {ERR:aracter} |
:echo "« |
{ERR:E5406: Chunk 0 end 7 splits multibyte ch}|
{ERR:aracter} |
:echo "«^ | :echo "«^ |
]]) ]])
end) end)
it('does the right thing when errorring', function() it('does the right thing when errorring', function()
if true then return pending('echoerr does not work well now') end
meths.set_var('Nvim_color_cmdline', 'Echoerring') meths.set_var('Nvim_color_cmdline', 'Echoerring')
feed(':e') feed(':e')
-- FIXME Does not work well with :echoerr: error message overwrites cmdline. -- FIXME Does not work well with :echoerr: error message overwrites cmdline.
end) end)
it('does the right thing when throwing', function() it('does the right thing when throwing', function()
if true then return pending('Throwing does not work well now') end
meths.set_var('Nvim_color_cmdline', 'Throwing') meths.set_var('Nvim_color_cmdline', 'Throwing')
feed(':e') feed(':e')
-- FIXME Does not work well with :throw: error message overwrites cmdline. -- FIXME Does not work well with :throw: error message overwrites cmdline.
@@ -280,16 +291,17 @@ describe('Command-line coloring', function()
feed(':let x = "«"\n') feed(':let x = "«"\n')
eq('«', meths.get_var('x')) eq('«', meths.get_var('x'))
local msg = 'E5405: Chunk 0 start 10 splits multibyte character' local msg = 'E5405: Chunk 0 start 10 splits multibyte character'
eq('\n'..msg..'\n'..msg, funcs.execute('messages')) eq('\n'..msg, funcs.execute('messages'))
end) end)
it('stops executing callback after a number of errors', function() it('stops executing callback after a number of errors', function()
meths.set_var('Nvim_color_cmdline', 'SplittedMultibyteStart') meths.set_var('Nvim_color_cmdline', 'SplittedMultibyteStart')
feed(':let x = "«»«»«»«»«»"\n') feed(':let x = "«»«»«»«»«»"\n')
eq('«»«»«»«»«»', meths.get_var('x')) eq('«»«»«»«»«»', meths.get_var('x'))
local msg = '\nE5405: Chunk 0 start 10 splits multibyte character' local msg = '\nE5405: Chunk 0 start 10 splits multibyte character'
eq(msg:rep(5), funcs.execute('messages')) eq(msg:rep(1), funcs.execute('messages'))
end) end)
it('allows interrupting callback with <C-c>', function() it('allows interrupting callback with <C-c>', function()
if true then return pending('<C-c> does not work well enough now') end
meths.set_var('Nvim_color_cmdline', 'Halting') meths.set_var('Nvim_color_cmdline', 'Halting')
feed(':echo 42') feed(':echo 42')
for i = 1, 6 do for i = 1, 6 do
@@ -338,7 +350,7 @@ describe('Command-line coloring', function()
{EOB:~ }| {EOB:~ }|
{EOB:~ }| {EOB:~ }|
{EOB:~ }| {EOB:~ }|
:echo {RBP1:(}"{RBP4:^M^@^@}"{RBP1:)}^ | :echo {RBP1:(}"{SK:^M^@^@}"{RBP1:)}^ |
]]) ]])
end) end)
-- TODO Check for all other errors -- TODO Check for all other errors