commit fb0dc825e9bbb9bed0d98b8c7f6ac1d117408c90
parent 7526fb449de39afb786a1a68365230996551b5c5
Author: glepnir <glephunter@gmail.com>
Date: Thu, 10 Jul 2025 09:15:08 +0800
feat(option): custom chars in 'winborder' #33772
Problem: winborder option only supported predefined styles and lacked support for custom border characters.
Solution: implement parsing for comma-separated list format that allows specifying 8 individual border characters (topleft, top, topright, right, botright, bottom, botleft, left).
Diffstat:
8 files changed, 100 insertions(+), 12 deletions(-)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -229,7 +229,7 @@ OPTIONS
• 'diffopt' `inline:` configures diff highlighting for changes within a line.
• 'grepformat' is now a |global-local| option.
• 'pummaxwidth' sets maximum width for the completion popup menu.
-• 'winborder' "bold" style.
+• 'winborder' "bold" style, custom border style.
• |g:clipboard| accepts a string name to force any builtin clipboard tool.
• 'busy' sets a buffer "busy" status. Indicated in the default statusline.
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
@@ -7410,6 +7410,10 @@ A jump table for the options with a short description can be found at |Q_op|.
- "shadow": Drop shadow effect, by blending with the background.
- "single": Single-line box.
- "solid": Adds padding by a single whitespace cell.
+ - custom: comma-separated list of exactly 8 characters in clockwise
+ order starting from topleft. Example: >lua
+ vim.o.winborder='+,-,+,|,+,-,+,|'
+<
*'window'* *'wi'*
'window' 'wi' number (default screen height - 1)
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
@@ -8117,8 +8117,15 @@ vim.wo.winbl = vim.wo.winblend
--- - "shadow": Drop shadow effect, by blending with the background.
--- - "single": Single-line box.
--- - "solid": Adds padding by a single whitespace cell.
+--- - custom: comma-separated list of exactly 8 characters in clockwise
+--- order starting from topleft. Example:
---
---- @type ''|'double'|'single'|'shadow'|'rounded'|'solid'|'bold'|'none'
+--- ```lua
+--- vim.o.winborder='+,-,+,`,+,-,+,`'
+--- ```
+---
+---
+--- @type string
vim.o.winborder = ""
vim.go.winborder = vim.o.winborder
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
@@ -1064,6 +1064,52 @@ static void generate_api_error(win_T *wp, const char *attribute, Error *err)
}
}
+/// Parses a border style name or custom (comma-separated) style.
+bool parse_winborder(WinConfig *fconfig, Error *err)
+{
+ if (!fconfig) {
+ return false;
+ }
+ Object style = OBJECT_INIT;
+
+ if (strchr(p_winborder, ',')) {
+ Array border_chars = ARRAY_DICT_INIT;
+ char *p = p_winborder;
+ char part[MAX_SCHAR_SIZE] = { 0 };
+ int count = 0;
+
+ while (*p != NUL) {
+ if (count >= 8) {
+ api_free_array(border_chars);
+ return false;
+ }
+
+ size_t part_len = copy_option_part(&p, part, sizeof(part), ",");
+ if (part_len == 0 || part[0] == NUL) {
+ api_free_array(border_chars);
+ return false;
+ }
+
+ String str = cstr_to_string(part);
+ ADD(border_chars, STRING_OBJ(str));
+ count++;
+ }
+
+ if (count != 8) {
+ api_free_array(border_chars);
+ return false;
+ }
+
+ style = ARRAY_OBJ(border_chars);
+ } else {
+ style = CSTR_TO_OBJ(p_winborder);
+ }
+
+ parse_border_style(style, fconfig, err);
+ api_free_object(style);
+ return !ERROR_SET(err);
+}
+
static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fconfig, bool reconf,
Error *err)
{
@@ -1297,14 +1343,15 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
goto fail;
}
border_style = config->border;
- } else if (*p_winborder != NUL && (wp == NULL || !wp->w_floating)) {
- border_style = CSTR_AS_OBJ(p_winborder);
- }
- if (border_style.type != kObjectTypeNil) {
- parse_border_style(border_style, fconfig, err);
- if (ERROR_SET(err)) {
- goto fail;
+ if (border_style.type != kObjectTypeNil) {
+ parse_border_style(border_style, fconfig, err);
+ if (ERROR_SET(err)) {
+ goto fail;
+ }
}
+ } else if (*p_winborder != NUL && (wp == NULL || !wp->w_floating)
+ && !parse_winborder(fconfig, err)) {
+ goto fail;
}
if (HAS_KEY_X(config, style)) {
diff --git a/src/nvim/api/win_config.h b/src/nvim/api/win_config.h
@@ -4,6 +4,7 @@
#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/win_config.h.generated.h"
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
@@ -10431,6 +10431,9 @@ local options = {
type = 'number',
},
{
+ full_name = 'winborder',
+ scope = { 'global' },
+ cb = 'did_set_winborder',
defaults = { if_true = '' },
values = { '', 'double', 'single', 'shadow', 'rounded', 'solid', 'bold', 'none' },
desc = [=[
@@ -10443,11 +10446,14 @@ local options = {
- "shadow": Drop shadow effect, by blending with the background.
- "single": Single-line box.
- "solid": Adds padding by a single whitespace cell.
+ - custom: comma-separated list of exactly 8 characters in clockwise
+ order starting from topleft. Example: >lua
+ vim.o.winborder='+,-,+,|,+,-,+,|'
+ <
]=],
- full_name = 'winborder',
- scope = { 'global' },
short_desc = N_('border of floating window'),
type = 'string',
+ list = 'onecomma',
varname = 'p_winborder',
},
{
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
@@ -4,6 +4,7 @@
#include <string.h>
#include "nvim/api/private/defs.h"
+#include "nvim/api/win_config.h"
#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
@@ -2101,6 +2102,19 @@ const char *did_set_winbar(optset_T *args)
return did_set_statustabline_rulerformat(args, false, false);
}
+/// The 'winborder' option is changed.
+const char *did_set_winborder(optset_T *args)
+{
+ WinConfig fconfig = WIN_CONFIG_INIT;
+ Error err = ERROR_INIT;
+ if (!parse_winborder(&fconfig, &err)) {
+ api_clear_error(&err);
+ return e_invarg;
+ }
+ api_clear_error(&err);
+ return NULL;
+}
+
/// The 'winhighlight' option is changed.
const char *did_set_winhighlight(optset_T *args)
{
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
@@ -10938,7 +10938,16 @@ describe('float window', function()
winid = api.nvim_open_win(buf, false, config)
eq('┏', api.nvim_win_get_config(winid).border[1])
- -- it is currently not supported.
+ command([[set winborder=+,-,+,\|,+,-,+,\|]])
+ winid = api.nvim_open_win(buf, false, config)
+ eq('+', api.nvim_win_get_config(winid).border[1])
+
+ command([[set winborder=●,○,●,○,●,○,●,○]])
+ winid = api.nvim_open_win(buf, false, config)
+ eq('●', api.nvim_win_get_config(winid).border[1])
+
+ eq('Vim(set):E474: Invalid argument: winborder=,,', pcall_err(command, 'set winborder=,,'))
+ eq('Vim(set):E474: Invalid argument: winborder=+,-,+,|,+,-,+,', pcall_err(command, [[set winborder=+,-,+,\|,+,-,+,]]))
eq('Vim(set):E474: Invalid argument: winborder=custom', pcall_err(command, 'set winborder=custom'))
end)
end