mirror of
https://github.com/neovim/neovim.git
synced 2025-09-16 16:28:17 +00:00
eval,ex_getln: Add support for coloring input() prompts
This commit is contained in:
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user