window.c (17194B)
1 #include <stdbool.h> 2 #include <stdint.h> 3 #include <stdlib.h> 4 5 #include "nvim/api/keysets_defs.h" 6 #include "nvim/api/private/defs.h" 7 #include "nvim/api/private/dispatch.h" 8 #include "nvim/api/private/helpers.h" 9 #include "nvim/api/private/validate.h" 10 #include "nvim/api/window.h" 11 #include "nvim/autocmd.h" 12 #include "nvim/buffer_defs.h" 13 #include "nvim/cursor.h" 14 #include "nvim/drawscreen.h" 15 #include "nvim/errors.h" 16 #include "nvim/eval/window.h" 17 #include "nvim/ex_docmd.h" 18 #include "nvim/gettext_defs.h" 19 #include "nvim/globals.h" 20 #include "nvim/lua/executor.h" 21 #include "nvim/memory_defs.h" 22 #include "nvim/message.h" 23 #include "nvim/move.h" 24 #include "nvim/plines.h" 25 #include "nvim/pos_defs.h" 26 #include "nvim/types_defs.h" 27 #include "nvim/window.h" 28 29 #include "api/window.c.generated.h" // IWYU pragma: keep 30 31 /// Gets the current buffer in a window 32 /// 33 /// @param window |window-ID|, or 0 for current window 34 /// @param[out] err Error details, if any 35 /// @return Buffer id 36 Buffer nvim_win_get_buf(Window window, Error *err) 37 FUNC_API_SINCE(1) 38 { 39 win_T *win = find_window_by_handle(window, err); 40 41 if (!win) { 42 return 0; 43 } 44 45 return win->w_buffer->handle; 46 } 47 48 /// Sets the current buffer in a window, without side effects 49 /// 50 /// @param window |window-ID|, or 0 for current window 51 /// @param buffer Buffer id 52 /// @param[out] err Error details, if any 53 void nvim_win_set_buf(Window window, Buffer buffer, Error *err) 54 FUNC_API_SINCE(5) 55 FUNC_API_TEXTLOCK_ALLOW_CMDWIN 56 { 57 win_T *win = find_window_by_handle(window, err); 58 buf_T *buf = find_buffer_by_handle(buffer, err); 59 if (!win || !buf) { 60 return; 61 } 62 63 if (win == cmdwin_win || win == cmdwin_old_curwin || buf == cmdwin_buf) { 64 api_set_error(err, kErrorTypeException, "%s", e_cmdwin); 65 return; 66 } 67 win_set_buf(win, buf, err); 68 } 69 70 /// Gets the (1,0)-indexed, buffer-relative cursor position for a given window 71 /// (different windows showing the same buffer have independent cursor 72 /// positions). |api-indexing| 73 /// 74 /// @see |getcurpos()| 75 /// 76 /// @param window |window-ID|, or 0 for current window 77 /// @param[out] err Error details, if any 78 /// @return (row, col) tuple 79 ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Arena *arena, Error *err) 80 FUNC_API_SINCE(1) 81 { 82 Array rv = ARRAY_DICT_INIT; 83 win_T *win = find_window_by_handle(window, err); 84 85 if (win) { 86 rv = arena_array(arena, 2); 87 ADD_C(rv, INTEGER_OBJ(win->w_cursor.lnum)); 88 ADD_C(rv, INTEGER_OBJ(win->w_cursor.col)); 89 } 90 91 return rv; 92 } 93 94 /// Sets the (1,0)-indexed cursor position in the window. |api-indexing| 95 /// This scrolls the window even if it is not the current one. 96 /// 97 /// @param window |window-ID|, or 0 for current window 98 /// @param pos (row, col) tuple representing the new position 99 /// @param[out] err Error details, if any 100 void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) 101 FUNC_API_SINCE(1) 102 { 103 win_T *win = find_window_by_handle(window, err); 104 105 if (!win) { 106 return; 107 } 108 109 if (pos.size != 2 || pos.items[0].type != kObjectTypeInteger 110 || pos.items[1].type != kObjectTypeInteger) { 111 api_set_error(err, 112 kErrorTypeValidation, 113 "Argument \"pos\" must be a [row, col] array"); 114 return; 115 } 116 117 int64_t row = pos.items[0].data.integer; 118 int64_t col = pos.items[1].data.integer; 119 120 if (row <= 0 || row > win->w_buffer->b_ml.ml_line_count) { 121 api_set_error(err, kErrorTypeValidation, "Cursor position outside buffer"); 122 return; 123 } 124 125 if (col > MAXCOL || col < 0) { 126 api_set_error(err, kErrorTypeValidation, "Column value outside range"); 127 return; 128 } 129 130 win->w_cursor.lnum = (linenr_T)row; 131 win->w_cursor.col = (colnr_T)col; 132 win->w_cursor.coladd = 0; 133 // When column is out of range silently correct it. 134 check_cursor_col(win); 135 136 // Make sure we stick in this column. 137 win->w_set_curswant = true; 138 139 // make sure cursor is in visible range and 140 // cursorcolumn and cursorline are updated even if win != curwin 141 switchwin_T switchwin; 142 switch_win(&switchwin, win, NULL, true); 143 update_topline(curwin); 144 validate_cursor(curwin); 145 restore_win(&switchwin, true); 146 147 redraw_later(win, UPD_VALID); 148 win->w_redr_status = true; 149 } 150 151 /// Gets the window height 152 /// 153 /// @param window |window-ID|, or 0 for current window 154 /// @param[out] err Error details, if any 155 /// @return Height as a count of rows 156 Integer nvim_win_get_height(Window window, Error *err) 157 FUNC_API_SINCE(1) 158 { 159 win_T *win = find_window_by_handle(window, err); 160 161 if (!win) { 162 return 0; 163 } 164 165 return win->w_height; 166 } 167 168 /// Sets the window height. 169 /// 170 /// @param window |window-ID|, or 0 for current window 171 /// @param height Height as a count of rows 172 /// @param[out] err Error details, if any 173 void nvim_win_set_height(Window window, Integer height, Error *err) 174 FUNC_API_SINCE(1) 175 { 176 win_T *win = find_window_by_handle(window, err); 177 178 if (!win) { 179 return; 180 } 181 182 TRY_WRAP(err, { 183 win_setheight_win((int)height, win); 184 }); 185 } 186 187 /// Gets the window width 188 /// 189 /// @param window |window-ID|, or 0 for current window 190 /// @param[out] err Error details, if any 191 /// @return Width as a count of columns 192 Integer nvim_win_get_width(Window window, Error *err) 193 FUNC_API_SINCE(1) 194 { 195 win_T *win = find_window_by_handle(window, err); 196 197 if (!win) { 198 return 0; 199 } 200 201 return win->w_width; 202 } 203 204 /// Sets the window width. This will only succeed if the screen is split 205 /// vertically. 206 /// 207 /// @param window |window-ID|, or 0 for current window 208 /// @param width Width as a count of columns 209 /// @param[out] err Error details, if any 210 void nvim_win_set_width(Window window, Integer width, Error *err) 211 FUNC_API_SINCE(1) 212 { 213 win_T *win = find_window_by_handle(window, err); 214 215 if (!win) { 216 return; 217 } 218 219 TRY_WRAP(err, { 220 win_setwidth_win((int)width, win); 221 }); 222 } 223 224 /// Gets a window-scoped (w:) variable 225 /// 226 /// @param window |window-ID|, or 0 for current window 227 /// @param name Variable name 228 /// @param[out] err Error details, if any 229 /// @return Variable value 230 Object nvim_win_get_var(Window window, String name, Arena *arena, Error *err) 231 FUNC_API_SINCE(1) 232 { 233 win_T *win = find_window_by_handle(window, err); 234 235 if (!win) { 236 return (Object)OBJECT_INIT; 237 } 238 239 return dict_get_value(win->w_vars, name, arena, err); 240 } 241 242 /// Sets a window-scoped (w:) variable 243 /// 244 /// @param window |window-ID|, or 0 for current window 245 /// @param name Variable name 246 /// @param value Variable value 247 /// @param[out] err Error details, if any 248 void nvim_win_set_var(Window window, String name, Object value, Error *err) 249 FUNC_API_SINCE(1) 250 { 251 win_T *win = find_window_by_handle(window, err); 252 253 if (!win) { 254 return; 255 } 256 257 dict_set_var(win->w_vars, name, value, false, false, NULL, err); 258 } 259 260 /// Removes a window-scoped (w:) variable 261 /// 262 /// @param window |window-ID|, or 0 for current window 263 /// @param name Variable name 264 /// @param[out] err Error details, if any 265 void nvim_win_del_var(Window window, String name, Error *err) 266 FUNC_API_SINCE(1) 267 { 268 win_T *win = find_window_by_handle(window, err); 269 270 if (!win) { 271 return; 272 } 273 274 dict_set_var(win->w_vars, name, NIL, true, false, NULL, err); 275 } 276 277 /// Gets the window position in display cells. First position is zero. 278 /// 279 /// @param window |window-ID|, or 0 for current window 280 /// @param[out] err Error details, if any 281 /// @return (row, col) tuple with the window position 282 ArrayOf(Integer, 2) nvim_win_get_position(Window window, Arena *arena, Error *err) 283 FUNC_API_SINCE(1) 284 { 285 Array rv = ARRAY_DICT_INIT; 286 win_T *win = find_window_by_handle(window, err); 287 288 if (win) { 289 rv = arena_array(arena, 2); 290 ADD_C(rv, INTEGER_OBJ(win->w_winrow)); 291 ADD_C(rv, INTEGER_OBJ(win->w_wincol)); 292 } 293 294 return rv; 295 } 296 297 /// Gets the window tabpage 298 /// 299 /// @param window |window-ID|, or 0 for current window 300 /// @param[out] err Error details, if any 301 /// @return Tabpage that contains the window 302 Tabpage nvim_win_get_tabpage(Window window, Error *err) 303 FUNC_API_SINCE(1) 304 { 305 Tabpage rv = 0; 306 win_T *win = find_window_by_handle(window, err); 307 308 if (win) { 309 rv = win_find_tabpage(win)->handle; 310 } 311 312 return rv; 313 } 314 315 /// Gets the window number 316 /// 317 /// @param window |window-ID|, or 0 for current window 318 /// @param[out] err Error details, if any 319 /// @return Window number 320 Integer nvim_win_get_number(Window window, Error *err) 321 FUNC_API_SINCE(1) 322 { 323 int rv = 0; 324 win_T *win = find_window_by_handle(window, err); 325 326 if (!win) { 327 return rv; 328 } 329 330 int tabnr; 331 win_get_tabwin(win->handle, &tabnr, &rv); 332 333 return rv; 334 } 335 336 /// Checks if a window is valid 337 /// 338 /// @param window |window-ID|, or 0 for current window 339 /// @return true if the window is valid, false otherwise 340 Boolean nvim_win_is_valid(Window window) 341 FUNC_API_SINCE(1) 342 { 343 Error stub = ERROR_INIT; 344 Boolean ret = find_window_by_handle(window, &stub) != NULL; 345 api_clear_error(&stub); 346 return ret; 347 } 348 349 /// Closes the window and hide the buffer it contains (like |:hide| with a 350 /// |window-ID|). 351 /// 352 /// Like |:hide| the buffer becomes hidden unless another window is editing it, 353 /// or 'bufhidden' is `unload`, `delete` or `wipe` as opposed to |:close| or 354 /// |nvim_win_close()|, which will close the buffer. 355 /// 356 /// @param window |window-ID|, or 0 for current window 357 /// @param[out] err Error details, if any 358 void nvim_win_hide(Window window, Error *err) 359 FUNC_API_SINCE(7) 360 FUNC_API_TEXTLOCK_ALLOW_CMDWIN 361 { 362 win_T *win = find_window_by_handle(window, err); 363 if (!win || !can_close_in_cmdwin(win, err)) { 364 return; 365 } 366 367 tabpage_T *tabpage = win_find_tabpage(win); 368 TRY_WRAP(err, { 369 // Never close the autocommand window. 370 if (is_aucmd_win(win)) { 371 emsg(_(e_autocmd_close)); 372 } else if (tabpage == curtab) { 373 win_close(win, false, false); 374 } else { 375 win_close_othertab(win, false, tabpage, false); 376 } 377 }); 378 } 379 380 /// Closes the window (like |:close| with a |window-ID|). 381 /// 382 /// @param window |window-ID|, or 0 for current window 383 /// @param force Behave like `:close!` The last window of a buffer with 384 /// unwritten changes can be closed. The buffer will become 385 /// hidden, even if 'hidden' is not set. 386 /// @param[out] err Error details, if any 387 void nvim_win_close(Window window, Boolean force, Error *err) 388 FUNC_API_SINCE(6) 389 FUNC_API_TEXTLOCK_ALLOW_CMDWIN 390 { 391 win_T *win = find_window_by_handle(window, err); 392 if (!win || !can_close_in_cmdwin(win, err)) { 393 return; 394 } 395 396 tabpage_T *tabpage = win_find_tabpage(win); 397 TRY_WRAP(err, { 398 ex_win_close(force, win, tabpage == curtab ? NULL : tabpage); 399 }); 400 } 401 402 /// Calls a function with window as temporary current window. 403 /// 404 /// @see |win_execute()| 405 /// @see |nvim_buf_call()| 406 /// 407 /// @param window |window-ID|, or 0 for current window 408 /// @param fun Function to call inside the window (currently Lua callable 409 /// only) 410 /// @param[out] err Error details, if any 411 /// @return Return value of function. 412 Object nvim_win_call(Window window, LuaRef fun, Error *err) 413 FUNC_API_SINCE(7) 414 FUNC_API_LUA_ONLY 415 { 416 win_T *win = find_window_by_handle(window, err); 417 if (!win) { 418 return NIL; 419 } 420 tabpage_T *tabpage = win_find_tabpage(win); 421 422 Object res = OBJECT_INIT; 423 TRY_WRAP(err, { 424 win_execute_T win_execute_args; 425 if (win_execute_before(&win_execute_args, win, tabpage)) { 426 Array args = ARRAY_DICT_INIT; 427 res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err); 428 } 429 win_execute_after(&win_execute_args); 430 }); 431 return res; 432 } 433 434 /// Set highlight namespace for a window. This will use highlights defined with 435 /// |nvim_set_hl()| for this namespace, but fall back to global highlights (ns=0) when 436 /// missing. 437 /// 438 /// This takes precedence over the 'winhighlight' option. 439 /// 440 /// @param window 441 /// @param ns_id the namespace to use 442 /// @param[out] err Error details, if any 443 void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err) 444 FUNC_API_SINCE(10) 445 { 446 win_T *win = find_window_by_handle(window, err); 447 if (!win) { 448 return; 449 } 450 451 // -1 is allowed as inherit global namespace 452 if (ns_id < -1) { 453 api_set_error(err, kErrorTypeValidation, "no such namespace"); 454 } 455 456 win->w_ns_hl = (NS)ns_id; 457 win->w_hl_needs_update = true; 458 redraw_later(win, UPD_NOT_VALID); 459 } 460 461 /// Computes the number of screen lines occupied by a range of text in a given window. 462 /// Works for off-screen text and takes folds into account. 463 /// 464 /// Diff filler or virtual lines above a line are counted as a part of that line, 465 /// unless the line is on "start_row" and "start_vcol" is specified. 466 /// 467 /// Diff filler or virtual lines below the last buffer line are counted in the result 468 /// when "end_row" is omitted. 469 /// 470 /// Line indexing is similar to |nvim_buf_get_text()|. 471 /// 472 /// @param window |window-ID|, or 0 for current window. 473 /// @param opts Optional parameters: 474 /// - start_row: Starting line index, 0-based inclusive. 475 /// When omitted start at the very top. 476 /// - end_row: Ending line index, 0-based inclusive. 477 /// When omitted end at the very bottom. 478 /// - start_vcol: Starting virtual column index on "start_row", 479 /// 0-based inclusive, rounded down to full screen lines. 480 /// When omitted include the whole line. 481 /// - end_vcol: Ending virtual column index on "end_row", 482 /// 0-based exclusive, rounded up to full screen lines. 483 /// When 0 only include diff filler and virtual lines above 484 /// "end_row". When omitted include the whole line. 485 /// - max_height: Don't add the height of lines below the row 486 /// for which this height is reached. Useful to e.g. limit the 487 /// height to the window height, avoiding unnecessary work. Or 488 /// to find out how many buffer lines beyond "start_row" take 489 /// up a certain number of logical lines (returned in 490 /// "end_row" and "end_vcol"). 491 /// @return Dict containing text height information, with these keys: 492 /// - all: The total number of screen lines occupied by the range. 493 /// - fill: The number of diff filler or virtual lines among them. 494 /// - end_row: The row on which the returned height is reached (first row of 495 /// a closed fold). 496 /// - end_vcol: Ending virtual column in "end_row" where "max_height" or the returned 497 /// height is reached. 0 if "end_row" is a closed fold. 498 /// 499 /// @see |virtcol()| for text width. 500 DictAs(win_text_height_ret) nvim_win_text_height(Window window, Dict(win_text_height) *opts, 501 Arena *arena, Error *err) 502 FUNC_API_SINCE(12) 503 { 504 Dict rv = arena_dict(arena, 2); 505 506 win_T *const win = find_window_by_handle(window, err); 507 if (!win) { 508 return rv; 509 } 510 buf_T *const buf = win->w_buffer; 511 const linenr_T line_count = buf->b_ml.ml_line_count; 512 513 linenr_T start_lnum = 1; 514 linenr_T end_lnum = line_count; 515 int64_t start_vcol = -1; 516 int64_t end_vcol = -1; 517 518 bool oob = false; 519 520 if (HAS_KEY(opts, win_text_height, start_row)) { 521 start_lnum = (linenr_T)normalize_index(buf, opts->start_row, false, &oob); 522 } 523 524 if (HAS_KEY(opts, win_text_height, end_row)) { 525 end_lnum = (linenr_T)normalize_index(buf, opts->end_row, false, &oob); 526 } 527 528 VALIDATE(!oob, "%s", "Line index out of bounds", { 529 return rv; 530 }); 531 VALIDATE((start_lnum <= end_lnum), "%s", "'start_row' is higher than 'end_row'", { 532 return rv; 533 }); 534 535 if (HAS_KEY(opts, win_text_height, start_vcol)) { 536 VALIDATE(HAS_KEY(opts, win_text_height, start_row), 537 "%s", "'start_vcol' specified without 'start_row'", { 538 return rv; 539 }); 540 start_vcol = opts->start_vcol; 541 VALIDATE_RANGE((start_vcol >= 0 && start_vcol <= MAXCOL), "start_vcol", { 542 return rv; 543 }); 544 } 545 546 if (HAS_KEY(opts, win_text_height, end_vcol)) { 547 VALIDATE(HAS_KEY(opts, win_text_height, end_row), 548 "%s", "'end_vcol' specified without 'end_row'", { 549 return rv; 550 }); 551 end_vcol = opts->end_vcol; 552 VALIDATE_RANGE((end_vcol >= 0 && end_vcol <= MAXCOL), "end_vcol", { 553 return rv; 554 }); 555 } 556 557 int64_t max = INT64_MAX; 558 if (HAS_KEY(opts, win_text_height, max_height)) { 559 VALIDATE_RANGE(opts->max_height > 0, "max_height", { 560 return rv; 561 }); 562 max = opts->max_height; 563 } 564 565 if (start_lnum == end_lnum && start_vcol >= 0 && end_vcol >= 0) { 566 VALIDATE((start_vcol <= end_vcol), "%s", "'start_vcol' is higher than 'end_vcol'", { 567 return rv; 568 }); 569 } 570 571 int64_t fill = 0; 572 int64_t all = win_text_height(win, start_lnum, start_vcol, &end_lnum, &end_vcol, &fill, max); 573 if (!HAS_KEY(opts, win_text_height, end_row)) { 574 const int64_t end_fill = win_get_fill(win, line_count + 1); 575 fill += end_fill; 576 all += end_fill; 577 } 578 PUT_C(rv, "all", INTEGER_OBJ(all)); 579 PUT_C(rv, "fill", INTEGER_OBJ(fill)); 580 PUT_C(rv, "end_row", INTEGER_OBJ(end_lnum - 1)); 581 PUT_C(rv, "end_vcol", INTEGER_OBJ(end_vcol)); 582 return rv; 583 }