neovim

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

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 }