diff options
author | Josh Rahm <rahm@google.com> | 2021-09-23 18:02:25 -0600 |
---|---|---|
committer | Josh Rahm <rahm@google.com> | 2021-10-05 02:20:00 -0600 |
commit | 264c6c463811f45c2a66e0ac948c49657c57b3ac (patch) | |
tree | a5ce8f15f9daaf6256014cbca148ce821a03bcbe | |
parent | aba397991b59dbadad21b9ab7ad9f7a3a21f6259 (diff) | |
download | rneovim-264c6c463811f45c2a66e0ac948c49657c57b3ac.tar.gz rneovim-264c6c463811f45c2a66e0ac948c49657c57b3ac.tar.bz2 rneovim-264c6c463811f45c2a66e0ac948c49657c57b3ac.zip |
Add the ability to title floating windows.
The window title is set using the {title} key on the FloatConfig.
The window title allows for 3 different positions as defined
by the {title_position} key in the FloatConfig:
- left
- center
- right
The title also supports StatusLine-style highlighting using
the %#<HL># keys.
-rw-r--r-- | src/nvim/api/private/helpers.c | 152 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 17 | ||||
-rw-r--r-- | src/nvim/screen.c | 51 | ||||
-rw-r--r-- | src/nvim/window.c | 5 |
4 files changed, 197 insertions, 28 deletions
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 541793e528..7f3231cff0 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1877,6 +1877,64 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) } } +static bool parse_title(FloatConfig* out, String s) +{ + // The raw title is going to be at most the length of the string. + char_u* out_title_raw = xcalloc(sizeof(char_u), s.size + 1); + size_t out_cursor = 0; + + char_u* data = (char_u*) s.data; + + size_t out_hlrec_nalloc = 4; + stl_hlrec_t* out_hlrec = xcalloc(sizeof(stl_hlrec_t), out_hlrec_nalloc); + out_hlrec[0].start = out_title_raw; + out_hlrec[0].userhl = 0; + size_t out_hl_cur = 1; + + char_u hlbuf[128]; + size_t hlbuf_cur = 0; + + int hl; + + for (size_t i = 0; i < s.size; i ++) { + if (data[i] == '\\' && i < s.size - 1) { + i ++; + out_title_raw[out_cursor++] = data[i]; + } else if ( + data[i] == '%' && + i < s.size - 1 && data[i + 1] == '#') { + i += 2; + while (i < s.size && data[i] != '#') { + if (hlbuf_cur < sizeof(hlbuf) - 1) { + hlbuf[hlbuf_cur ++] = data[i]; + } + i ++; + } + hlbuf[hlbuf_cur++] = 0; + hl = syn_check_group(hlbuf, (int) strlen((char*)hlbuf)); + hlbuf_cur = 0; + + if (out_hl_cur >= out_hlrec_nalloc - 1) { // Leave room for last. + out_hlrec = + xrealloc(out_hlrec, sizeof(stl_hlrec_t) * (out_hlrec_nalloc *= 2)); + } + + out_hlrec[out_hl_cur].start = out_title_raw + out_cursor; + out_hlrec[out_hl_cur++].userhl = -hl; + } else { + out_title_raw[out_cursor++] = data[i]; + } + } + + out->n_title = out_cursor; + out_title_raw[out_cursor++] = 0; + out_hlrec[out_hl_cur] = (stl_hlrec_t) { 0, 0 }; + out->title_hl = out_hlrec; + out->title = out_title_raw; + + return true; +} + bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bool new_win, Error *err) { @@ -1887,6 +1945,12 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bo bool has_width = false, has_height = false; bool has_bufpos = false; + xfree(fconfig->title); + xfree(fconfig->title_hl); + fconfig->title_hl = NULL; + fconfig->n_title = 0; + fconfig->title = NULL; + for (size_t i = 0; i < config.size; i++) { char *key = config.items[i].key.data; Object val = config.items[i].value; @@ -1899,7 +1963,7 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bo } else { api_set_error(err, kErrorTypeValidation, "'row' key must be Integer or Float"); - return false; + goto free_and_fail; } } else if (!strcmp(key, "col")) { has_col = true; @@ -1910,7 +1974,7 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bo } else { api_set_error(err, kErrorTypeValidation, "'col' key must be Integer or Float"); - return false; + goto free_and_fail; } } else if (strequal(key, "width")) { has_width = true; @@ -1919,7 +1983,7 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bo } else { api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer"); - return false; + goto free_and_fail; } } else if (strequal(key, "height")) { has_height = true; @@ -1928,24 +1992,24 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bo } else { api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer"); - return false; + goto free_and_fail; } } else if (!strcmp(key, "anchor")) { if (val.type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, "'anchor' key must be String"); - return false; + goto free_and_fail; } if (!parse_float_anchor(val.data.string, &fconfig->anchor)) { api_set_error(err, kErrorTypeValidation, "Invalid value of 'anchor' key"); - return false; + goto free_and_fail; } } else if (!strcmp(key, "relative")) { if (val.type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, "'relative' key must be String"); - return false; + goto free_and_fail; } // ignore empty string, to match nvim_win_get_config if (val.data.string.size > 0) { @@ -1953,41 +2017,72 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bo if (!parse_float_relative(val.data.string, &fconfig->relative)) { api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key"); - return false; + goto free_and_fail; } } + } else if (!strcmp(key, "title")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'title' key."); + goto free_and_fail; + } + + xfree(fconfig->title); + xfree(fconfig->title_hl); + + if (!parse_title(fconfig, val.data.string)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for title"); + } + } else if (!strcmp(key, "title_position")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'title_position' key"); + goto free_and_fail; + } + + if (striequal(val.data.string.data, "left")) { + fconfig->title_pos = kTitleLeft; + } else if (striequal(val.data.string.data, "center")) { + fconfig->title_pos = kTitleCenter; + } else if (striequal(val.data.string.data, "right")) { + fconfig->title_pos = kTitleRight; + } else { + api_set_error(err, kErrorTypeValidation, + "Invalid value for 'title_position'"); + goto free_and_fail; + } } else if (!strcmp(key, "win")) { has_window = true; if (val.type != kObjectTypeInteger && val.type != kObjectTypeWindow) { api_set_error(err, kErrorTypeValidation, "'win' key must be Integer or Window"); - return false; + goto free_and_fail; } fconfig->window = (Window)val.data.integer; } else if (!strcmp(key, "bufpos")) { if (val.type != kObjectTypeArray) { api_set_error(err, kErrorTypeValidation, "'bufpos' key must be Array"); - return false; + goto free_and_fail; } if (!parse_float_bufpos(val.data.array, &fconfig->bufpos)) { api_set_error(err, kErrorTypeValidation, "Invalid value of 'bufpos' key"); - return false; + goto free_and_fail; } has_bufpos = true; } else if (!strcmp(key, "external")) { has_external = fconfig->external = api_object_to_bool(val, "'external' key", false, err); if (ERROR_SET(err)) { - return false; + goto free_and_fail; } } else if (!strcmp(key, "focusable")) { fconfig->focusable = api_object_to_bool(val, "'focusable' key", true, err); if (ERROR_SET(err)) { - return false; + goto free_and_fail; } } else if (strequal(key, "zindex")) { if (val.type == kObjectTypeInteger && val.data.integer > 0) { @@ -1995,37 +2090,37 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bo } else { api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer"); - return false; + goto free_and_fail; } } else if (!strcmp(key, "border")) { parse_border_style(val, fconfig, err); if (ERROR_SET(err)) { - return false; + goto free_and_fail; } } else if (!strcmp(key, "style")) { if (val.type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, "'style' key must be String"); - return false; + goto free_and_fail; } if (val.data.string.data[0] == NUL) { fconfig->style = kWinStyleUnused; } else if (striequal(val.data.string.data, "minimal")) { fconfig->style = kWinStyleMinimal; } else { - api_set_error(err, kErrorTypeValidation, + api_set_error(err, kErrorTypeValidation, "Invalid value of 'style' key"); } } else if (strequal(key, "noautocmd") && new_win) { fconfig->noautocmd = api_object_to_bool(val, "'noautocmd' key", false, err); if (ERROR_SET(err)) { - return false; + goto free_and_fail; } } else { api_set_error(err, kErrorTypeValidation, "Invalid key '%s'", key); - return false; + goto free_and_fail; } } @@ -2033,7 +2128,7 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bo && fconfig->relative == kFloatRelativeWindow)) { api_set_error(err, kErrorTypeValidation, "'win' key is only valid with relative='win'"); - return false; + goto free_and_fail; } if ((has_relative && fconfig->relative == kFloatRelativeWindow) @@ -2059,11 +2154,11 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bo if (has_relative && has_external) { api_set_error(err, kErrorTypeValidation, "Only one of 'relative' and 'external' must be used"); - return false; + goto free_and_fail; } else if (!reconf && !has_relative && !has_external) { api_set_error(err, kErrorTypeValidation, "One of 'relative' and 'external' must be used"); - return false; + goto free_and_fail; } else if (has_relative) { fconfig->external = false; } @@ -2071,19 +2166,26 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bo if (!reconf && !(has_height && has_width)) { api_set_error(err, kErrorTypeValidation, "Must specify 'width' and 'height'"); - return false; + goto free_and_fail; } if (fconfig->external && !ui_has(kUIMultigrid)) { api_set_error(err, kErrorTypeValidation, "UI doesn't support external windows"); - return false; + goto free_and_fail; } if (has_relative != has_row || has_row != has_col) { api_set_error(err, kErrorTypeValidation, "'relative' requires 'row'/'col' or 'bufpos'"); - return false; + goto free_and_fail; } return true; +free_and_fail: + xfree(fconfig->title); + xfree(fconfig->title_hl); + fconfig->n_title = 0; + fconfig->title_hl = NULL; + fconfig->title = NULL; + return false; } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index ba2bcd7223..5451f908cd 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1080,6 +1080,12 @@ typedef enum { kWinStyleMinimal, /// Minimal UI: no number column, eob markers, etc } WinStyle; +typedef enum { + kTitleLeft = 0, + kTitleCenter, + kTitleRight +} TitlePosition; + typedef struct { Window window; lpos_T bufpos; @@ -1097,6 +1103,11 @@ typedef struct { int border_hl_ids[8]; int border_attr[8]; bool noautocmd; + + stl_hlrec_t* title_hl; + char_u* title; + size_t n_title; + TitlePosition title_pos; } FloatConfig; #define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \ @@ -1106,7 +1117,11 @@ typedef struct { .focusable = true, \ .zindex = kZIndexFloatDefault, \ .style = kWinStyleUnused, \ - .noautocmd = false }) + .noautocmd = false, \ + .title_hl = NULL, \ + .title = NULL, \ + .n_title = 0, \ + .title_pos = kTitleLeft}) // Structure to store last cursor position and topline. Used by check_lnums() // and reset_lnums(). diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 79e960fe8b..0a75e2d09f 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -5567,17 +5567,64 @@ static void win_redr_border(win_T *wp) schar_T *chars = wp->w_float_config.border_chars; int *attrs = wp->w_float_config.border_attr; - int *adj = wp->w_border_adj; int irow = wp->w_height_inner, icol = wp->w_width_inner; + char_u* title = wp->w_float_config.title; + size_t n_title = wp->w_float_config.n_title; + stl_hlrec_t* title_hl = wp->w_float_config.title_hl; + + int m8[MAX_MCO + 1]; + int cc; + int len; + int t_attr = title_hl != NULL && title_hl->userhl + ? syn_id2attr(title_hl->userhl) + : 0; + t_attr = hl_combine_attr(attrs[1], t_attr); + + int title_pos = 2; + switch (wp->w_float_config.title_pos) { + case kTitleLeft: + title_pos = 2; + break; + case kTitleRight: + title_pos = icol - 2 - vim_strsize(title); + break; + case kTitleCenter: + title_pos = (icol - vim_strsize(title)) / 2 - 1; + break; + } + title_pos = title_pos < 2 ? 2 : title_pos; + if (adj[0]) { grid_puts_line_start(grid, 0); if (adj[3]) { grid_put_schar(grid, 0, 0, chars[0], attrs[0]); } for (int i = 0; i < icol; i++) { - grid_put_schar(grid, 0, i+adj[3], chars[1], attrs[1]); + schar_T ch; + int attr; + // Draw the title if in the correct position. + if (i > title_pos && n_title > 0 && i < icol - 2) { + cc = utfc_ptr2char(title, m8); + len = utfc_ptr2len(title); + n_title -= len; + title += len; + + while (title_hl != NULL && + (title_hl + 1)->start != NULL && + (title_hl + 1)->start < title) { + ++ title_hl; + t_attr = hl_combine_attr(attrs[1], syn_id2attr(-title_hl->userhl)); + } + + schar_from_cc(ch, cc, m8); + attr = t_attr; + } else { + memcpy(ch, chars[1], sizeof(schar_T)); + attr = attrs[1]; + } + grid_put_schar(grid, 0, i+adj[3], ch, attr); } if (adj[1]) { grid_put_schar(grid, 0, icol+adj[3], chars[2], attrs[2]); diff --git a/src/nvim/window.c b/src/nvim/window.c index 400962f993..a0ddd580b2 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2785,6 +2785,11 @@ static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp) wp = firstwin; } } + xfree(win->w_float_config.title); + xfree(win->w_float_config.title_hl); + win->w_float_config.title_hl = NULL; + win->w_float_config.title = NULL; + win->w_float_config.n_title = 0; win_free(win, tp); // When deleting the current window of another tab page select a new |