ui/tui: highlighting refactor

Make HlAttr contain highlighting state for both color modes (cterm and rgb).
This allows us to implement termguicolors completely in the TUI.

Simplify some logic duplicated between ui.c and screen.c. Also avoid
some superfluous highlighting reset events.
This commit is contained in:
Björn Linse
2018-02-06 19:46:45 +01:00
parent c205360f00
commit 5d8da126d0
18 changed files with 195 additions and 289 deletions

View File

@@ -86,6 +86,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
ui->put = remote_ui_put; ui->put = remote_ui_put;
ui->bell = remote_ui_bell; ui->bell = remote_ui_bell;
ui->visual_bell = remote_ui_visual_bell; ui->visual_bell = remote_ui_visual_bell;
ui->default_colors_set = remote_ui_default_colors_set;
ui->update_fg = remote_ui_update_fg; ui->update_fg = remote_ui_update_fg;
ui->update_bg = remote_ui_update_bg; ui->update_bg = remote_ui_update_bg;
ui->update_sp = remote_ui_update_sp; ui->update_sp = remote_ui_update_sp;
@@ -243,7 +244,7 @@ static void push_call(UI *ui, char *name, Array args)
static void remote_ui_highlight_set(UI *ui, HlAttrs attrs) static void remote_ui_highlight_set(UI *ui, HlAttrs attrs)
{ {
Array args = ARRAY_DICT_INIT; Array args = ARRAY_DICT_INIT;
Dictionary hl = hlattrs2dict(attrs); Dictionary hl = hlattrs2dict(&attrs, ui->rgb);
ADD(args, DICTIONARY_OBJ(hl)); ADD(args, DICTIONARY_OBJ(hl));
push_call(ui, "highlight_set", args); push_call(ui, "highlight_set", args);

View File

@@ -47,11 +47,14 @@ void visual_bell(void)
void flush(void) void flush(void)
FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL;
void update_fg(Integer fg) void update_fg(Integer fg)
FUNC_API_SINCE(3); FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL;
void update_bg(Integer bg) void update_bg(Integer bg)
FUNC_API_SINCE(3); FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL;
void update_sp(Integer sp) void update_sp(Integer sp)
FUNC_API_SINCE(3); FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL;
void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
FUNC_API_SINCE(4);
void suspend(void) void suspend(void)
FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL;
void set_title(String title) void set_title(String title)
@@ -59,7 +62,7 @@ void set_title(String title)
void set_icon(String icon) void set_icon(String icon)
FUNC_API_SINCE(3); FUNC_API_SINCE(3);
void option_set(String name, Object value) void option_set(String name, Object value)
FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; FUNC_API_SINCE(4);
void popupmenu_show(Array items, Integer selected, Integer row, Integer col) void popupmenu_show(Array items, Integer selected, Integer row, Integer col)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;

View File

@@ -2929,11 +2929,11 @@ static void ui_ext_cmdline_show(CmdlineInfo *line)
Array item = ARRAY_DICT_INIT; Array item = ARRAY_DICT_INIT;
if (chunk.attr) { if (chunk.attr) {
attrentry_T *aep = syn_cterm_attr2entry(chunk.attr); HlAttrs *aep = syn_cterm_attr2entry(chunk.attr);
// TODO(bfredl): this desicion could be delayed by making attr_code a // TODO(bfredl): this desicion could be delayed by making attr_code a
// recognized type // recognized type
HlAttrs rgb_attrs = attrentry2hlattrs(aep, true); Dictionary rgb_attrs = hlattrs2dict(aep, true);
ADD(item, DICTIONARY_OBJ(hlattrs2dict(rgb_attrs))); ADD(item, DICTIONARY_OBJ(rgb_attrs));
} else { } else {
ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
} }

View File

@@ -7,6 +7,34 @@
typedef int32_t RgbValue; typedef int32_t RgbValue;
/// Highlighting attribute bits.
typedef enum {
HL_INVERSE = 0x01,
HL_BOLD = 0x02,
HL_ITALIC = 0x04,
HL_UNDERLINE = 0x08,
HL_UNDERCURL = 0x10,
HL_STANDOUT = 0x20,
} HlAttrFlags;
/// Stores a complete highlighting entry, including colors and attributes
/// for both TUI and GUI.
typedef struct attr_entry {
int16_t rgb_ae_attr, cterm_ae_attr; // HL_BOLD, etc.
RgbValue rgb_fg_color, rgb_bg_color, rgb_sp_color;
int cterm_fg_color, cterm_bg_color;
} HlAttrs;
#define HLATTRS_INIT (HlAttrs) { \
.rgb_ae_attr = 0, \
.cterm_ae_attr = 0, \
.rgb_fg_color = -1, \
.rgb_bg_color = -1, \
.rgb_sp_color = -1, \
.cterm_fg_color = 0, \
.cterm_bg_color = 0, \
}
/// Values for index in highlight_attr[]. /// Values for index in highlight_attr[].
/// When making changes, also update hlf_names below! /// When making changes, also update hlf_names below!
typedef enum { typedef enum {
@@ -117,7 +145,6 @@ EXTERN int highlight_attr[HLF_COUNT]; // Highl. attr for each context.
EXTERN int highlight_user[9]; // User[1-9] attributes EXTERN int highlight_user[9]; // User[1-9] attributes
EXTERN int highlight_stlnc[9]; // On top of user EXTERN int highlight_stlnc[9]; // On top of user
EXTERN int cterm_normal_fg_color INIT(= 0); EXTERN int cterm_normal_fg_color INIT(= 0);
EXTERN int cterm_normal_fg_bold INIT(= 0);
EXTERN int cterm_normal_bg_color INIT(= 0); EXTERN int cterm_normal_bg_color INIT(= 0);
EXTERN RgbValue normal_fg INIT(= -1); EXTERN RgbValue normal_fg INIT(= -1);
EXTERN RgbValue normal_bg INIT(= -1); EXTERN RgbValue normal_bg INIT(= -1);

View File

@@ -2447,7 +2447,7 @@ return {
full_name='termguicolors', abbreviation='tgc', full_name='termguicolors', abbreviation='tgc',
type='bool', scope={'global'}, type='bool', scope={'global'},
vi_def=false, vi_def=false,
redraw={'everything', 'ui_option'}, redraw={'ui_option'},
varname='p_tgc', varname='p_tgc',
defaults={if_true={vi=false}} defaults={if_true={vi=false}}
}, },

View File

@@ -139,11 +139,6 @@
* doesn't fit. */ * doesn't fit. */
#define W_ENDCOL(wp) (wp->w_wincol + wp->w_width) #define W_ENDCOL(wp) (wp->w_wincol + wp->w_width)
/*
* The attributes that are actually active for writing to the screen.
*/
static int screen_attr = 0;
static match_T search_hl; /* used for 'hlsearch' highlight matching */ static match_T search_hl; /* used for 'hlsearch' highlight matching */
static foldinfo_T win_foldinfo; /* info for 'foldcolumn' */ static foldinfo_T win_foldinfo; /* info for 'foldcolumn' */
@@ -189,8 +184,6 @@ void redraw_win_later(win_T *wp, int type)
void redraw_later_clear(void) void redraw_later_clear(void)
{ {
redraw_all_later(CLEAR); redraw_all_later(CLEAR);
/* Use attributes that is very unlikely to appear in text. */
screen_attr = HL_BOLD | HL_UNDERLINE | HL_INVERSE;
} }
/* /*
@@ -5847,30 +5840,16 @@ next_search_hl_pos(
return 0; return 0;
} }
static void screen_start_highlight(int attr)
{
screen_attr = attr;
ui_start_highlight(attr);
}
static void screen_stop_highlight(void)
{
ui_stop_highlight();
screen_attr = 0;
}
/* /*
* Put character ScreenLines["off"] on the screen at position "row" and "col", * Put character ScreenLines["off"] on the screen at position "row" and "col",
* using the attributes from ScreenAttrs["off"]. * using the attributes from ScreenAttrs["off"].
*/ */
static void screen_char(unsigned off, int row, int col) static void screen_char(unsigned off, int row, int col)
{ {
int attr; // Check for illegal values, just in case (could happen just after resizing).
if (row >= screen_Rows || col >= screen_Columns) {
/* Check for illegal values, just in case (could happen just after
* resizing). */
if (row >= screen_Rows || col >= screen_Columns)
return; return;
}
// Outputting the last character on the screen may scrollup the screen. // Outputting the last character on the screen may scrollup the screen.
// Don't to it! Mark the character invalid (update it when scrolled up) // Don't to it! Mark the character invalid (update it when scrolled up)
@@ -5882,17 +5861,8 @@ static void screen_char(unsigned off, int row, int col)
return; return;
} }
/*
* Stop highlighting first, so it's easier to move the cursor.
*/
attr = ScreenAttrs[off];
if (screen_attr != attr)
screen_stop_highlight();
ui_cursor_goto(row, col); ui_cursor_goto(row, col);
ui_set_highlight(ScreenAttrs[off]);
if (screen_attr != attr)
screen_start_highlight(attr);
if (enc_utf8 && ScreenLinesUC[off] != 0) { if (enc_utf8 && ScreenLinesUC[off] != 0) {
char_u buf[MB_MAXBYTES + 1]; char_u buf[MB_MAXBYTES + 1];
@@ -6001,7 +5971,7 @@ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1,
++off; ++off;
if (off < end_off) { /* something to be cleared */ if (off < end_off) { /* something to be cleared */
col = off - LineOffset[row]; col = off - LineOffset[row];
screen_stop_highlight(); ui_clear_highlight();
ui_cursor_goto(row, col); // clear rest of this screen line ui_cursor_goto(row, col); // clear rest of this screen line
ui_call_eol_clear(); ui_call_eol_clear();
col = end_col - col; col = end_col - col;
@@ -6383,8 +6353,7 @@ static void screenclear2(void)
return; return;
} }
screen_stop_highlight(); /* don't want highlighting here */ ui_clear_highlight(); // don't want highlighting here
/* blank out ScreenLines */ /* blank out ScreenLines */
for (i = 0; i < Rows; ++i) { for (i = 0; i < Rows; ++i) {

View File

@@ -64,7 +64,7 @@ struct hl_group {
int sg_cterm_bold; ///< bold attr was set for light color int sg_cterm_bold; ///< bold attr was set for light color
// for RGB UIs // for RGB UIs
int sg_gui; ///< "gui=" highlighting attributes int sg_gui; ///< "gui=" highlighting attributes
///< (combination of \ref HL_ATTRIBUTES) ///< (combination of \ref HlAttrFlags)
RgbValue sg_rgb_fg; ///< RGB foreground color RgbValue sg_rgb_fg; ///< RGB foreground color
RgbValue sg_rgb_bg; ///< RGB background color RgbValue sg_rgb_bg; ///< RGB background color
RgbValue sg_rgb_sp; ///< RGB special color RgbValue sg_rgb_sp; ///< RGB special color
@@ -6796,7 +6796,6 @@ void do_highlight(const char *line, const bool forceit, const bool init)
HL_TABLE()[idx].sg_cterm_fg = color + 1; HL_TABLE()[idx].sg_cterm_fg = color + 1;
if (is_normal_group) { if (is_normal_group) {
cterm_normal_fg_color = color + 1; cterm_normal_fg_color = color + 1;
cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD);
must_redraw = CLEAR; must_redraw = CLEAR;
} }
} else { } else {
@@ -6940,7 +6939,6 @@ void restore_cterm_colors(void)
normal_bg = -1; normal_bg = -1;
normal_sp = -1; normal_sp = -1;
cterm_normal_fg_color = 0; cterm_normal_fg_color = 0;
cterm_normal_fg_bold = 0;
cterm_normal_bg_color = 0; cterm_normal_bg_color = 0;
} }
@@ -6994,9 +6992,9 @@ static void highlight_clear(int idx)
/// GUI can redraw at any time for any buffer. /// GUI can redraw at any time for any buffer.
static garray_T attr_table = GA_EMPTY_INIT_VALUE; static garray_T attr_table = GA_EMPTY_INIT_VALUE;
static inline attrentry_T * ATTR_ENTRY(int idx) static inline HlAttrs * ATTR_ENTRY(int idx)
{ {
return &((attrentry_T *)attr_table.ga_data)[idx]; return &((HlAttrs *)attr_table.ga_data)[idx];
} }
@@ -7004,23 +7002,21 @@ static inline attrentry_T * ATTR_ENTRY(int idx)
/// Add a new entry to the term_attr_table, attr_table or gui_attr_table /// Add a new entry to the term_attr_table, attr_table or gui_attr_table
/// if the combination is new. /// if the combination is new.
/// @return 0 for error. /// @return 0 for error.
int get_attr_entry(attrentry_T *aep) int get_attr_entry(HlAttrs *aep)
{ {
garray_T *table = &attr_table; garray_T *table = &attr_table;
attrentry_T *taep; HlAttrs *taep;
static int recursive = FALSE; static int recursive = false;
/* /*
* Init the table, in case it wasn't done yet. * Init the table, in case it wasn't done yet.
*/ */
table->ga_itemsize = sizeof(attrentry_T); table->ga_itemsize = sizeof(HlAttrs);
ga_set_growsize(table, 7); ga_set_growsize(table, 7);
/* // Try to find an entry with the same specifications.
* Try to find an entry with the same specifications. for (int i = 0; i < table->ga_len; i++) {
*/ taep = &(((HlAttrs *)table->ga_data)[i]);
for (int i = 0; i < table->ga_len; ++i) {
taep = &(((attrentry_T *)table->ga_data)[i]);
if (aep->cterm_ae_attr == taep->cterm_ae_attr if (aep->cterm_ae_attr == taep->cterm_ae_attr
&& aep->cterm_fg_color == taep->cterm_fg_color && aep->cterm_fg_color == taep->cterm_fg_color
&& aep->cterm_bg_color == taep->cterm_bg_color && aep->cterm_bg_color == taep->cterm_bg_color
@@ -7057,7 +7053,7 @@ int get_attr_entry(attrentry_T *aep)
// This is a new combination of colors and font, add an entry. // This is a new combination of colors and font, add an entry.
taep = GA_APPEND_VIA_PTR(attrentry_T, table); taep = GA_APPEND_VIA_PTR(HlAttrs, table);
memset(taep, 0, sizeof(*taep)); memset(taep, 0, sizeof(*taep));
taep->cterm_ae_attr = aep->cterm_ae_attr; taep->cterm_ae_attr = aep->cterm_ae_attr;
taep->cterm_fg_color = aep->cterm_fg_color; taep->cterm_fg_color = aep->cterm_fg_color;
@@ -7085,9 +7081,9 @@ void clear_hl_tables(void)
// Return the resulting attributes. // Return the resulting attributes.
int hl_combine_attr(int char_attr, int prim_attr) int hl_combine_attr(int char_attr, int prim_attr)
{ {
attrentry_T *char_aep = NULL; HlAttrs *char_aep = NULL;
attrentry_T *spell_aep; HlAttrs *spell_aep;
attrentry_T new_en = ATTRENTRY_INIT; HlAttrs new_en = HLATTRS_INIT;
if (char_attr == 0) { if (char_attr == 0) {
return prim_attr; return prim_attr;
@@ -7136,7 +7132,7 @@ int hl_combine_attr(int char_attr, int prim_attr)
/// \note this function does not apply exclusively to cterm attr contrary /// \note this function does not apply exclusively to cterm attr contrary
/// to what its name implies /// to what its name implies
/// \warn don't call it with attr 0 (i.e., the null attribute) /// \warn don't call it with attr 0 (i.e., the null attribute)
attrentry_T *syn_cterm_attr2entry(int attr) HlAttrs *syn_cterm_attr2entry(int attr)
{ {
attr -= ATTR_OFF; attr -= ATTR_OFF;
if (attr >= attr_table.ga_len) { if (attr >= attr_table.ga_len) {
@@ -7385,7 +7381,7 @@ syn_list_header(int did_header, int outlen, int id)
/// @param idx corrected highlight index /// @param idx corrected highlight index
static void set_hl_attr(int idx) static void set_hl_attr(int idx)
{ {
attrentry_T at_en = ATTRENTRY_INIT; HlAttrs at_en = HLATTRS_INIT;
struct hl_group *sgp = HL_TABLE() + idx; struct hl_group *sgp = HL_TABLE() + idx;
@@ -8509,24 +8505,21 @@ RgbValue name_to_color(const char_u *name)
/// Gets highlight description for id `attr_id` as a map. /// Gets highlight description for id `attr_id` as a map.
Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err) Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err)
{ {
HlAttrs attrs = HLATTRS_INIT; HlAttrs *aep = NULL;
Dictionary dic = ARRAY_DICT_INIT; Dictionary dic = ARRAY_DICT_INIT;
if (attr_id == 0) { if (attr_id == 0) {
goto end; return dic;
} }
attrentry_T *aep = syn_cterm_attr2entry((int)attr_id); aep = syn_cterm_attr2entry((int)attr_id);
if (!aep) { if (!aep) {
api_set_error(err, kErrorTypeException, api_set_error(err, kErrorTypeException,
"Invalid attribute id: %" PRId64, attr_id); "Invalid attribute id: %" PRId64, attr_id);
return dic; return dic;
} }
attrs = attrentry2hlattrs(aep, rgb); return hlattrs2dict(aep, rgb);
end:
return hlattrs2dict(attrs);
} }

View File

@@ -6,19 +6,6 @@
#include "nvim/buffer_defs.h" #include "nvim/buffer_defs.h"
#include "nvim/ex_cmds_defs.h" #include "nvim/ex_cmds_defs.h"
/// Terminal highlighting attribute bits.
/// Attributes above HL_ALL are used for syntax highlighting.
/// \addtogroup HL_ATTRIBUTES
/// @{
#define HL_NORMAL 0x00
#define HL_INVERSE 0x01
#define HL_BOLD 0x02
#define HL_ITALIC 0x04
#define HL_UNDERLINE 0x08
#define HL_UNDERCURL 0x10
#define HL_STANDOUT 0x20
/// @}
#define HL_CONTAINED 0x01 /* not used on toplevel */ #define HL_CONTAINED 0x01 /* not used on toplevel */
#define HL_TRANSP 0x02 /* has no highlighting */ #define HL_TRANSP 0x02 /* has no highlighting */
#define HL_ONELINE 0x04 /* match within one line only */ #define HL_ONELINE 0x04 /* match within one line only */

View File

@@ -68,21 +68,4 @@ struct syn_state {
* may have made the state invalid */ * may have made the state invalid */
}; };
// Structure shared between syntax.c, screen.c
typedef struct attr_entry {
int16_t rgb_ae_attr, cterm_ae_attr; // HL_BOLD, etc.
RgbValue rgb_fg_color, rgb_bg_color, rgb_sp_color;
int cterm_fg_color, cterm_bg_color;
} attrentry_T;
#define ATTRENTRY_INIT { \
.rgb_ae_attr = 0, \
.cterm_ae_attr = 0, \
.rgb_fg_color = -1, \
.rgb_bg_color = -1, \
.rgb_sp_color = -1, \
.cterm_fg_color = 0, \
.cterm_bg_color = 0, \
}
#endif // NVIM_SYNTAX_DEFS_H #endif // NVIM_SYNTAX_DEFS_H

View File

@@ -585,7 +585,7 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr,
int attr_id = 0; int attr_id = 0;
if (hl_attrs || vt_fg != -1 || vt_bg != -1) { if (hl_attrs || vt_fg != -1 || vt_bg != -1) {
attr_id = get_attr_entry(&(attrentry_T) { attr_id = get_attr_entry(&(HlAttrs) {
.cterm_ae_attr = (int16_t)hl_attrs, .cterm_ae_attr = (int16_t)hl_attrs,
.cterm_fg_color = vt_fg_idx, .cterm_fg_color = vt_fg_idx,
.cterm_bg_color = vt_bg_idx, .cterm_bg_color = vt_bg_idx,

View File

@@ -125,7 +125,6 @@ UI *tui_start(void)
{ {
UI *ui = xcalloc(1, sizeof(UI)); // Freed by ui_bridge_stop(). UI *ui = xcalloc(1, sizeof(UI)); // Freed by ui_bridge_stop().
ui->stop = tui_stop; ui->stop = tui_stop;
ui->rgb = p_tgc;
ui->resize = tui_resize; ui->resize = tui_resize;
ui->clear = tui_clear; ui->clear = tui_clear;
ui->eol_clear = tui_eol_clear; ui->eol_clear = tui_eol_clear;
@@ -143,15 +142,12 @@ UI *tui_start(void)
ui->put = tui_put; ui->put = tui_put;
ui->bell = tui_bell; ui->bell = tui_bell;
ui->visual_bell = tui_visual_bell; ui->visual_bell = tui_visual_bell;
ui->update_fg = tui_update_fg; ui->default_colors_set = tui_default_colors_set;
ui->update_bg = tui_update_bg;
ui->update_sp = tui_update_sp;
ui->flush = tui_flush; ui->flush = tui_flush;
ui->suspend = tui_suspend; ui->suspend = tui_suspend;
ui->set_title = tui_set_title; ui->set_title = tui_set_title;
ui->set_icon = tui_set_icon; ui->set_icon = tui_set_icon;
ui->option_set= tui_option_set; ui->option_set= tui_option_set;
ui->event = tui_event;
memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
@@ -410,36 +406,58 @@ static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data)
ui_schedule_refresh(); ui_schedule_refresh();
} }
static bool attrs_differ(HlAttrs a1, HlAttrs a2) static bool attrs_differ(HlAttrs a1, HlAttrs a2, bool rgb)
{ {
return a1.foreground != a2.foreground || a1.background != a2.background if (rgb) {
|| a1.bold != a2.bold || a1.italic != a2.italic // TODO(bfredl): when we start to support special color,
|| a1.undercurl != a2.undercurl || a1.underline != a2.underline // rgb_sp_color must be added here
|| a1.reverse != a2.reverse; return a1.rgb_fg_color != a2.rgb_fg_color
|| a1.rgb_bg_color != a2.rgb_bg_color
|| a1.rgb_ae_attr != a2.rgb_ae_attr;
} else {
return a1.cterm_fg_color != a2.cterm_fg_color
|| a1.cterm_bg_color != a2.cterm_bg_color
|| a1.cterm_ae_attr != a2.cterm_ae_attr;
}
} }
static void update_attrs(UI *ui, HlAttrs attrs) static void update_attrs(UI *ui, HlAttrs attrs)
{ {
TUIData *data = ui->data; TUIData *data = ui->data;
if (!attrs_differ(attrs, data->print_attrs)) { if (!attrs_differ(attrs, data->print_attrs, ui->rgb)) {
return; return;
} }
data->print_attrs = attrs; data->print_attrs = attrs;
UGrid *grid = &data->grid; UGrid *grid = &data->grid;
int fg = attrs.foreground != -1 ? attrs.foreground : grid->fg; int fg = ui->rgb ? attrs.rgb_fg_color : (attrs.cterm_fg_color - 1);
int bg = attrs.background != -1 ? attrs.background : grid->bg; if (fg == -1) {
fg = ui->rgb ? grid->clear_attrs.rgb_fg_color
: (grid->clear_attrs.cterm_fg_color - 1);
}
int bg = ui->rgb ? attrs.rgb_bg_color : (attrs.cterm_bg_color - 1);
if (bg == -1) {
bg = ui->rgb ? grid->clear_attrs.rgb_bg_color
: (grid->clear_attrs.cterm_bg_color - 1);
}
int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr;
bool bold = attr & HL_BOLD;
bool italic = attr & HL_ITALIC;
bool reverse = attr & (HL_INVERSE | HL_STANDOUT);
bool underline = attr & (HL_UNDERLINE), undercurl = attr & (HL_UNDERCURL);
if (unibi_get_str(data->ut, unibi_set_attributes)) { if (unibi_get_str(data->ut, unibi_set_attributes)) {
if (attrs.bold || attrs.reverse || attrs.underline || attrs.undercurl) { if (bold || reverse || underline || undercurl) {
UNIBI_SET_NUM_VAR(data->params[0], 0); // standout UNIBI_SET_NUM_VAR(data->params[0], 0); // standout
UNIBI_SET_NUM_VAR(data->params[1], attrs.underline || attrs.undercurl); UNIBI_SET_NUM_VAR(data->params[1], underline || undercurl);
UNIBI_SET_NUM_VAR(data->params[2], attrs.reverse); UNIBI_SET_NUM_VAR(data->params[2], reverse);
UNIBI_SET_NUM_VAR(data->params[3], 0); // blink UNIBI_SET_NUM_VAR(data->params[3], 0); // blink
UNIBI_SET_NUM_VAR(data->params[4], 0); // dim UNIBI_SET_NUM_VAR(data->params[4], 0); // dim
UNIBI_SET_NUM_VAR(data->params[5], attrs.bold); UNIBI_SET_NUM_VAR(data->params[5], bold);
UNIBI_SET_NUM_VAR(data->params[6], 0); // blank UNIBI_SET_NUM_VAR(data->params[6], 0); // blank
UNIBI_SET_NUM_VAR(data->params[7], 0); // protect UNIBI_SET_NUM_VAR(data->params[7], 0); // protect
UNIBI_SET_NUM_VAR(data->params[8], 0); // alternate character set UNIBI_SET_NUM_VAR(data->params[8], 0); // alternate character set
@@ -451,17 +469,17 @@ static void update_attrs(UI *ui, HlAttrs attrs)
if (!data->default_attr) { if (!data->default_attr) {
unibi_out(ui, unibi_exit_attribute_mode); unibi_out(ui, unibi_exit_attribute_mode);
} }
if (attrs.bold) { if (bold) {
unibi_out(ui, unibi_enter_bold_mode); unibi_out(ui, unibi_enter_bold_mode);
} }
if (attrs.underline || attrs.undercurl) { if (underline || undercurl) {
unibi_out(ui, unibi_enter_underline_mode); unibi_out(ui, unibi_enter_underline_mode);
} }
if (attrs.reverse) { if (reverse) {
unibi_out(ui, unibi_enter_reverse_mode); unibi_out(ui, unibi_enter_reverse_mode);
} }
} }
if (attrs.italic) { if (italic) {
unibi_out(ui, unibi_enter_italics_mode); unibi_out(ui, unibi_enter_italics_mode);
} }
if (ui->rgb) { if (ui->rgb) {
@@ -491,8 +509,7 @@ static void update_attrs(UI *ui, HlAttrs attrs)
} }
data->default_attr = fg == -1 && bg == -1 data->default_attr = fg == -1 && bg == -1
&& !attrs.bold && !attrs.italic && !attrs.underline && !attrs.undercurl && !bold && !italic && !underline && !undercurl && !reverse;
&& !attrs.reverse;
} }
static void final_column_wrap(UI *ui) static void final_column_wrap(UI *ui)
@@ -534,7 +551,7 @@ static bool cheap_to_print(UI *ui, int row, int col, int next)
UCell *cell = grid->cells[row] + col; UCell *cell = grid->cells[row] + col;
while (next) { while (next) {
next--; next--;
if (attrs_differ(cell->attrs, data->print_attrs)) { if (attrs_differ(cell->attrs, data->print_attrs, ui->rgb)) {
if (data->default_attr) { if (data->default_attr) {
return false; return false;
} }
@@ -659,13 +676,12 @@ static void clear_region(UI *ui, int top, int bot, int left, int right)
int saved_col = grid->col; int saved_col = grid->col;
bool cleared = false; bool cleared = false;
if (grid->bg == -1 && right == ui->width -1) { bool nobg = ui->rgb ? grid->clear_attrs.rgb_bg_color == -1
: grid->clear_attrs.cterm_bg_color == 0;
if (nobg && right == ui->width -1) {
// Background is set to the default color and the right edge matches the // Background is set to the default color and the right edge matches the
// screen end, try to use terminal codes for clearing the requested area. // screen end, try to use terminal codes for clearing the requested area.
HlAttrs clear_attrs = HLATTRS_INIT; update_attrs(ui, grid->clear_attrs);
clear_attrs.foreground = grid->fg;
clear_attrs.background = grid->bg;
update_attrs(ui, clear_attrs);
if (left == 0) { if (left == 0) {
if (bot == ui->height - 1) { if (bot == ui->height - 1) {
if (top == 0) { if (top == 0) {
@@ -788,6 +804,7 @@ static void tui_clear(UI *ui)
TUIData *data = ui->data; TUIData *data = ui->data;
UGrid *grid = &data->grid; UGrid *grid = &data->grid;
ugrid_clear(grid); ugrid_clear(grid);
kv_size(data->invalid_regions) = 0;
clear_region(ui, grid->top, grid->bot, grid->left, grid->right); clear_region(ui, grid->top, grid->bot, grid->left, grid->right);
} }
@@ -905,7 +922,7 @@ static void tui_set_mode(UI *ui, ModeShape mode)
if (c.id != 0 && ui->rgb) { if (c.id != 0 && ui->rgb) {
int attr = syn_id2attr(c.id); int attr = syn_id2attr(c.id);
if (attr > 0) { if (attr > 0) {
attrentry_T *aep = syn_cterm_attr2entry(attr); HlAttrs *aep = syn_cterm_attr2entry(attr);
UNIBI_SET_NUM_VAR(data->params[0], aep->rgb_bg_color); UNIBI_SET_NUM_VAR(data->params[0], aep->rgb_bg_color);
unibi_out_ext(ui, data->unibi_ext.set_cursor_color); unibi_out_ext(ui, data->unibi_ext.set_cursor_color);
} }
@@ -960,10 +977,7 @@ static void tui_scroll(UI *ui, Integer count)
cursor_goto(ui, grid->top, grid->left); cursor_goto(ui, grid->top, grid->left);
// also set default color attributes or some terminals can become funny // also set default color attributes or some terminals can become funny
if (scroll_clears_to_current_colour) { if (scroll_clears_to_current_colour) {
HlAttrs clear_attrs = HLATTRS_INIT; update_attrs(ui, grid->clear_attrs);
clear_attrs.foreground = grid->fg;
clear_attrs.background = grid->bg;
update_attrs(ui, clear_attrs);
} }
if (count > 0) { if (count > 0) {
@@ -1028,19 +1042,16 @@ static void tui_visual_bell(UI *ui)
unibi_out(ui, unibi_flash_screen); unibi_out(ui, unibi_flash_screen);
} }
static void tui_update_fg(UI *ui, Integer fg) static void tui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg,
Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
{ {
((TUIData *)ui->data)->grid.fg = (int)fg; UGrid *grid = &((TUIData *)ui->data)->grid;
} grid->clear_attrs.rgb_fg_color = (int)rgb_fg;
grid->clear_attrs.rgb_bg_color = (int)rgb_bg;
static void tui_update_bg(UI *ui, Integer bg) grid->clear_attrs.rgb_sp_color = (int)rgb_sp;
{ grid->clear_attrs.cterm_fg_color = (int)cterm_fg;
((TUIData *)ui->data)->grid.bg = (int)bg; grid->clear_attrs.cterm_bg_color = (int)cterm_bg;
}
static void tui_update_sp(UI *ui, Integer sp)
{
// Do nothing; 'special' color is for GUI only
} }
static void tui_flush(UI *ui) static void tui_flush(UI *ui)
@@ -1065,6 +1076,7 @@ static void tui_flush(UI *ui)
while (kv_size(data->invalid_regions)) { while (kv_size(data->invalid_regions)) {
Rect r = kv_pop(data->invalid_regions); Rect r = kv_pop(data->invalid_regions);
assert(r.bot < grid->height && r.right < grid->width);
UGRID_FOREACH_CELL(grid, r.top, r.bot, r.left, r.right, { UGRID_FOREACH_CELL(grid, r.top, r.bot, r.left, r.right, {
cursor_goto(ui, row, col); cursor_goto(ui, row, col);
print_cell(ui, cell); print_cell(ui, cell);
@@ -1149,18 +1161,13 @@ static void tui_set_icon(UI *ui, String icon)
static void tui_option_set(UI *ui, String name, Object value) static void tui_option_set(UI *ui, String name, Object value)
{ {
TUIData *data = ui->data;
if (strequal(name.data, "termguicolors")) { if (strequal(name.data, "termguicolors")) {
// NB: value for bridge is set in ui_bridge.c
ui->rgb = value.data.boolean; ui->rgb = value.data.boolean;
invalidate(ui, 0, data->grid.height-1, 0, data->grid.width-1);
} }
} }
// NB: if we start to use this, the ui_bridge must be updated
// to make a copy for the tui thread
static void tui_event(UI *ui, char *name, Array args, bool *args_consumed)
{
}
static void invalidate(UI *ui, int top, int bot, int left, int right) static void invalidate(UI *ui, int top, int bot, int left, int right)
{ {
TUIData *data = ui->data; TUIData *data = ui->data;

View File

@@ -17,7 +17,7 @@
void ugrid_init(UGrid *grid) void ugrid_init(UGrid *grid)
{ {
grid->attrs = HLATTRS_INIT; grid->attrs = HLATTRS_INIT;
grid->fg = grid->bg = -1; grid->clear_attrs = HLATTRS_INIT;
grid->cells = NULL; grid->cells = NULL;
} }
@@ -107,6 +107,7 @@ UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size)
UCell *cell = grid->cells[grid->row] + grid->col; UCell *cell = grid->cells[grid->row] + grid->col;
cell->data[size] = 0; cell->data[size] = 0;
cell->attrs = grid->attrs; cell->attrs = grid->attrs;
assert(size <= CELLBYTES);
if (text) { if (text) {
memcpy(cell->data, text, size); memcpy(cell->data, text, size);
@@ -118,9 +119,7 @@ UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size)
static void clear_region(UGrid *grid, int top, int bot, int left, int right) static void clear_region(UGrid *grid, int top, int bot, int left, int right)
{ {
HlAttrs clear_attrs = HLATTRS_INIT; HlAttrs clear_attrs = grid->clear_attrs;
clear_attrs.foreground = grid->fg;
clear_attrs.background = grid->bg;
UGRID_FOREACH_CELL(grid, top, bot, left, right, { UGRID_FOREACH_CELL(grid, top, bot, left, right, {
cell->data[0] = ' '; cell->data[0] = ' ';
cell->data[1] = 0; cell->data[1] = 0;

View File

@@ -7,15 +7,17 @@
typedef struct ucell UCell; typedef struct ucell UCell;
typedef struct ugrid UGrid; typedef struct ugrid UGrid;
#define CELLBYTES (4 * (MAX_MCO+1))
struct ucell { struct ucell {
char data[6 * MAX_MCO + 1]; char data[CELLBYTES + 1];
HlAttrs attrs; HlAttrs attrs;
}; };
struct ugrid { struct ugrid {
int top, bot, left, right; int top, bot, left, right;
int row, col; int row, col;
int bg, fg; HlAttrs clear_attrs;
int width, height; int width, height;
HlAttrs attrs; HlAttrs attrs;
UCell **cells; UCell **cells;

View File

@@ -55,7 +55,7 @@ static int row = 0, col = 0;
static struct { static struct {
int top, bot, left, right; int top, bot, left, right;
} sr; } sr;
static int current_attr_code = 0; static int current_attr_code = -1;
static bool pending_cursor_update = false; static bool pending_cursor_update = false;
static int busy = 0; static int busy = 0;
static int height, width; static int height, width;
@@ -107,8 +107,9 @@ static char uilog_last_event[1024] = { 0 };
} \ } \
} while (0) } while (0)
#endif #endif
#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore) #define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, \
#define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6 MORE, MORE, ZERO, ignore)
#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, ...) a7
#define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__) #define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__)
// Resolves to UI_CALL_MORE or UI_CALL_ZERO. // Resolves to UI_CALL_MORE or UI_CALL_ZERO.
#define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__) #define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__)
@@ -151,6 +152,9 @@ bool ui_is_stopped(UI *ui)
bool ui_rgb_attached(void) bool ui_rgb_attached(void)
{ {
if (!headless_mode && p_tgc) {
return true;
}
for (size_t i = 0; i < ui_count; i++) { for (size_t i = 0; i < ui_count; i++) {
if (uis[i]->rgb) { if (uis[i]->rgb) {
return true; return true;
@@ -174,84 +178,57 @@ void ui_event(char *name, Array args)
} }
/// Converts an attrentry_T into an HlAttrs /// Converts an HlAttrs into Dictionary
/// ///
/// @param[in] aep data to convert /// @param[in] aep data to convert
/// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*' /// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*'
HlAttrs attrentry2hlattrs(const attrentry_T *aep, bool use_rgb) Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb)
{ {
assert(aep); assert(aep);
HlAttrs attrs = HLATTRS_INIT;
int mask = 0;
mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr;
attrs.bold = mask & HL_BOLD;
attrs.underline = mask & HL_UNDERLINE;
attrs.undercurl = mask & HL_UNDERCURL;
attrs.italic = mask & HL_ITALIC;
attrs.reverse = mask & (HL_INVERSE | HL_STANDOUT);
if (use_rgb) {
if (aep->rgb_fg_color != -1) {
attrs.foreground = aep->rgb_fg_color;
}
if (aep->rgb_bg_color != -1) {
attrs.background = aep->rgb_bg_color;
}
if (aep->rgb_sp_color != -1) {
attrs.special = aep->rgb_sp_color;
}
} else {
if (cterm_normal_fg_color != aep->cterm_fg_color) {
attrs.foreground = aep->cterm_fg_color - 1;
}
if (cterm_normal_bg_color != aep->cterm_bg_color) {
attrs.background = aep->cterm_bg_color - 1;
}
}
return attrs;
}
Dictionary hlattrs2dict(HlAttrs attrs)
{
Dictionary hl = ARRAY_DICT_INIT; Dictionary hl = ARRAY_DICT_INIT;
int mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr;
if (attrs.bold) { if (mask & HL_BOLD) {
PUT(hl, "bold", BOOLEAN_OBJ(true)); PUT(hl, "bold", BOOLEAN_OBJ(true));
} }
if (attrs.underline) { if (mask & HL_UNDERLINE) {
PUT(hl, "underline", BOOLEAN_OBJ(true)); PUT(hl, "underline", BOOLEAN_OBJ(true));
} }
if (attrs.undercurl) { if (mask & HL_UNDERCURL) {
PUT(hl, "undercurl", BOOLEAN_OBJ(true)); PUT(hl, "undercurl", BOOLEAN_OBJ(true));
} }
if (attrs.italic) { if (mask & HL_ITALIC) {
PUT(hl, "italic", BOOLEAN_OBJ(true)); PUT(hl, "italic", BOOLEAN_OBJ(true));
} }
if (attrs.reverse) { if (mask & (HL_INVERSE | HL_STANDOUT)) {
PUT(hl, "reverse", BOOLEAN_OBJ(true)); PUT(hl, "reverse", BOOLEAN_OBJ(true));
} }
if (attrs.foreground != -1) {
PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground));
}
if (attrs.background != -1) { if (use_rgb) {
PUT(hl, "background", INTEGER_OBJ(attrs.background)); if (aep->rgb_fg_color != -1) {
} PUT(hl, "foreground", INTEGER_OBJ(aep->rgb_fg_color));
}
if (attrs.special != -1) { if (aep->rgb_bg_color != -1) {
PUT(hl, "special", INTEGER_OBJ(attrs.special)); PUT(hl, "background", INTEGER_OBJ(aep->rgb_bg_color));
}
if (aep->rgb_sp_color != -1) {
PUT(hl, "special", INTEGER_OBJ(aep->rgb_sp_color));
}
} else {
if (cterm_normal_fg_color != aep->cterm_fg_color) {
PUT(hl, "foreground", INTEGER_OBJ(aep->cterm_fg_color - 1));
}
if (cterm_normal_bg_color != aep->cterm_bg_color) {
PUT(hl, "background", INTEGER_OBJ(aep->cterm_bg_color - 1));
}
} }
return hl; return hl;
@@ -296,6 +273,7 @@ void ui_refresh(void)
ui_mode_info_set(); ui_mode_info_set();
old_mode_idx = -1; old_mode_idx = -1;
ui_cursor_shape(); ui_cursor_shape();
current_attr_code = -1;
} }
static void ui_refresh_event(void **argv) static void ui_refresh_event(void **argv)
@@ -313,6 +291,11 @@ void ui_resize(int new_width, int new_height)
width = new_width; width = new_width;
height = new_height; height = new_height;
// TODO(bfredl): update default colors when they changed, NOT on resize.
ui_call_default_colors_set(normal_fg, normal_bg, normal_sp,
cterm_normal_fg_color, cterm_normal_bg_color);
// Deprecated:
UI_CALL(update_fg, (ui->rgb ? normal_fg : cterm_normal_fg_color - 1)); UI_CALL(update_fg, (ui->rgb ? normal_fg : cterm_normal_fg_color - 1));
UI_CALL(update_bg, (ui->rgb ? normal_bg : cterm_normal_bg_color - 1)); UI_CALL(update_bg, (ui->rgb ? normal_bg : cterm_normal_bg_color - 1));
UI_CALL(update_sp, (ui->rgb ? normal_sp : -1)); UI_CALL(update_sp, (ui->rgb ? normal_sp : -1));
@@ -406,26 +389,28 @@ void ui_reset_scroll_region(void)
ui_call_set_scroll_region(sr.top, sr.bot, sr.left, sr.right); ui_call_set_scroll_region(sr.top, sr.bot, sr.left, sr.right);
} }
void ui_start_highlight(int attr_code) void ui_set_highlight(int attr_code)
{ {
if (current_attr_code == attr_code) {
return;
}
current_attr_code = attr_code; current_attr_code = attr_code;
if (!ui_active()) { HlAttrs attrs = HLATTRS_INIT;
return;
if (attr_code != 0) {
HlAttrs *aep = syn_cterm_attr2entry(attr_code);
if (aep) {
attrs = *aep;
}
} }
set_highlight_args(current_attr_code); UI_CALL(highlight_set, attrs);
} }
void ui_stop_highlight(void) void ui_clear_highlight(void)
{ {
current_attr_code = HL_NORMAL; ui_set_highlight(0);
if (!ui_active()) {
return;
}
set_highlight_args(current_attr_code);
} }
void ui_puts(uint8_t *str) void ui_puts(uint8_t *str)
@@ -503,26 +488,6 @@ void ui_flush(void)
ui_call_flush(); ui_call_flush();
} }
static void set_highlight_args(int attr_code)
{
HlAttrs rgb_attrs = HLATTRS_INIT;
HlAttrs cterm_attrs = rgb_attrs;
if (attr_code == HL_NORMAL) {
goto end;
}
attrentry_T *aep = syn_cterm_attr2entry(attr_code);
if (!aep) {
goto end;
}
rgb_attrs = attrentry2hlattrs(aep, true);
cterm_attrs = attrentry2hlattrs(aep, false);
end:
UI_CALL(highlight_set, (ui->rgb ? rgb_attrs : cterm_attrs));
}
void ui_linefeed(void) void ui_linefeed(void)
{ {

View File

@@ -16,14 +16,6 @@ typedef enum {
} UIWidget; } UIWidget;
#define UI_WIDGETS (kUIWildmenu + 1) #define UI_WIDGETS (kUIWildmenu + 1)
typedef struct {
bool bold, underline, undercurl, italic, reverse;
int foreground, background, special;
} HlAttrs;
#define HLATTRS_INIT \
((HlAttrs){ false, false, false, false, false, -1, -1, -1 })
typedef struct ui_t UI; typedef struct ui_t UI;
struct ui_t { struct ui_t {

View File

@@ -59,9 +59,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
rv->bridge.put = ui_bridge_put; rv->bridge.put = ui_bridge_put;
rv->bridge.bell = ui_bridge_bell; rv->bridge.bell = ui_bridge_bell;
rv->bridge.visual_bell = ui_bridge_visual_bell; rv->bridge.visual_bell = ui_bridge_visual_bell;
rv->bridge.update_fg = ui_bridge_update_fg; rv->bridge.default_colors_set = ui_bridge_default_colors_set;
rv->bridge.update_bg = ui_bridge_update_bg;
rv->bridge.update_sp = ui_bridge_update_sp;
rv->bridge.flush = ui_bridge_flush; rv->bridge.flush = ui_bridge_flush;
rv->bridge.suspend = ui_bridge_suspend; rv->bridge.suspend = ui_bridge_suspend;
rv->bridge.set_title = ui_bridge_set_title; rv->bridge.set_title = ui_bridge_set_title;
@@ -146,29 +144,6 @@ static void ui_bridge_highlight_set_event(void **argv)
xfree(argv[1]); xfree(argv[1]);
} }
static void ui_bridge_option_set(UI *ui, String name, Object value)
{
// Assumes bridge is only used by TUI
if (strequal(name.data, "termguicolors")) {
ui->rgb = value.data.boolean;
}
String copy_name = copy_string(name);
Object *copy_value = xmalloc(sizeof(Object));
*copy_value = copy_object(value);
UI_BRIDGE_CALL(ui, option_set, 4, ui,
copy_name.data, INT2PTR(copy_name.size), copy_value);
}
static void ui_bridge_option_set_event(void **argv)
{
UI *ui = UI(argv[0]);
String name = (String){ .data = argv[1], .size = (size_t)argv[2] };
Object value = *(Object *)argv[3];
ui->option_set(ui, name, value);
api_free_string(name);
api_free_object(value);
xfree(argv[3]);
}
static void ui_bridge_suspend(UI *b) static void ui_bridge_suspend(UI *b)
{ {
UIBridgeData *data = (UIBridgeData *)b; UIBridgeData *data = (UIBridgeData *)b;

View File

@@ -242,7 +242,7 @@ describe('tui', function()
{9:~ }| {9:~ }|
{9:~ }| {9:~ }|
{3:[No Name] [+] }| {3:[No Name] [+] }|
| :set termguicolors |
{4:-- TERMINAL --} | {4:-- TERMINAL --} |
]]) ]])
@@ -253,7 +253,7 @@ describe('tui', function()
{2:~ }| {2:~ }|
{2:~ }| {2:~ }|
{3:[No Name] [+] }| {3:[No Name] [+] }|
| :set notermguicolors |
{4:-- TERMINAL --} | {4:-- TERMINAL --} |
]]) ]])
end) end)

View File

@@ -455,6 +455,9 @@ function Screen:_handle_visual_bell()
self.visual_bell = true self.visual_bell = true
end end
function Screen:_handle_default_colors_set()
end
function Screen:_handle_update_fg(fg) function Screen:_handle_update_fg(fg)
self._fg = fg self._fg = fg
end end