neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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:
Mruntime/doc/news.txt | 2+-
Mruntime/doc/options.txt | 4++++
Mruntime/lua/vim/_meta/options.lua | 9++++++++-
Msrc/nvim/api/win_config.c | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/nvim/api/win_config.h | 1+
Msrc/nvim/options.lua | 10++++++++--
Msrc/nvim/optionstr.c | 14++++++++++++++
Mtest/functional/ui/float_spec.lua | 11++++++++++-
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