ext_cmdline: implement redraw!

This commit is contained in:
Björn Linse 2017-08-20 17:47:42 +02:00
parent 2050e66046
commit bed0a3a842
3 changed files with 146 additions and 81 deletions

View File

@ -67,6 +67,30 @@
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/highlight_defs.h" #include "nvim/highlight_defs.h"
/// Command-line colors: one chunk
///
/// Defines a region which has the same highlighting.
typedef struct {
int start; ///< Colored chunk start.
int end; ///< Colored chunk end (exclusive, > start).
int attr; ///< Highlight attr.
} CmdlineColorChunk;
/// Command-line colors
///
/// Holds data about all colors.
typedef kvec_t(CmdlineColorChunk) CmdlineColors;
/// Command-line coloring
///
/// Holds both what are the colors and what have been colored. Latter is used to
/// suppress unnecessary calls to coloring callbacks.
typedef struct {
unsigned prompt_id; ///< ID of the prompt which was colored last.
char *cmdbuff; ///< What exactly was colored last time or NULL.
CmdlineColors colors; ///< Last colors.
} ColoredCmdline;
/* /*
* Variables shared between getcmdline(), redrawcmdline() and others. * Variables shared between getcmdline(), redrawcmdline() and others.
* These need to be saved when using CTRL-R |, that's why they are in a * These need to be saved when using CTRL-R |, that's why they are in a
@ -91,8 +115,11 @@ struct cmdline_info {
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. unsigned prompt_id; ///< Prompt number, used to disable coloring on errors.
Callback highlight_callback; ///< Callback used for coloring user input. Callback highlight_callback; ///< Callback used for coloring user input.
ColoredCmdline last_colors; ///< Last cmdline colors
int level; // current cmdline level int level; // current cmdline level
struct cmdline_info *prev_ccline; ///< pointer to saved cmdline state struct cmdline_info *prev_ccline; ///< pointer to saved cmdline state
char special_char; ///< last putcmdline char (used for redraws)
bool special_shift; ///< shift of last putcmdline char
}; };
/// Last value of prompt_id, incremented when doing new prompt /// Last value of prompt_id, incremented when doing new prompt
static unsigned last_prompt_id = 0; static unsigned last_prompt_id = 0;
@ -145,36 +172,6 @@ typedef struct command_line_state {
struct cmdline_info save_ccline; struct cmdline_info save_ccline;
} CommandLineState; } CommandLineState;
/// Command-line colors: one chunk
///
/// Defines a region which has the same highlighting.
typedef struct {
int start; ///< Colored chunk start.
int end; ///< Colored chunk end (exclusive, > start).
int attr; ///< Highlight attr.
} CmdlineColorChunk;
/// Command-line colors
///
/// Holds data about all colors.
typedef kvec_t(CmdlineColorChunk) CmdlineColors;
/// Command-line coloring
///
/// Holds both what are the colors and what have been colored. Latter is used to
/// suppress unnecessary calls to coloring callbacks.
typedef struct {
unsigned prompt_id; ///< ID of the prompt which was colored last.
char *cmdbuff; ///< What exactly was colored last time or NULL.
CmdlineColors colors; ///< Last colors.
} ColoredCmdline;
/// Last command-line colors.
ColoredCmdline last_ccline_colors = {
.cmdbuff = NULL,
.colors = KV_INITIAL_VALUE
};
typedef struct cmdline_info CmdlineInfo; typedef struct cmdline_info CmdlineInfo;
/* The current cmdline_info. It is initialized in getcmdline() and after that /* The current cmdline_info. It is initialized in getcmdline() and after that
@ -242,7 +239,6 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
cmd_hkmap = 0; cmd_hkmap = 0;
} }
// TODO(bfredl): can these be combined?
ccline.prompt_id = last_prompt_id++; ccline.prompt_id = last_prompt_id++;
ccline.level++; ccline.level++;
ccline.overstrike = false; // always start in insert mode ccline.overstrike = false; // always start in insert mode
@ -264,6 +260,9 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
ccline.cmdlen = ccline.cmdpos = 0; ccline.cmdlen = ccline.cmdpos = 0;
ccline.cmdbuff[0] = NUL; ccline.cmdbuff[0] = NUL;
ccline.last_colors = (ColoredCmdline){ .cmdbuff = NULL,
.colors = KV_INITIAL_VALUE };
// autoindent for :insert and :append // autoindent for :insert and :append
if (s->firstc <= 0) { if (s->firstc <= 0) {
memset(ccline.cmdbuff, ' ', s->indent); memset(ccline.cmdbuff, ' ', s->indent);
@ -414,6 +413,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
setmouse(); setmouse();
ui_cursor_shape(); // may show different cursor shape ui_cursor_shape(); // may show different cursor shape
xfree(s->save_p_icm); xfree(s->save_p_icm);
xfree(ccline.last_colors.cmdbuff);
kv_destroy(ccline.last_colors.colors);
{ {
char_u *p = ccline.cmdbuff; char_u *p = ccline.cmdbuff;
@ -2364,8 +2365,7 @@ enum { MAX_CB_ERRORS = 1 };
/// Should use built-in command parser or user-specified one. Currently only the /// Should use built-in command parser or user-specified one. Currently only the
/// latter is supported. /// latter is supported.
/// ///
/// @param[in] colored_ccline Command-line to color. /// @param[in,out] colored_ccline Command-line to color. Also holds a cache:
/// @param[out] ret_ccline_colors What should be colored. Also holds a cache:
/// if ->prompt_id and ->cmdbuff values happen /// if ->prompt_id and ->cmdbuff values happen
/// to be equal to those from colored_cmdline it /// to be equal to those from colored_cmdline it
/// will just do nothing, assuming that ->colors /// will just do nothing, assuming that ->colors
@ -2375,8 +2375,7 @@ enum { MAX_CB_ERRORS = 1 };
/// ///
/// @return true if draw_cmdline may proceed, false if it does not need anything /// @return true if draw_cmdline may proceed, false if it does not need anything
/// to do. /// to do.
static bool color_cmdline(const CmdlineInfo *const colored_ccline, static bool color_cmdline(CmdlineInfo *colored_ccline)
ColoredCmdline *const ret_ccline_colors)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{ {
bool printed_errmsg = false; bool printed_errmsg = false;
@ -2389,19 +2388,21 @@ static bool color_cmdline(const CmdlineInfo *const colored_ccline,
} while (0) } while (0)
bool ret = true; bool ret = true;
ColoredCmdline *ccline_colors = &colored_ccline->last_colors;
// Check whether result of the previous call is still valid. // Check whether result of the previous call is still valid.
if (ret_ccline_colors->prompt_id == colored_ccline->prompt_id if (ccline_colors->prompt_id == colored_ccline->prompt_id
&& ret_ccline_colors->cmdbuff != NULL && ccline_colors->cmdbuff != NULL
&& STRCMP(ret_ccline_colors->cmdbuff, colored_ccline->cmdbuff) == 0) { && STRCMP(ccline_colors->cmdbuff, colored_ccline->cmdbuff) == 0) {
return ret; return ret;
} }
kv_size(ret_ccline_colors->colors) = 0; kv_size(ccline_colors->colors) = 0;
if (colored_ccline->cmdbuff == NULL || *colored_ccline->cmdbuff == NUL) { if (colored_ccline->cmdbuff == NULL || *colored_ccline->cmdbuff == NUL) {
// Nothing to do, exiting. // Nothing to do, exiting.
xfree(ret_ccline_colors->cmdbuff); xfree(ccline_colors->cmdbuff);
ret_ccline_colors->cmdbuff = NULL; ccline_colors->cmdbuff = NULL;
return ret; return ret;
} }
@ -2523,7 +2524,7 @@ static bool color_cmdline(const CmdlineInfo *const colored_ccline,
goto color_cmdline_error; goto color_cmdline_error;
} }
if (start != prev_end) { if (start != prev_end) {
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
.start = prev_end, .start = prev_end,
.end = start, .end = start,
.attr = 0, .attr = 0,
@ -2552,14 +2553,14 @@ static bool color_cmdline(const CmdlineInfo *const colored_ccline,
} }
const int id = syn_name2id((char_u *)group); const int id = syn_name2id((char_u *)group);
const int attr = (id == 0 ? 0 : syn_id2attr(id)); const int attr = (id == 0 ? 0 : syn_id2attr(id));
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
.start = start, .start = start,
.end = end, .end = end,
.attr = attr, .attr = attr,
})); }));
} }
if (prev_end < colored_ccline->cmdlen) { if (prev_end < colored_ccline->cmdlen) {
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
.start = prev_end, .start = prev_end,
.end = colored_ccline->cmdlen, .end = colored_ccline->cmdlen,
.attr = 0, .attr = 0,
@ -2571,14 +2572,14 @@ color_cmdline_end:
if (can_free_cb) { if (can_free_cb) {
callback_free(&color_cb); callback_free(&color_cb);
} }
xfree(ret_ccline_colors->cmdbuff); xfree(ccline_colors->cmdbuff);
// Note: errors “output” is cached just as well as regular results. // Note: errors “output” is cached just as well as regular results.
ret_ccline_colors->prompt_id = colored_ccline->prompt_id; ccline_colors->prompt_id = colored_ccline->prompt_id;
if (arg_allocated) { if (arg_allocated) {
ret_ccline_colors->cmdbuff = (char *)arg.vval.v_string; ccline_colors->cmdbuff = (char *)arg.vval.v_string;
} else { } else {
ret_ccline_colors->cmdbuff = xmemdupz((const char *)colored_ccline->cmdbuff, ccline_colors->cmdbuff = xmemdupz((const char *)colored_ccline->cmdbuff,
(size_t)colored_ccline->cmdlen); (size_t)colored_ccline->cmdlen);
} }
tv_clear(&tv); tv_clear(&tv);
return ret; return ret;
@ -2591,7 +2592,7 @@ color_cmdline_error:
(void)printed_errmsg; (void)printed_errmsg;
prev_prompt_errors++; prev_prompt_errors++;
kv_size(ret_ccline_colors->colors) = 0; kv_size(ccline_colors->colors) = 0;
redrawcmdline(); redrawcmdline();
ret = false; ret = false;
goto color_cmdline_end; goto color_cmdline_end;
@ -2604,12 +2605,13 @@ color_cmdline_error:
*/ */
static void draw_cmdline(int start, int len) static void draw_cmdline(int start, int len)
{ {
if (!color_cmdline(&ccline, &last_ccline_colors)) { if (!color_cmdline(&ccline)) {
return; return;
} }
if (ui_is_external(kUICmdline)) { if (ui_is_external(kUICmdline)) {
ui_ext_cmdline_show(); ccline.special_char = NUL;
ui_ext_cmdline_show(&ccline);
return; return;
} }
@ -2711,9 +2713,9 @@ static void draw_cmdline(int start, int len)
msg_outtrans_len(arshape_buf, newlen); msg_outtrans_len(arshape_buf, newlen);
} else { } else {
draw_cmdline_no_arabicshape: draw_cmdline_no_arabicshape:
if (kv_size(last_ccline_colors.colors)) { if (kv_size(ccline.last_colors.colors)) {
for (size_t i = 0; i < kv_size(last_ccline_colors.colors); i++) { for (size_t i = 0; i < kv_size(ccline.last_colors.colors); i++) {
CmdlineColorChunk chunk = kv_A(last_ccline_colors.colors, i); CmdlineColorChunk chunk = kv_A(ccline.last_colors.colors, i);
if (chunk.end <= start) { if (chunk.end <= start) {
continue; continue;
} }
@ -2728,12 +2730,12 @@ draw_cmdline_no_arabicshape:
} }
} }
void ui_ext_cmdline_show(void) static void ui_ext_cmdline_show(CmdlineInfo *line)
{ {
Array content = ARRAY_DICT_INIT; Array content = ARRAY_DICT_INIT;
if (kv_size(last_ccline_colors.colors)) { if (kv_size(line->last_colors.colors)) {
for (size_t i = 0; i < kv_size(last_ccline_colors.colors); i++) { for (size_t i = 0; i < kv_size(line->last_colors.colors); i++) {
CmdlineColorChunk chunk = kv_A(last_ccline_colors.colors, i); CmdlineColorChunk chunk = kv_A(line->last_colors.colors, i);
Array item = ARRAY_DICT_INIT; Array item = ARRAY_DICT_INIT;
if (chunk.attr) { if (chunk.attr) {
@ -2745,21 +2747,26 @@ void ui_ext_cmdline_show(void)
} else { } else {
ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
} }
ADD(item, STRING_OBJ(cbuf_to_string((char *)ccline.cmdbuff + chunk.start, ADD(item, STRING_OBJ(cbuf_to_string((char *)line->cmdbuff + chunk.start,
chunk.end-chunk.start))); chunk.end-chunk.start)));
ADD(content, ARRAY_OBJ(item)); ADD(content, ARRAY_OBJ(item));
} }
} else { } else {
Array item = ARRAY_DICT_INIT; Array item = ARRAY_DICT_INIT;
ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
ADD(item, STRING_OBJ(cstr_to_string((char *)(ccline.cmdbuff)))); ADD(item, STRING_OBJ(cstr_to_string((char *)(line->cmdbuff))));
ADD(content, ARRAY_OBJ(item)); ADD(content, ARRAY_OBJ(item));
} }
ui_call_cmdline_show(content, ccline.cmdpos, ui_call_cmdline_show(content, line->cmdpos,
cchar_to_string((char)ccline.cmdfirstc), cchar_to_string((char)line->cmdfirstc),
cstr_to_string((char *)(ccline.cmdprompt)), cstr_to_string((char *)(line->cmdprompt)),
ccline.cmdindent, line->cmdindent,
ccline.level); line->level);
if (line->special_char) {
ui_call_cmdline_special_char(cchar_to_string((char)(line->special_char)),
line->special_shift,
line->level);
}
} }
void ui_ext_cmdline_block_append(int indent, const char *line) void ui_ext_cmdline_block_append(int indent, const char *line)
@ -2787,6 +2794,28 @@ void ui_ext_cmdline_block_leave(void)
ui_call_cmdline_block_hide(); ui_call_cmdline_block_hide();
} }
/// Extra redrawing needed for redraw! and on ui_attach
/// assumes "redrawcmdline()" will already be invoked
void cmdline_screen_cleared(void)
{
if (!ui_is_external(kUICmdline)) {
return;
}
if (cmdline_block.size) {
ui_call_cmdline_block_show(copy_array(cmdline_block));
}
int prev_level = ccline.level-1;
CmdlineInfo *prev_ccline = ccline.prev_ccline;
while (prev_level > 0 && prev_ccline) {
if (prev_ccline->level == prev_level) {
ui_ext_cmdline_show(prev_ccline);
prev_level--;
}
prev_ccline = prev_ccline->prev_ccline;
}
}
/* /*
* Put a character on the command line. Shifts the following text to the * Put a character on the command line. Shifts the following text to the
@ -2806,6 +2835,8 @@ void putcmdline(int c, int shift)
} }
msg_no_more = false; msg_no_more = false;
} else { } else {
ccline.special_char = c;
ccline.special_shift = shift;
ui_call_cmdline_special_char(cchar_to_string((char)(c)), shift, ui_call_cmdline_special_char(cchar_to_string((char)(c)), shift,
ccline.level); ccline.level);
} }
@ -2971,6 +3002,7 @@ static void save_cmdline(struct cmdline_info *ccp)
ccline.cmdbuff = NULL; ccline.cmdbuff = NULL;
ccline.cmdprompt = NULL; ccline.cmdprompt = NULL;
ccline.xpc = NULL; ccline.xpc = NULL;
ccline.special_char = NUL;
} }
/* /*
@ -3147,7 +3179,7 @@ static void redrawcmdprompt(void)
if (cmd_silent) if (cmd_silent)
return; return;
if (ui_is_external(kUICmdline)) { if (ui_is_external(kUICmdline)) {
ui_ext_cmdline_show(); ui_ext_cmdline_show(&ccline);
return; return;
} }
if (ccline.cmdfirstc != NUL) { if (ccline.cmdfirstc != NUL) {

View File

@ -345,8 +345,9 @@ void update_screen(int type)
if (need_highlight_changed) if (need_highlight_changed)
highlight_changed(); highlight_changed();
if (type == CLEAR) { /* first clear screen */ if (type == CLEAR) { // first clear screen
screenclear(); /* will reset clear_cmdline */ screenclear(); // will reset clear_cmdline
cmdline_screen_cleared(); // clear external cmdline state
type = NOT_VALID; type = NOT_VALID;
} }

View File

@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen')
local clear, feed, eq = helpers.clear, helpers.feed, helpers.eq local clear, feed, eq = helpers.clear, helpers.feed, helpers.eq
local source = helpers.source local source = helpers.source
local ok = helpers.ok local ok = helpers.ok
local command = helpers.command
if helpers.pending_win32(pending) then return end if helpers.pending_win32(pending) then return end
@ -218,14 +219,7 @@ describe('external cmdline', function()
end) end)
feed('1+2') feed('1+2')
screen:expect([[ local expectation = {{
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({{
content = { { {}, "xx" } }, content = { { {}, "xx" } },
firstc = ":", firstc = ":",
indent = 0, indent = 0,
@ -238,16 +232,40 @@ describe('external cmdline', function()
indent = 0, indent = 0,
pos = 3, pos = 3,
prompt = "", prompt = "",
}}, cmdline) }}
end)
feed('<cr>')
screen:expect([[ screen:expect([[
^ | ^ |
~ | ~ |
~ | ~ |
~ | ~ |
| |
]], nil, nil, function()
eq(expectation, cmdline)
end)
-- erase information, so we check if it is retransmitted
cmdline = {}
command("redraw!")
-- redraw! forgets cursor position. Be OK with that, as UI should indicate
-- focus is at external cmdline anyway.
screen:expect([[
|
~ |
~ |
~ |
^ |
]], nil, nil, function()
eq(expectation, cmdline)
end)
feed('<cr>')
screen:expect([[
|
~ |
~ |
~ |
^ |
]], nil, nil, function() ]], nil, nil, function()
eq({{ eq({{
content = { { {}, "xx3" } }, content = { { {}, "xx3" } },
@ -301,6 +319,20 @@ describe('external cmdline', function()
{{{}, ' line1'}}}, block) {{{}, ' line1'}}}, block)
end) end)
block = {}
command("redraw!")
screen:expect([[
|
~ |
~ |
~ |
^ |
]], nil, nil, function()
eq({{{{}, 'function Foo()'}},
{{{}, ' line1'}}}, block)
end)
feed('endfunction<cr>') feed('endfunction<cr>')
screen:expect([[ screen:expect([[
^ | ^ |