aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/api/private/helpers.c152
-rw-r--r--src/nvim/buffer_defs.h17
-rw-r--r--src/nvim/screen.c51
-rw-r--r--src/nvim/window.c5
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