aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/eval.txt17
-rw-r--r--src/nvim/eval.c12
-rw-r--r--src/nvim/ex_getln.c88
-rw-r--r--src/nvim/ex_getln.h2
-rw-r--r--test/functional/eval/input_spec.lua57
-rw-r--r--test/functional/ui/cmdline_highlight_spec.lua50
6 files changed, 169 insertions, 57 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 2a73590c76..2262c01374 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -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
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 18aa5bf763..1cc4a3eb15 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -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);
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index e4914d6ebc..a2cc6d2b65 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -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;
- if (!tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_cmdline"),
- &color_cb)) {
- goto color_cmdline_error;
+ 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;
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index 051564fbe1..92cb274010 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -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
diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua
index 74ad32bc6c..f0c1314754 100644
--- a/test/functional/eval/input_spec.lua
+++ b/test/functional/eval/input_spec.lua
@@ -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)
diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua
index eb0a65bea7..4ccfb44261 100644
--- a/test/functional/ui/cmdline_highlight_spec.lua
+++ b/test/functional/ui/cmdline_highlight_spec.lua
@@ -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)
@@ -238,17 +239,25 @@ describe('Command-line coloring', function()
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()
@@ -256,21 +265,23 @@ describe('Command-line coloring', function()
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