diff options
| -rw-r--r-- | runtime/doc/eval.txt | 17 | ||||
| -rw-r--r-- | src/nvim/eval.c | 12 | ||||
| -rw-r--r-- | src/nvim/ex_getln.c | 88 | ||||
| -rw-r--r-- | src/nvim/ex_getln.h | 2 | ||||
| -rw-r--r-- | test/functional/eval/input_spec.lua | 57 | ||||
| -rw-r--r-- | test/functional/ui/cmdline_highlight_spec.lua | 50 | 
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  | 
