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
|inputdialog()|. Also works with
input().
highlight nothing Highlight handler: |Funcref|.
The highlighting set with |:echohl| is used for the prompt.
The input is entered just like a command-line, with the same
@@ -4722,6 +4723,22 @@ input({opts})
more information. Example: >
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
the versions that only run in GUI mode (e.g., the Win32 GUI).
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 *cancelreturn = NULL;
const char *xp_name = NULL;
Callback input_callback = { .type = kCallbackNone };
char prompt_buf[NUMBUFLEN];
char defstr_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"));
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, "");
if (prompt == NULL) {
return;
@@ -11045,6 +11046,9 @@ void get_user_input(const typval_T *const argvars,
if (xp_name == def) { // default to NULL
xp_name = NULL;
}
if (!tv_dict_get_callback(dict, S_LEN("highlight"), &input_callback)) {
return;
}
} else {
prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf);
if (prompt == NULL) {
@@ -11103,12 +11107,16 @@ void get_user_input(const typval_T *const argvars,
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;
getln_input_callback = input_callback;
rettv->vval.v_string =
getcmdline_prompt(inputsecret_flag ? NUL : '@', (char_u *)p, echo_attr,
xp_type, (char_u *)xp_arg);
getln_input_callback = save_getln_input_callback;
ex_normal_busy = save_ex_normal_busy;
callback_free(&input_callback);
if (rettv->vval.v_string == NULL && cancelreturn != NULL) {
rettv->vval.v_string = (char_u *)xstrdup(cancelreturn);

View File

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

View File

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

View File

@@ -23,10 +23,41 @@ before_each(function()
function CustomListCompl(...)
return ['FOO']
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({
EOB={bold = true, foreground = Screen.colors.Blue1},
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)
@@ -196,6 +227,19 @@ describe('input()', function()
eq('Vim(call):E118: Too many arguments for function: input',
exc_exec('call input("prompt> ", "default", "file", "extra")'))
end)
it('supports highlighting', function()
feed([[:call input({"highlight": "RainBowParens"})<CR>]])
wait()
feed('(())')
wait()
screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
|
{RBP1:(}{RBP2:()}{RBP1:)}^ |
]])
end)
end)
describe('inputdialog()', function()
it('works with multiline prompts', function()
@@ -363,4 +407,17 @@ describe('inputdialog()', function()
eq('Vim(call):E118: Too many arguments for function: inputdialog',
exc_exec('call inputdialog("prompt> ", "default", "file", "extra")'))
end)
it('supports highlighting', function()
feed([[:call inputdialog({"highlight": "RainBowParens"})<CR>]])
wait()
feed('(())')
wait()
screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
|
{RBP1:(}{RBP2:()}{RBP1:)}^ |
]])
end)
end)

View File

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