win_config.c (48531B)
1 #include <assert.h> 2 #include <stdbool.h> 3 #include <string.h> 4 5 #include "klib/kvec.h" 6 #include "nvim/api/extmark.h" 7 #include "nvim/api/keysets_defs.h" 8 #include "nvim/api/private/defs.h" 9 #include "nvim/api/private/dispatch.h" 10 #include "nvim/api/private/helpers.h" 11 #include "nvim/api/win_config.h" 12 #include "nvim/ascii_defs.h" 13 #include "nvim/autocmd.h" 14 #include "nvim/autocmd_defs.h" 15 #include "nvim/buffer.h" 16 #include "nvim/buffer_defs.h" 17 #include "nvim/decoration_defs.h" 18 #include "nvim/drawscreen.h" 19 #include "nvim/errors.h" 20 #include "nvim/eval/window.h" 21 #include "nvim/globals.h" 22 #include "nvim/highlight_group.h" 23 #include "nvim/macros_defs.h" 24 #include "nvim/mbyte.h" 25 #include "nvim/memory.h" 26 #include "nvim/memory_defs.h" 27 #include "nvim/option.h" 28 #include "nvim/option_vars.h" 29 #include "nvim/pos_defs.h" 30 #include "nvim/strings.h" 31 #include "nvim/syntax.h" 32 #include "nvim/types_defs.h" 33 #include "nvim/ui.h" 34 #include "nvim/ui_defs.h" 35 #include "nvim/vim_defs.h" 36 #include "nvim/window.h" 37 #include "nvim/winfloat.h" 38 39 #include "api/win_config.c.generated.h" 40 41 /// Opens a new split window, or a floating window if `relative` is specified, 42 /// or an external window (managed by the UI) if `external` is specified. 43 /// 44 /// Floats are windows that are drawn above the split layout, at some anchor 45 /// position in some other window. Floats can be drawn internally or by external 46 /// GUI with the |ui-multigrid| extension. External windows are only supported 47 /// with multigrid GUIs, and are displayed as separate top-level windows. 48 /// 49 /// For a general overview of floats, see |api-floatwin|. 50 /// 51 /// The `width` and `height` of the new window must be specified when opening 52 /// a floating window, but are optional for normal windows. 53 /// 54 /// If `relative` and `external` are omitted, a normal "split" window is created. 55 /// The `win` property determines which window will be split. If no `win` is 56 /// provided or `win == 0`, a window will be created adjacent to the current window. 57 /// If -1 is provided, a top-level split will be created. `vertical` and `split` are 58 /// only valid for normal windows, and are used to control split direction. For `vertical`, 59 /// the exact direction is determined by 'splitright' and 'splitbelow'. 60 /// Split windows cannot have `bufpos`, `row`, `col`, `border`, `title`, `footer` properties. 61 /// 62 /// With relative=editor (row=0,col=0) refers to the top-left corner of the 63 /// screen-grid and (row=Lines-1,col=Columns-1) refers to the bottom-right 64 /// corner. Fractional values are allowed, but the builtin implementation 65 /// (used by non-multigrid UIs) will always round down to nearest integer. 66 /// 67 /// Out-of-bounds values, and configurations that make the float not fit inside 68 /// the main editor, are allowed. The builtin implementation truncates values 69 /// so floats are fully within the main screen grid. External GUIs 70 /// could let floats hover outside of the main window like a tooltip, but 71 /// this should not be used to specify arbitrary WM screen positions. 72 /// 73 /// Examples: 74 /// 75 /// ```lua 76 /// -- Window-relative float with 'statusline' enabled: 77 /// local w1 = vim.api.nvim_open_win(0, false, 78 /// {relative='win', row=3, col=3, width=40, height=4}) 79 /// vim.wo[w1].statusline = vim.o.statusline 80 /// 81 /// -- Buffer-relative float (travels as buffer is scrolled): 82 /// vim.api.nvim_open_win(0, false, 83 /// {relative='win', width=40, height=4, bufpos={100,10}}) 84 /// 85 /// -- Vertical split left of the current window: 86 /// vim.api.nvim_open_win(0, false, { split = 'left', win = 0, }) 87 /// ``` 88 /// 89 /// @param buffer Buffer to display, or 0 for current buffer 90 /// @param enter Enter the window (make it the current window) 91 /// @param config Map defining the window configuration. Keys: 92 /// - anchor: Decides which corner of the float to place at (row,col): 93 /// - "NW" northwest (default) 94 /// - "NE" northeast 95 /// - "SW" southwest 96 /// - "SE" southeast 97 /// - border: (`string|string[]`) (defaults to 'winborder' option) Window border. The string form 98 /// accepts the same values as the 'winborder' option. The array form must have a length of 99 /// eight or any divisor of eight, specifying the chars that form the border in a clockwise 100 /// fashion starting from the top-left corner. For example, the double-box style can be 101 /// specified as: 102 /// ``` 103 /// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. 104 /// ``` 105 /// If fewer than eight chars are given, they will be repeated. An ASCII border could be 106 /// specified as: 107 /// ``` 108 /// [ "/", "-", \"\\\\\", "|" ], 109 /// ``` 110 /// Or one char for all sides: 111 /// ``` 112 /// [ "x" ]. 113 /// ``` 114 /// Empty string can be used to hide a specific border. This example will show only vertical 115 /// borders, not horizontal: 116 /// ``` 117 /// [ "", "", "", ">", "", "", "", "<" ] 118 /// ``` 119 /// By default, |hl-FloatBorder| highlight is used, which links to |hl-WinSeparator| when not 120 /// defined. Each border side can specify an optional highlight: 121 /// ``` 122 /// [ ["+", "MyCorner"], ["x", "MyBorder"] ]. 123 /// ``` 124 /// - bufpos: Places float relative to buffer text (only when 125 /// relative="win"). Takes a tuple of zero-indexed `[line, column]`. 126 /// `row` and `col` if given are applied relative to this 127 /// position, else they default to: 128 /// - `row=1` and `col=0` if `anchor` is "NW" or "NE" 129 /// - `row=0` and `col=0` if `anchor` is "SW" or "SE" 130 /// (thus like a tooltip near the buffer text). 131 /// - col: Column position in units of screen cell width, may be fractional. 132 /// - external: GUI should display the window as an external 133 /// top-level window. Currently accepts no other positioning 134 /// configuration together with this. 135 /// - fixed: If true when anchor is NW or SW, the float window 136 /// would be kept fixed even if the window would be truncated. 137 /// - focusable: Enable focus by user actions (wincmds, mouse events). 138 /// Defaults to true. Non-focusable windows can be entered by 139 /// |nvim_set_current_win()|, or, when the `mouse` field is set to true, 140 /// by mouse events. See |focusable|. 141 /// - footer: (optional) Footer in window border, string or list. 142 /// List should consist of `[text, highlight]` tuples. 143 /// If string, or a tuple lacks a highlight, the default highlight group is `FloatFooter`. 144 /// - footer_pos: Footer position. Must be set with `footer` option. 145 /// Value can be one of "left", "center", or "right". 146 /// Default is `"left"`. 147 /// - height: Window height (in character cells). Minimum of 1. 148 /// - hide: If true the floating window will be hidden and the cursor will be invisible when 149 /// focused on it. 150 /// - mouse: Specify how this window interacts with mouse events. 151 /// Defaults to `focusable` value. 152 /// - If false, mouse events pass through this window. 153 /// - If true, mouse events interact with this window normally. 154 /// - noautocmd: Block all autocommands for the duration of the call. Cannot be changed by 155 /// |nvim_win_set_config()|. 156 /// - relative: Sets the window layout to "floating", placed at (row,col) 157 /// coordinates relative to: 158 /// - "cursor" Cursor position in current window. 159 /// - "editor" The global editor grid. 160 /// - "laststatus" 'laststatus' if present, or last row. 161 /// - "mouse" Mouse position. 162 /// - "tabline" Tabline if present, or first row. 163 /// - "win" Window given by the `win` field, or current window. 164 /// - row: Row position in units of "screen cell height", may be fractional. 165 /// - split: Split direction: "left", "right", "above", "below". 166 /// - style: (optional) Configure the appearance of the window. Currently 167 /// only supports one value: 168 /// - "minimal" Nvim will display the window with many UI options 169 /// disabled. This is useful when displaying a temporary 170 /// float where the text should not be edited. Disables 171 /// 'number', 'relativenumber', 'cursorline', 'cursorcolumn', 172 /// 'foldcolumn', 'spell' and 'list' options. 'signcolumn' 173 /// is changed to `auto` and 'colorcolumn' is cleared. 174 /// 'statuscolumn' is changed to empty. The end-of-buffer 175 /// region is hidden by setting `eob` flag of 176 /// 'fillchars' to a space char, and clearing the 177 /// |hl-EndOfBuffer| region in 'winhighlight'. 178 /// - title: (optional) Title in window border, string or list. 179 /// List should consist of `[text, highlight]` tuples. 180 /// If string, or a tuple lacks a highlight, the default highlight group is `FloatTitle`. 181 /// - title_pos: Title position. Must be set with `title` option. 182 /// Value can be one of "left", "center", or "right". 183 /// Default is `"left"`. 184 /// - vertical: Split vertically |:vertical|. 185 /// - width: Window width (in character cells). Minimum of 1. 186 /// - win: |window-ID| window to split, or relative window when creating a float (relative="win"). 187 /// When splitting, negative value works like |:topleft|, |:botright|. 188 /// - zindex: Stacking order. floats with higher `zindex` go on top on 189 /// floats with lower indices. Must be larger than zero. The 190 /// following screen elements have hard-coded z-indices: 191 /// - 100: insert completion popupmenu 192 /// - 200: message scrollback 193 /// - 250: cmdline completion popupmenu (when wildoptions+=pum) 194 /// The default value for floats are 50. In general, values below 100 are 195 /// recommended, unless there is a good reason to overshadow builtin 196 /// elements. 197 /// - _cmdline_offset: (EXPERIMENTAL) When provided, anchor the |cmdline-completion| 198 /// popupmenu to this window, with an offset in screen cell width. 199 /// 200 /// @param[out] err Error details, if any 201 /// 202 /// @return |window-ID|, or 0 on error 203 Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Error *err) 204 FUNC_API_SINCE(6) FUNC_API_TEXTLOCK_ALLOW_CMDWIN 205 { 206 #define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key) 207 buf_T *buf = find_buffer_by_handle(buffer, err); 208 if (!buf) { 209 return 0; 210 } 211 if ((cmdwin_type != 0 && enter) || buf == cmdwin_buf) { 212 api_set_error(err, kErrorTypeException, "%s", e_cmdwin); 213 return 0; 214 } 215 216 WinConfig fconfig = WIN_CONFIG_INIT; 217 if (!parse_win_config(NULL, config, &fconfig, false, err)) { 218 return 0; 219 } 220 221 bool is_split = HAS_KEY_X(config, split) || HAS_KEY_X(config, vertical); 222 Window rv = 0; 223 if (fconfig.noautocmd) { 224 block_autocmds(); 225 } 226 227 win_T *wp = NULL; 228 tabpage_T *tp = curtab; 229 assert(curwin != NULL); 230 win_T *parent = config->win == 0 ? curwin : NULL; 231 if (config->win > 0) { 232 parent = find_window_by_handle(fconfig.window, err); 233 if (!parent) { 234 // find_window_by_handle has already set the error 235 goto cleanup; 236 } else if (is_split && parent->w_floating) { 237 api_set_error(err, kErrorTypeException, "Cannot split a floating window"); 238 goto cleanup; 239 } 240 tp = win_find_tabpage(parent); 241 } 242 if (is_split) { 243 if (!check_split_disallowed_err(parent ? parent : curwin, err)) { 244 goto cleanup; // error already set 245 } 246 247 if (HAS_KEY_X(config, vertical) && !HAS_KEY_X(config, split)) { 248 if (config->vertical) { 249 fconfig.split = p_spr ? kWinSplitRight : kWinSplitLeft; 250 } else { 251 fconfig.split = p_sb ? kWinSplitBelow : kWinSplitAbove; 252 } 253 } 254 int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; 255 int size = (flags & WSP_VERT) ? fconfig.width : fconfig.height; 256 257 TRY_WRAP(err, { 258 if (parent == NULL || parent == curwin) { 259 wp = win_split_ins(size, flags, NULL, 0, NULL); 260 } else { 261 switchwin_T switchwin; 262 // `parent` is valid in `tp`, so switch_win should not fail. 263 const int result = switch_win(&switchwin, parent, tp, true); 264 assert(result == OK); 265 (void)result; 266 wp = win_split_ins(size, flags, NULL, 0, NULL); 267 restore_win(&switchwin, true); 268 } 269 }); 270 if (wp) { 271 wp->w_config = fconfig; 272 if (size > 0) { 273 // Without room for the requested size, window sizes may have been equalized instead. 274 // If the size differs from what was requested, try to set it again now. 275 if ((flags & WSP_VERT) && wp->w_width != size) { 276 win_setwidth_win(size, wp); 277 } else if (!(flags & WSP_VERT) && wp->w_height != size) { 278 win_setheight_win(size, wp); 279 } 280 } 281 } 282 } else { 283 // Unlike check_split_disallowed_err, ignore `split_disallowed`, as opening a float shouldn't 284 // mess with the frame structure. Still check `b_locked_split` to avoid opening more windows 285 // into a closing buffer, though. 286 if (curwin->w_buffer->b_locked_split) { // Can't instead check `buf` in case win_set_buf fails! 287 api_set_error(err, kErrorTypeException, "E1159: Cannot open a float when closing the buffer"); 288 goto cleanup; 289 } 290 wp = win_new_float(NULL, false, fconfig, err); 291 } 292 if (!wp) { 293 if (!ERROR_SET(err)) { 294 api_set_error(err, kErrorTypeException, "Failed to create window"); 295 } 296 goto cleanup; 297 } 298 299 if (fconfig._cmdline_offset < INT_MAX) { 300 cmdline_win = wp; 301 } 302 303 // Autocommands may close `wp` or move it to another tabpage, so update and check `tp` after each 304 // event. In each case, `wp` should already be valid in `tp`, so switch_win should not fail. 305 // Also, autocommands may free the `buf` to switch to, so store a bufref to check. 306 bufref_T bufref; 307 set_bufref(&bufref, buf); 308 if (!fconfig.noautocmd) { 309 switchwin_T switchwin; 310 const int result = switch_win_noblock(&switchwin, wp, tp, true); 311 assert(result == OK); 312 (void)result; 313 if (apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf)) { 314 tp = win_find_tabpage(wp); 315 } 316 restore_win_noblock(&switchwin, true); 317 } 318 if (tp && enter) { 319 goto_tabpage_win(tp, wp); 320 tp = win_find_tabpage(wp); 321 } 322 if (tp && bufref_valid(&bufref) && buf != wp->w_buffer) { 323 // win_set_buf temporarily makes `wp` the curwin to set the buffer. 324 // If not entering `wp`, block Enter and Leave events. (cringe) 325 const bool au_no_enter_leave = curwin != wp && !fconfig.noautocmd; 326 if (au_no_enter_leave) { 327 autocmd_no_enter++; 328 autocmd_no_leave++; 329 } 330 win_set_buf(wp, buf, err); 331 if (!fconfig.noautocmd) { 332 tp = win_find_tabpage(wp); 333 } 334 if (au_no_enter_leave) { 335 autocmd_no_enter--; 336 autocmd_no_leave--; 337 } 338 } 339 if (!tp) { 340 api_clear_error(err); // may have been set by win_set_buf 341 api_set_error(err, kErrorTypeException, "Window was closed immediately"); 342 goto cleanup; 343 } 344 345 if (fconfig.style == kWinStyleMinimal) { 346 win_set_minimal_style(wp); 347 didset_window_options(wp, true); 348 } 349 rv = wp->handle; 350 351 cleanup: 352 if (fconfig.noautocmd) { 353 unblock_autocmds(); 354 } 355 return rv; 356 #undef HAS_KEY_X 357 } 358 359 static WinSplit win_split_dir(win_T *win) 360 { 361 if (win->w_frame == NULL || win->w_frame->fr_parent == NULL) { 362 return kWinSplitLeft; 363 } 364 365 char layout = win->w_frame->fr_parent->fr_layout; 366 if (layout == FR_COL) { 367 return win->w_frame->fr_next ? kWinSplitAbove : kWinSplitBelow; 368 } else { 369 return win->w_frame->fr_next ? kWinSplitLeft : kWinSplitRight; 370 } 371 } 372 373 static int win_split_flags(WinSplit split, bool toplevel) 374 { 375 int flags = 0; 376 if (split == kWinSplitAbove || split == kWinSplitBelow) { 377 flags |= WSP_HOR; 378 } else { 379 flags |= WSP_VERT; 380 } 381 if (split == kWinSplitAbove || split == kWinSplitLeft) { 382 flags |= toplevel ? WSP_TOP : WSP_ABOVE; 383 } else { 384 flags |= toplevel ? WSP_BOT : WSP_BELOW; 385 } 386 return flags; 387 } 388 389 /// Reconfigures the layout of a window. 390 /// 391 /// - Absent (`nil`) keys will not be changed. 392 /// - `row` / `col` / `relative` must be reconfigured together. 393 /// - Cannot be used to move the last window in a tabpage to a different one. 394 /// 395 /// Example: to convert a floating window to a "normal" split window, specify the `win` field: 396 /// 397 /// ```lua 398 /// vim.api.nvim_win_set_config(0, { split = 'above', win = vim.fn.win_getid(1), }) 399 /// ``` 400 /// 401 /// @see |nvim_open_win()| 402 /// 403 /// @param window |window-ID|, or 0 for current window 404 /// @param config Map defining the window configuration, see [nvim_open_win()] 405 /// @param[out] err Error details, if any 406 void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) 407 FUNC_API_SINCE(6) 408 { 409 #define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key) 410 win_T *win = find_window_by_handle(window, err); 411 if (!win) { 412 return; 413 } 414 415 tabpage_T *win_tp = win_find_tabpage(win); 416 bool was_split = !win->w_floating; 417 bool has_split = HAS_KEY_X(config, split); 418 bool has_vertical = HAS_KEY_X(config, vertical); 419 // reuse old values, if not overridden 420 WinConfig fconfig = win->w_config; 421 422 bool to_split = config->relative.size == 0 423 && !(HAS_KEY_X(config, external) ? config->external : fconfig.external) 424 && (has_split || has_vertical || was_split); 425 426 if (!parse_win_config(win, config, &fconfig, !was_split || to_split, err)) { 427 return; 428 } 429 430 if (was_split && !to_split) { 431 if (!win_new_float(win, false, fconfig, err)) { 432 return; 433 } 434 redraw_later(win, UPD_NOT_VALID); 435 } else if (to_split) { 436 win_T *parent = NULL; 437 tabpage_T *parent_tp = NULL; 438 if (config->win == 0) { 439 parent = curwin; 440 parent_tp = curtab; 441 } else if (config->win > 0) { 442 parent = find_window_by_handle(fconfig.window, err); 443 if (!parent) { 444 return; 445 } 446 parent_tp = win_find_tabpage(parent); 447 } 448 if (parent) { 449 if (parent->w_floating) { 450 api_set_error(err, kErrorTypeException, "Cannot split a floating window"); 451 return; 452 } 453 if (is_aucmd_win(win) && win_tp != parent_tp) { 454 api_set_error(err, kErrorTypeException, "Cannot move autocmd window to another tabpage"); 455 return; 456 } 457 // Can't move the cmdwin or its old curwin to a different tabpage. 458 if ((win == cmdwin_win || win == cmdwin_old_curwin) && win_tp != parent_tp) { 459 api_set_error(err, kErrorTypeException, "%s", e_cmdwin); 460 return; 461 } 462 } 463 464 WinSplit old_split = win_split_dir(win); 465 if (has_vertical && !has_split) { 466 if (config->vertical) { 467 fconfig.split = (old_split == kWinSplitRight || p_spr) ? kWinSplitRight : kWinSplitLeft; 468 } else { 469 fconfig.split = (old_split == kWinSplitBelow || p_sb) ? kWinSplitBelow : kWinSplitAbove; 470 } 471 } 472 merge_win_config(&win->w_config, fconfig); 473 474 // If there's no "vertical" or "split" set, or if "split" is unchanged, 475 // then we can just change the size of the window. 476 if ((!has_vertical && !has_split) 477 || (was_split && !HAS_KEY_X(config, win) && old_split == fconfig.split)) { 478 if (HAS_KEY_X(config, width)) { 479 win_setwidth_win(fconfig.width, win); 480 } 481 if (HAS_KEY_X(config, height)) { 482 win_setheight_win(fconfig.height, win); 483 } 484 redraw_later(win, UPD_NOT_VALID); 485 return; 486 } 487 488 if (!check_split_disallowed_err(win, err)) { 489 return; // error already set 490 } 491 492 bool to_split_ok = false; 493 // If we are moving curwin to another tabpage, switch windows *before* we remove it from the 494 // window list or remove its frame (if non-floating), so it's valid for autocommands. 495 const bool curwin_moving_tp = win == curwin && parent && win_tp != parent_tp; 496 if (curwin_moving_tp) { 497 if (was_split) { 498 int dir; 499 win_T *altwin = winframe_find_altwin(win, &dir, NULL, NULL); 500 // Autocommands may still make this the last non-float after this check. 501 // That case will be caught later when trying to move the window. 502 if (!altwin) { 503 api_set_error(err, kErrorTypeException, "Cannot move last non-floating window"); 504 return; 505 } 506 win_goto(altwin); 507 } else { 508 win_goto(win_float_find_altwin(win, NULL)); 509 } 510 511 // Autocommands may have been a real nuisance and messed things up... 512 if (curwin == win) { 513 api_set_error(err, kErrorTypeException, "Failed to switch away from window %d", 514 win->handle); 515 return; 516 } 517 win_tp = win_find_tabpage(win); 518 if (!win_tp || !win_valid_any_tab(parent)) { 519 api_set_error(err, kErrorTypeException, "Windows to split were closed"); 520 goto restore_curwin; 521 } 522 if (was_split == win->w_floating || parent->w_floating) { 523 api_set_error(err, kErrorTypeException, "Floating state of windows to split changed"); 524 goto restore_curwin; 525 } 526 } 527 528 int dir = 0; 529 frame_T *unflat_altfr = NULL; 530 win_T *altwin = NULL; 531 532 if (was_split) { 533 // If the window is the last in the tabpage or `fconfig.win` is 534 // a handle to itself, we can't split it. 535 if (win->w_frame->fr_parent == NULL) { 536 // FIXME(willothy): if the window is the last in the tabpage but there is another tabpage 537 // and the target window is in that other tabpage, should we move the window to that 538 // tabpage and close the previous one, or just error? 539 api_set_error(err, kErrorTypeException, "Cannot move last non-floating window"); 540 goto restore_curwin; 541 } else if (parent != NULL && parent->handle == win->handle) { 542 int n_frames = 0; 543 for (frame_T *fr = win->w_frame->fr_parent->fr_child; fr != NULL; fr = fr->fr_next) { 544 n_frames++; 545 } 546 547 win_T *neighbor = NULL; 548 549 if (n_frames > 2) { 550 // There are three or more windows in the frame, we need to split a neighboring window. 551 frame_T *frame = win->w_frame->fr_parent; 552 553 if (frame->fr_parent) { 554 // ┌──────────────┐ 555 // │ A │ 556 // ├────┬────┬────┤ 557 // │ B │ C │ D │ 558 // └────┴────┴────┘ 559 // || 560 // \/ 561 // ┌───────────────────┐ 562 // │ A │ 563 // ├─────────┬─────────┤ 564 // │ │ C │ 565 // │ B ├─────────┤ 566 // │ │ D │ 567 // └─────────┴─────────┘ 568 if (fconfig.split == kWinSplitAbove || fconfig.split == kWinSplitLeft) { 569 neighbor = win->w_next; 570 } else { 571 neighbor = win->w_prev; 572 } 573 } 574 // If the frame doesn't have a parent, the old frame 575 // was the root frame and we need to create a top-level split. 576 altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); 577 } else if (n_frames == 2) { 578 // There are two windows in the frame, we can just rotate it. 579 altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); 580 neighbor = altwin; 581 } else { 582 // There is only one window in the frame, we can't split it. 583 api_set_error(err, kErrorTypeException, "Cannot split window into itself"); 584 goto restore_curwin; 585 } 586 // Set the parent to whatever the correct neighbor window was determined to be. 587 parent = neighbor; 588 } else { 589 altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); 590 } 591 } else { 592 altwin = win_float_find_altwin(win, win_tp == curtab ? NULL : win_tp); 593 } 594 595 win_remove(win, win_tp == curtab ? NULL : win_tp); 596 if (win_tp == curtab) { 597 last_status(false); // may need to remove last status line 598 win_comp_pos(); // recompute window positions 599 } 600 601 int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; 602 parent_tp = parent ? win_find_tabpage(parent) : curtab; 603 604 TRY_WRAP(err, { 605 const bool need_switch = parent != NULL && parent != curwin; 606 switchwin_T switchwin; 607 if (need_switch) { 608 // `parent` is valid in its tabpage, so switch_win should not fail. 609 const int result = switch_win(&switchwin, parent, parent_tp, true); 610 (void)result; 611 assert(result == OK); 612 } 613 to_split_ok = win_split_ins(0, flags, win, 0, unflat_altfr) != NULL; 614 if (!to_split_ok) { 615 // Restore `win` to the window list now, so it's valid for restore_win (if used). 616 win_append(win->w_prev, win, win_tp == curtab ? NULL : win_tp); 617 } 618 if (need_switch) { 619 restore_win(&switchwin, true); 620 } 621 }); 622 if (!to_split_ok) { 623 if (was_split) { 624 // win_split_ins doesn't change sizes or layout if it fails to insert an existing window, so 625 // just undo winframe_remove. 626 winframe_restore(win, dir, unflat_altfr); 627 } 628 if (!ERROR_SET(err)) { 629 api_set_error(err, kErrorTypeException, "Failed to move window %d into split", win->handle); 630 } 631 632 restore_curwin: 633 // If `win` was the original curwin, and autocommands didn't move it outside of curtab, be a 634 // good citizen and try to return to it. 635 if (curwin_moving_tp && win_valid(win)) { 636 win_goto(win); 637 } 638 return; 639 } 640 641 // If `win` moved tabpages and was the curwin of its old one, select a new curwin for it. 642 if (win_tp != parent_tp && win_tp->tp_curwin == win) { 643 win_tp->tp_curwin = altwin; 644 } 645 646 if (HAS_KEY_X(config, width)) { 647 win_setwidth_win(fconfig.width, win); 648 } 649 if (HAS_KEY_X(config, height)) { 650 win_setheight_win(fconfig.height, win); 651 } 652 } else { 653 win_config_float(win, fconfig); 654 } 655 if (HAS_KEY_X(config, style)) { 656 if (fconfig.style == kWinStyleMinimal) { 657 win_set_minimal_style(win); 658 didset_window_options(win, true); 659 } 660 } 661 if (fconfig._cmdline_offset < INT_MAX) { 662 cmdline_win = win; 663 } else if (win == cmdline_win && fconfig._cmdline_offset == INT_MAX) { 664 cmdline_win = NULL; 665 } 666 #undef HAS_KEY_X 667 } 668 669 #define PUT_KEY_X(d, key, value) PUT_KEY(d, win_config, key, value) 670 static void config_put_bordertext(Dict(win_config) *config, WinConfig *fconfig, 671 BorderTextType bordertext_type, Arena *arena) 672 { 673 VirtText vt; 674 AlignTextPos align; 675 switch (bordertext_type) { 676 case kBorderTextTitle: 677 vt = fconfig->title_chunks; 678 align = fconfig->title_pos; 679 break; 680 case kBorderTextFooter: 681 vt = fconfig->footer_chunks; 682 align = fconfig->footer_pos; 683 break; 684 } 685 686 Array bordertext = virt_text_to_array(vt, true, arena); 687 688 char *pos; 689 switch (align) { 690 case kAlignLeft: 691 pos = "left"; 692 break; 693 case kAlignCenter: 694 pos = "center"; 695 break; 696 case kAlignRight: 697 pos = "right"; 698 break; 699 } 700 701 switch (bordertext_type) { 702 case kBorderTextTitle: 703 PUT_KEY_X(*config, title, ARRAY_OBJ(bordertext)); 704 PUT_KEY_X(*config, title_pos, cstr_as_string(pos)); 705 break; 706 case kBorderTextFooter: 707 PUT_KEY_X(*config, footer, ARRAY_OBJ(bordertext)); 708 PUT_KEY_X(*config, footer_pos, cstr_as_string(pos)); 709 } 710 } 711 712 /// Gets window configuration in the form of a dict which can be passed as the `config` parameter of 713 /// |nvim_open_win()|. 714 /// 715 /// For non-floating windows, `relative` is empty. 716 /// 717 /// @param window |window-ID|, or 0 for current window 718 /// @param[out] err Error details, if any 719 /// @return Map defining the window configuration, see |nvim_open_win()| 720 Dict(win_config) nvim_win_get_config(Window window, Arena *arena, Error *err) 721 FUNC_API_SINCE(6) 722 { 723 /// Keep in sync with FloatRelative in buffer_defs.h 724 static const char *const float_relative_str[] = { 725 "editor", "win", "cursor", "mouse", "tabline", "laststatus" 726 }; 727 728 /// Keep in sync with WinSplit in buffer_defs.h 729 static const char *const win_split_str[] = { "left", "right", "above", "below" }; 730 731 Dict(win_config) rv = KEYDICT_INIT; 732 733 win_T *wp = find_window_by_handle(window, err); 734 if (!wp) { 735 return rv; 736 } 737 738 WinConfig *config = &wp->w_config; 739 740 PUT_KEY_X(rv, focusable, config->focusable); 741 PUT_KEY_X(rv, external, config->external); 742 PUT_KEY_X(rv, hide, config->hide); 743 PUT_KEY_X(rv, mouse, config->mouse); 744 745 if (wp->w_floating) { 746 PUT_KEY_X(rv, width, config->width); 747 PUT_KEY_X(rv, height, config->height); 748 if (!config->external) { 749 if (config->relative == kFloatRelativeWindow) { 750 PUT_KEY_X(rv, win, config->window); 751 if (config->bufpos.lnum >= 0) { 752 Array pos = arena_array(arena, 2); 753 ADD_C(pos, INTEGER_OBJ(config->bufpos.lnum)); 754 ADD_C(pos, INTEGER_OBJ(config->bufpos.col)); 755 PUT_KEY_X(rv, bufpos, pos); 756 } 757 } 758 PUT_KEY_X(rv, anchor, cstr_as_string(float_anchor_str[config->anchor])); 759 PUT_KEY_X(rv, row, config->row); 760 PUT_KEY_X(rv, col, config->col); 761 PUT_KEY_X(rv, zindex, config->zindex); 762 } 763 if (config->border) { 764 Array border = arena_array(arena, 8); 765 for (size_t i = 0; i < 8; i++) { 766 String s = cstrn_as_string(config->border_chars[i], MAX_SCHAR_SIZE); 767 768 int hi_id = config->border_hl_ids[i]; 769 char *hi_name = syn_id2name(hi_id); 770 if (hi_name[0]) { 771 Array tuple = arena_array(arena, 2); 772 ADD_C(tuple, STRING_OBJ(s)); 773 ADD_C(tuple, CSTR_AS_OBJ(hi_name)); 774 ADD_C(border, ARRAY_OBJ(tuple)); 775 } else { 776 ADD_C(border, STRING_OBJ(s)); 777 } 778 } 779 PUT_KEY_X(rv, border, ARRAY_OBJ(border)); 780 if (config->title) { 781 config_put_bordertext(&rv, config, kBorderTextTitle, arena); 782 } 783 if (config->footer) { 784 config_put_bordertext(&rv, config, kBorderTextFooter, arena); 785 } 786 } else { 787 PUT_KEY_X(rv, border, STRING_OBJ(cstr_as_string("none"))); 788 } 789 } else if (!config->external) { 790 PUT_KEY_X(rv, width, wp->w_width); 791 PUT_KEY_X(rv, height, wp->w_height); 792 WinSplit split = win_split_dir(wp); 793 PUT_KEY_X(rv, split, cstr_as_string(win_split_str[split])); 794 } 795 796 const char *rel = (wp->w_floating && !config->external 797 ? float_relative_str[config->relative] : ""); 798 PUT_KEY_X(rv, relative, cstr_as_string(rel)); 799 if (config->_cmdline_offset < INT_MAX) { 800 PUT_KEY_X(rv, _cmdline_offset, config->_cmdline_offset); 801 } 802 803 return rv; 804 } 805 806 static bool parse_float_anchor(String anchor, FloatAnchor *out) 807 { 808 if (anchor.size == 0) { 809 *out = (FloatAnchor)0; 810 } 811 char *str = anchor.data; 812 if (striequal(str, "NW")) { 813 *out = 0; // NW is the default 814 } else if (striequal(str, "NE")) { 815 *out = kFloatAnchorEast; 816 } else if (striequal(str, "SW")) { 817 *out = kFloatAnchorSouth; 818 } else if (striequal(str, "SE")) { 819 *out = kFloatAnchorSouth | kFloatAnchorEast; 820 } else { 821 return false; 822 } 823 return true; 824 } 825 826 static bool parse_float_relative(String relative, FloatRelative *out) 827 { 828 char *str = relative.data; 829 if (striequal(str, "editor")) { 830 *out = kFloatRelativeEditor; 831 } else if (striequal(str, "win")) { 832 *out = kFloatRelativeWindow; 833 } else if (striequal(str, "cursor")) { 834 *out = kFloatRelativeCursor; 835 } else if (striequal(str, "mouse")) { 836 *out = kFloatRelativeMouse; 837 } else if (striequal(str, "tabline")) { 838 *out = kFloatRelativeTabline; 839 } else if (striequal(str, "laststatus")) { 840 *out = kFloatRelativeLaststatus; 841 } else { 842 return false; 843 } 844 return true; 845 } 846 847 static bool parse_config_split(String split, WinSplit *out) 848 { 849 char *str = split.data; 850 if (striequal(str, "left")) { 851 *out = kWinSplitLeft; 852 } else if (striequal(str, "right")) { 853 *out = kWinSplitRight; 854 } else if (striequal(str, "above")) { 855 *out = kWinSplitAbove; 856 } else if (striequal(str, "below")) { 857 *out = kWinSplitBelow; 858 } else { 859 return false; 860 } 861 return true; 862 } 863 864 static bool parse_float_bufpos(Array bufpos, lpos_T *out) 865 { 866 if (bufpos.size != 2 || bufpos.items[0].type != kObjectTypeInteger 867 || bufpos.items[1].type != kObjectTypeInteger) { 868 return false; 869 } 870 out->lnum = (linenr_T)bufpos.items[0].data.integer; 871 out->col = (colnr_T)bufpos.items[1].data.integer; 872 return true; 873 } 874 875 static void parse_bordertext(Object bordertext, BorderTextType bordertext_type, WinConfig *fconfig, 876 Error *err) 877 { 878 if (bordertext.type != kObjectTypeString && bordertext.type != kObjectTypeArray) { 879 api_set_error(err, kErrorTypeValidation, "title/footer must be string or array"); 880 return; 881 } 882 883 if (bordertext.type == kObjectTypeArray && bordertext.data.array.size == 0) { 884 api_set_error(err, kErrorTypeValidation, "title/footer cannot be an empty array"); 885 return; 886 } 887 888 bool *is_present; 889 VirtText *chunks; 890 int *width; 891 switch (bordertext_type) { 892 case kBorderTextTitle: 893 is_present = &fconfig->title; 894 chunks = &fconfig->title_chunks; 895 width = &fconfig->title_width; 896 break; 897 case kBorderTextFooter: 898 is_present = &fconfig->footer; 899 chunks = &fconfig->footer_chunks; 900 width = &fconfig->footer_width; 901 break; 902 } 903 904 if (bordertext.type == kObjectTypeString) { 905 if (bordertext.data.string.size == 0) { 906 *is_present = false; 907 return; 908 } 909 kv_init(*chunks); 910 kv_push(*chunks, ((VirtTextChunk){ .text = xstrdup(bordertext.data.string.data), 911 .hl_id = -1 })); 912 *width = (int)mb_string2cells(bordertext.data.string.data); 913 *is_present = true; 914 return; 915 } 916 917 *width = 0; 918 *chunks = parse_virt_text(bordertext.data.array, err, width); 919 920 *is_present = true; 921 } 922 923 static bool parse_bordertext_pos(win_T *wp, String bordertext_pos, BorderTextType bordertext_type, 924 WinConfig *fconfig, Error *err) 925 { 926 AlignTextPos *align; 927 switch (bordertext_type) { 928 case kBorderTextTitle: 929 align = &fconfig->title_pos; 930 break; 931 case kBorderTextFooter: 932 align = &fconfig->footer_pos; 933 break; 934 } 935 936 if (bordertext_pos.size == 0) { 937 if (!wp) { 938 *align = kAlignLeft; 939 } 940 return true; 941 } 942 943 char *pos = bordertext_pos.data; 944 945 if (strequal(pos, "left")) { 946 *align = kAlignLeft; 947 } else if (strequal(pos, "center")) { 948 *align = kAlignCenter; 949 } else if (strequal(pos, "right")) { 950 *align = kAlignRight; 951 } else { 952 switch (bordertext_type) { 953 case kBorderTextTitle: 954 api_set_error(err, kErrorTypeValidation, "invalid title_pos value"); 955 break; 956 case kBorderTextFooter: 957 api_set_error(err, kErrorTypeValidation, "invalid footer_pos value"); 958 break; 959 } 960 return false; 961 } 962 return true; 963 } 964 965 void parse_border_style(Object style, WinConfig *fconfig, Error *err) 966 { 967 struct { 968 const char *name; 969 char chars[8][MAX_SCHAR_SIZE]; 970 bool shadow_color; 971 } defaults[] = { 972 { opt_winborder_values[1], { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false }, 973 { opt_winborder_values[2], { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false }, 974 { opt_winborder_values[3], { "", "", " ", " ", " ", " ", " ", "" }, true }, 975 { opt_winborder_values[4], { "╭", "─", "╮", "│", "╯", "─", "╰", "│" }, false }, 976 { opt_winborder_values[5], { " ", " ", " ", " ", " ", " ", " ", " " }, false }, 977 { opt_winborder_values[6], { "┏", "━", "┓", "┃", "┛", "━", "┗", "┃" }, false }, 978 { NULL, { { NUL } }, false }, 979 }; 980 981 char(*chars)[MAX_SCHAR_SIZE] = fconfig->border_chars; 982 int *hl_ids = fconfig->border_hl_ids; 983 984 fconfig->border = true; 985 986 if (style.type == kObjectTypeArray) { 987 Array arr = style.data.array; 988 size_t size = arr.size; 989 if (!size || size > 8 || (size & (size - 1))) { 990 api_set_error(err, kErrorTypeValidation, "invalid number of border chars"); 991 return; 992 } 993 for (size_t i = 0; i < size; i++) { 994 Object iytem = arr.items[i]; 995 String string; 996 int hl_id = 0; 997 if (iytem.type == kObjectTypeArray) { 998 Array iarr = iytem.data.array; 999 if (!iarr.size || iarr.size > 2) { 1000 api_set_error(err, kErrorTypeValidation, "invalid border char"); 1001 return; 1002 } 1003 if (iarr.items[0].type != kObjectTypeString) { 1004 api_set_error(err, kErrorTypeValidation, "invalid border char"); 1005 return; 1006 } 1007 string = iarr.items[0].data.string; 1008 if (iarr.size == 2) { 1009 hl_id = object_to_hl_id(iarr.items[1], "border char highlight", err); 1010 if (ERROR_SET(err)) { 1011 return; 1012 } 1013 } 1014 } else if (iytem.type == kObjectTypeString) { 1015 string = iytem.data.string; 1016 } else { 1017 api_set_error(err, kErrorTypeValidation, "invalid border char"); 1018 return; 1019 } 1020 if (string.size && mb_string2cells_len(string.data, string.size) > 1) { 1021 api_set_error(err, kErrorTypeValidation, "border chars must be one cell"); 1022 return; 1023 } 1024 size_t len = MIN(string.size, sizeof(*chars) - 1); 1025 if (len) { 1026 memcpy(chars[i], string.data, len); 1027 } 1028 chars[i][len] = NUL; 1029 hl_ids[i] = hl_id; 1030 } 1031 while (size < 8) { 1032 memcpy(chars + size, chars, sizeof(*chars) * size); 1033 memcpy(hl_ids + size, hl_ids, sizeof(*hl_ids) * size); 1034 size <<= 1; 1035 } 1036 if ((chars[7][0] && chars[1][0] && !chars[0][0]) 1037 || (chars[1][0] && chars[3][0] && !chars[2][0]) 1038 || (chars[3][0] && chars[5][0] && !chars[4][0]) 1039 || (chars[5][0] && chars[7][0] && !chars[6][0])) { 1040 api_set_error(err, kErrorTypeValidation, "corner between used edges must be specified"); 1041 } 1042 } else if (style.type == kObjectTypeString) { 1043 String str = style.data.string; 1044 if (str.size == 0 || strequal(str.data, "none")) { 1045 fconfig->border = false; 1046 // border text does not work with border equal none 1047 fconfig->title = false; 1048 fconfig->footer = false; 1049 return; 1050 } 1051 for (size_t i = 0; defaults[i].name; i++) { 1052 if (strequal(str.data, defaults[i].name)) { 1053 memcpy(chars, defaults[i].chars, sizeof(defaults[i].chars)); 1054 memset(hl_ids, 0, 8 * sizeof(*hl_ids)); 1055 if (defaults[i].shadow_color) { 1056 int hl_blend = SYN_GROUP_STATIC("FloatShadow"); 1057 int hl_through = SYN_GROUP_STATIC("FloatShadowThrough"); 1058 hl_ids[2] = hl_through; 1059 hl_ids[3] = hl_blend; 1060 hl_ids[4] = hl_blend; 1061 hl_ids[5] = hl_blend; 1062 hl_ids[6] = hl_through; 1063 } 1064 return; 1065 } 1066 } 1067 api_set_error(err, kErrorTypeValidation, "invalid border style \"%s\"", str.data); 1068 } 1069 } 1070 1071 static void generate_api_error(win_T *wp, const char *attribute, Error *err) 1072 { 1073 if (wp != NULL && wp->w_floating) { 1074 api_set_error(err, kErrorTypeValidation, 1075 "Missing 'relative' field when reconfiguring floating window %d", 1076 wp->handle); 1077 } else { 1078 api_set_error(err, kErrorTypeValidation, "non-float cannot have '%s'", attribute); 1079 } 1080 } 1081 1082 /// Parses a border style name or custom (comma-separated) style. 1083 bool parse_winborder(WinConfig *fconfig, char *border_opt, Error *err) 1084 { 1085 if (!fconfig) { 1086 return false; 1087 } 1088 Object style = OBJECT_INIT; 1089 1090 if (strchr(border_opt, ',')) { 1091 Array border_chars = ARRAY_DICT_INIT; 1092 char *p = border_opt; 1093 char part[MAX_SCHAR_SIZE] = { 0 }; 1094 int count = 0; 1095 1096 while (*p != NUL) { 1097 if (count >= 8) { 1098 api_free_array(border_chars); 1099 return false; 1100 } 1101 1102 size_t part_len = copy_option_part(&p, part, sizeof(part), ","); 1103 if (part_len == 0 || part[0] == NUL) { 1104 api_free_array(border_chars); 1105 return false; 1106 } 1107 1108 String str = cstr_to_string(part); 1109 ADD(border_chars, STRING_OBJ(str)); 1110 count++; 1111 } 1112 1113 if (count != 8) { 1114 api_free_array(border_chars); 1115 return false; 1116 } 1117 1118 style = ARRAY_OBJ(border_chars); 1119 } else { 1120 style = CSTR_TO_OBJ(border_opt); 1121 } 1122 1123 parse_border_style(style, fconfig, err); 1124 api_free_object(style); 1125 return !ERROR_SET(err); 1126 } 1127 1128 static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fconfig, bool reconf, 1129 Error *err) 1130 { 1131 #define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key) 1132 bool has_relative = false, relative_is_win = false, is_split = false; 1133 if (config->relative.size > 0) { 1134 if (!parse_float_relative(config->relative, &fconfig->relative)) { 1135 api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key"); 1136 goto fail; 1137 } 1138 1139 if (config->relative.size > 0 && !(HAS_KEY_X(config, row) && HAS_KEY_X(config, col)) 1140 && !HAS_KEY_X(config, bufpos)) { 1141 api_set_error(err, kErrorTypeValidation, "'relative' requires 'row'/'col' or 'bufpos'"); 1142 goto fail; 1143 } 1144 1145 has_relative = true; 1146 fconfig->external = false; 1147 if (fconfig->relative == kFloatRelativeWindow) { 1148 relative_is_win = true; 1149 fconfig->bufpos.lnum = -1; 1150 } 1151 } else if (!config->external) { 1152 if (HAS_KEY_X(config, vertical) || HAS_KEY_X(config, split)) { 1153 is_split = true; 1154 } else if (wp == NULL) { // new win 1155 api_set_error(err, kErrorTypeValidation, 1156 "Must specify 'relative' or 'external' when creating a float"); 1157 goto fail; 1158 } 1159 } 1160 1161 if (HAS_KEY_X(config, vertical)) { 1162 if (!is_split) { 1163 api_set_error(err, kErrorTypeValidation, "floating windows cannot have 'vertical'"); 1164 goto fail; 1165 } 1166 } 1167 1168 if (HAS_KEY_X(config, split)) { 1169 if (!is_split) { 1170 api_set_error(err, kErrorTypeValidation, "floating windows cannot have 'split'"); 1171 goto fail; 1172 } 1173 if (!parse_config_split(config->split, &fconfig->split)) { 1174 api_set_error(err, kErrorTypeValidation, "Invalid value of 'split' key"); 1175 goto fail; 1176 } 1177 } 1178 1179 if (HAS_KEY_X(config, anchor)) { 1180 if (!parse_float_anchor(config->anchor, &fconfig->anchor)) { 1181 api_set_error(err, kErrorTypeValidation, "Invalid value of 'anchor' key"); 1182 goto fail; 1183 } 1184 } 1185 1186 if (HAS_KEY_X(config, row)) { 1187 if (!has_relative || is_split) { 1188 generate_api_error(wp, "row", err); 1189 goto fail; 1190 } 1191 fconfig->row = config->row; 1192 } 1193 1194 if (HAS_KEY_X(config, col)) { 1195 if (!has_relative || is_split) { 1196 generate_api_error(wp, "col", err); 1197 goto fail; 1198 } 1199 fconfig->col = config->col; 1200 } 1201 1202 if (HAS_KEY_X(config, bufpos)) { 1203 if (!has_relative || is_split) { 1204 generate_api_error(wp, "bufpos", err); 1205 goto fail; 1206 } else { 1207 if (!parse_float_bufpos(config->bufpos, &fconfig->bufpos)) { 1208 api_set_error(err, kErrorTypeValidation, "Invalid value of 'bufpos' key"); 1209 goto fail; 1210 } 1211 1212 if (!HAS_KEY_X(config, row)) { 1213 fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1; 1214 } 1215 if (!HAS_KEY_X(config, col)) { 1216 fconfig->col = 0; 1217 } 1218 } 1219 } 1220 1221 if (HAS_KEY_X(config, width)) { 1222 if (config->width > 0) { 1223 fconfig->width = (int)config->width; 1224 } else { 1225 api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer"); 1226 goto fail; 1227 } 1228 } else if (!reconf && !is_split) { 1229 api_set_error(err, kErrorTypeValidation, "Must specify 'width'"); 1230 goto fail; 1231 } 1232 1233 if (HAS_KEY_X(config, height)) { 1234 if (config->height > 0) { 1235 fconfig->height = (int)config->height; 1236 } else { 1237 api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer"); 1238 goto fail; 1239 } 1240 } else if (!reconf && !is_split) { 1241 api_set_error(err, kErrorTypeValidation, "Must specify 'height'"); 1242 goto fail; 1243 } 1244 1245 if (relative_is_win || is_split) { 1246 if (reconf && relative_is_win) { 1247 win_T *target_win = find_window_by_handle(config->win, err); 1248 if (!target_win) { 1249 goto fail; 1250 } 1251 1252 if (target_win == wp) { 1253 api_set_error(err, kErrorTypeException, "floating window cannot be relative to itself"); 1254 goto fail; 1255 } 1256 } 1257 fconfig->window = curwin->handle; 1258 if (HAS_KEY_X(config, win)) { 1259 if (config->win > 0) { 1260 fconfig->window = config->win; 1261 } 1262 } 1263 } else if (HAS_KEY_X(config, win)) { 1264 if (has_relative) { 1265 api_set_error(err, kErrorTypeValidation, 1266 "'win' key is only valid with relative='win' and relative=''"); 1267 goto fail; 1268 } else if (!is_split) { 1269 api_set_error(err, kErrorTypeValidation, 1270 "non-float with 'win' requires at least 'split' or 'vertical'"); 1271 goto fail; 1272 } 1273 } 1274 1275 if (HAS_KEY_X(config, external)) { 1276 fconfig->external = config->external; 1277 if (has_relative && fconfig->external) { 1278 api_set_error(err, kErrorTypeValidation, 1279 "Only one of 'relative' and 'external' must be used"); 1280 goto fail; 1281 } 1282 if (fconfig->external && !ui_has(kUIMultigrid)) { 1283 api_set_error(err, kErrorTypeValidation, "UI doesn't support external windows"); 1284 goto fail; 1285 } 1286 } 1287 1288 if (HAS_KEY_X(config, focusable)) { 1289 fconfig->focusable = config->focusable; 1290 fconfig->mouse = config->focusable; 1291 } 1292 1293 if (HAS_KEY_X(config, mouse)) { 1294 fconfig->mouse = config->mouse; 1295 } 1296 1297 if (HAS_KEY_X(config, zindex)) { 1298 if (is_split) { 1299 api_set_error(err, kErrorTypeValidation, "non-float cannot have 'zindex'"); 1300 goto fail; 1301 } 1302 if (config->zindex > 0) { 1303 fconfig->zindex = (int)config->zindex; 1304 } else { 1305 api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer"); 1306 goto fail; 1307 } 1308 } 1309 1310 if (HAS_KEY_X(config, title)) { 1311 if (is_split) { 1312 api_set_error(err, kErrorTypeValidation, "non-float cannot have 'title'"); 1313 goto fail; 1314 } 1315 1316 parse_bordertext(config->title, kBorderTextTitle, fconfig, err); 1317 if (ERROR_SET(err)) { 1318 goto fail; 1319 } 1320 1321 // handles unset 'title_pos' same as empty string 1322 if (!parse_bordertext_pos(wp, config->title_pos, kBorderTextTitle, fconfig, err)) { 1323 goto fail; 1324 } 1325 } else { 1326 if (HAS_KEY_X(config, title_pos)) { 1327 api_set_error(err, kErrorTypeException, "title_pos requires title to be set"); 1328 goto fail; 1329 } 1330 } 1331 1332 if (HAS_KEY_X(config, footer)) { 1333 if (is_split) { 1334 api_set_error(err, kErrorTypeValidation, "non-float cannot have 'footer'"); 1335 goto fail; 1336 } 1337 1338 parse_bordertext(config->footer, kBorderTextFooter, fconfig, err); 1339 if (ERROR_SET(err)) { 1340 goto fail; 1341 } 1342 1343 // handles unset 'footer_pos' same as empty string 1344 if (!parse_bordertext_pos(wp, config->footer_pos, kBorderTextFooter, fconfig, err)) { 1345 goto fail; 1346 } 1347 } else { 1348 if (HAS_KEY_X(config, footer_pos)) { 1349 api_set_error(err, kErrorTypeException, "footer_pos requires footer to be set"); 1350 goto fail; 1351 } 1352 } 1353 1354 Object border_style = OBJECT_INIT; 1355 if (HAS_KEY_X(config, border)) { 1356 if (is_split) { 1357 api_set_error(err, kErrorTypeValidation, "non-float cannot have 'border'"); 1358 goto fail; 1359 } 1360 border_style = config->border; 1361 if (border_style.type != kObjectTypeNil) { 1362 parse_border_style(border_style, fconfig, err); 1363 if (ERROR_SET(err)) { 1364 goto fail; 1365 } 1366 } 1367 } else if (*p_winborder != NUL && (wp == NULL || !wp->w_floating) 1368 && !parse_winborder(fconfig, p_winborder, err)) { 1369 goto fail; 1370 } 1371 1372 if (HAS_KEY_X(config, style)) { 1373 if (config->style.data[0] == NUL) { 1374 fconfig->style = kWinStyleUnused; 1375 } else if (striequal(config->style.data, "minimal")) { 1376 fconfig->style = kWinStyleMinimal; 1377 } else { 1378 api_set_error(err, kErrorTypeValidation, "Invalid value of 'style' key"); 1379 goto fail; 1380 } 1381 } 1382 1383 if (HAS_KEY_X(config, noautocmd)) { 1384 if (wp && config->noautocmd != fconfig->noautocmd) { 1385 api_set_error(err, kErrorTypeValidation, 1386 "'noautocmd' cannot be changed with existing windows"); 1387 goto fail; 1388 } 1389 fconfig->noautocmd = config->noautocmd; 1390 } 1391 1392 if (HAS_KEY_X(config, fixed)) { 1393 fconfig->fixed = config->fixed; 1394 } 1395 1396 if (HAS_KEY_X(config, hide)) { 1397 fconfig->hide = config->hide; 1398 } 1399 1400 if (HAS_KEY_X(config, _cmdline_offset)) { 1401 fconfig->_cmdline_offset = (int)config->_cmdline_offset; 1402 } 1403 1404 return true; 1405 1406 fail: 1407 merge_win_config(fconfig, wp != NULL ? wp->w_config : WIN_CONFIG_INIT); 1408 return false; 1409 #undef HAS_KEY_X 1410 }