neovim

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

winfloat.c (13781B)


      1 #include <assert.h>
      2 #include <stdbool.h>
      3 #include <stdlib.h>
      4 #include <string.h>
      5 
      6 #include "klib/kvec.h"
      7 #include "nvim/api/private/defs.h"
      8 #include "nvim/api/private/helpers.h"
      9 #include "nvim/api/vim.h"
     10 #include "nvim/ascii_defs.h"
     11 #include "nvim/autocmd.h"
     12 #include "nvim/buffer_defs.h"
     13 #include "nvim/drawscreen.h"
     14 #include "nvim/errors.h"
     15 #include "nvim/globals.h"
     16 #include "nvim/grid.h"
     17 #include "nvim/grid_defs.h"
     18 #include "nvim/macros_defs.h"
     19 #include "nvim/memory.h"
     20 #include "nvim/message.h"
     21 #include "nvim/mouse.h"
     22 #include "nvim/move.h"
     23 #include "nvim/option.h"
     24 #include "nvim/option_defs.h"
     25 #include "nvim/option_vars.h"
     26 #include "nvim/optionstr.h"
     27 #include "nvim/pos_defs.h"
     28 #include "nvim/strings.h"
     29 #include "nvim/types_defs.h"
     30 #include "nvim/ui.h"
     31 #include "nvim/ui_defs.h"
     32 #include "nvim/vim_defs.h"
     33 #include "nvim/window.h"
     34 #include "nvim/winfloat.h"
     35 
     36 #include "winfloat.c.generated.h"
     37 
     38 /// Create a new float.
     39 ///
     40 /// @param wp      if NULL, allocate a new window, otherwise turn existing window into a float.
     41 ///                It must then already belong to the current tabpage!
     42 /// @param last    make the window the last one in the window list.
     43 ///                Only used when allocating the autocommand window.
     44 /// @param config  must already have been validated!
     45 win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err)
     46 {
     47  if (wp == NULL) {
     48    tabpage_T *tp = NULL;
     49    win_T *tp_last = last ? lastwin : lastwin_nofloating();
     50    if (fconfig.window != 0) {
     51      assert(!last);
     52      win_T *parent_wp = find_window_by_handle(fconfig.window, err);
     53      if (!parent_wp) {
     54        return NULL;
     55      }
     56      tp = win_find_tabpage(parent_wp);
     57      if (!tp) {
     58        return NULL;
     59      }
     60      tp_last = tp == curtab ? lastwin : tp->tp_lastwin;
     61      while (tp_last->w_floating && tp_last->w_prev) {
     62        tp_last = tp_last->w_prev;
     63      }
     64    }
     65    wp = win_alloc(tp_last, false);
     66    win_init(wp, curwin, 0);
     67    if (wp->w_p_wbr != NULL && fconfig.height == 1) {
     68      if (wp->w_p_wbr != empty_string_option) {
     69        free_string_option(wp->w_p_wbr);
     70      }
     71      wp->w_p_wbr = empty_string_option;
     72    }
     73    if (wp->w_p_stl && wp->w_p_stl != empty_string_option) {
     74      free_string_option(wp->w_p_stl);
     75      wp->w_p_stl = empty_string_option;
     76    }
     77  } else {
     78    assert(!last);
     79    assert(!wp->w_floating);
     80    if (firstwin == wp && lastwin_nofloating() == wp) {
     81      // last non-float
     82      api_set_error(err, kErrorTypeException,
     83                    "Cannot change last window into float");
     84      return NULL;
     85    } else if (!win_valid(wp)) {
     86      api_set_error(err, kErrorTypeException,
     87                    "Cannot change window from different tabpage into float");
     88      return NULL;
     89    } else if (cmdwin_win != NULL && !cmdwin_win->w_floating) {
     90      // cmdwin can't become the only non-float. Check for others.
     91      bool other_nonfloat = false;
     92      for (win_T *wp2 = firstwin; wp2 != NULL && !wp2->w_floating; wp2 = wp2->w_next) {
     93        if (wp2 != wp && wp2 != cmdwin_win) {
     94          other_nonfloat = true;
     95          break;
     96        }
     97      }
     98      if (!other_nonfloat) {
     99        api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
    100        return NULL;
    101      }
    102    }
    103    int dir;
    104    winframe_remove(wp, &dir, NULL, NULL);
    105    XFREE_CLEAR(wp->w_frame);
    106    win_comp_pos();  // recompute window positions
    107    win_remove(wp, NULL);
    108    win_append(lastwin_nofloating(), wp, NULL);
    109  }
    110  wp->w_floating = true;
    111  wp->w_status_height = wp->w_p_stl && *wp->w_p_stl != NUL
    112                        && (p_ls == 1 || p_ls == 2) ? STATUS_HEIGHT : 0;
    113  wp->w_winbar_height = 0;
    114  wp->w_hsep_height = 0;
    115  wp->w_vsep_width = 0;
    116 
    117  win_config_float(wp, fconfig);
    118  redraw_later(wp, UPD_VALID);
    119  return wp;
    120 }
    121 
    122 void win_set_minimal_style(win_T *wp)
    123 {
    124  wp->w_p_nu = false;
    125  wp->w_p_rnu = false;
    126  wp->w_p_cul = false;
    127  wp->w_p_cuc = false;
    128  wp->w_p_spell = false;
    129  wp->w_p_list = false;
    130 
    131  // Hide EOB region: use " " fillchar and cleared highlighting
    132  if (wp->w_p_fcs_chars.eob != ' ') {
    133    char *old = wp->w_p_fcs;
    134    wp->w_p_fcs = ((*old == NUL)
    135                   ? xstrdup("eob: ")
    136                   : concat_str(old, ",eob: "));
    137    free_string_option(old);
    138  }
    139 
    140  // TODO(bfredl): this could use a highlight namespace directly,
    141  // and avoid peculiarities around window options
    142  char *old = wp->w_p_winhl;
    143  wp->w_p_winhl = ((*old == NUL)
    144                   ? xstrdup("EndOfBuffer:")
    145                   : concat_str(old, ",EndOfBuffer:"));
    146  free_string_option(old);
    147  parse_winhl_opt(NULL, wp);
    148 
    149  // signcolumn: use 'auto'
    150  if (wp->w_p_scl[0] != 'a' || strlen(wp->w_p_scl) >= 8) {
    151    free_string_option(wp->w_p_scl);
    152    wp->w_p_scl = xstrdup("auto");
    153  }
    154 
    155  // foldcolumn: use '0'
    156  if (wp->w_p_fdc[0] != '0') {
    157    free_string_option(wp->w_p_fdc);
    158    wp->w_p_fdc = xstrdup("0");
    159  }
    160 
    161  // colorcolumn: cleared
    162  if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) {
    163    free_string_option(wp->w_p_cc);
    164    wp->w_p_cc = xstrdup("");
    165  }
    166 
    167  // statuscolumn: cleared
    168  if (wp->w_p_stc != NULL && *wp->w_p_stc != NUL) {
    169    free_string_option(wp->w_p_stc);
    170    wp->w_p_stc = empty_string_option;
    171  }
    172 
    173  // statusline: cleared (for floating windows)
    174  if (wp->w_floating && wp->w_p_stl != NULL && *wp->w_p_stl != NUL) {
    175    free_string_option(wp->w_p_stl);
    176    wp->w_p_stl = empty_string_option;
    177    if (wp->w_status_height > 0) {
    178      win_config_float(wp, wp->w_config);
    179    }
    180  }
    181 }
    182 
    183 int win_border_height(win_T *wp)
    184 {
    185  return wp->w_border_adj[0] + wp->w_border_adj[2];
    186 }
    187 
    188 int win_border_width(win_T *wp)
    189 {
    190  return wp->w_border_adj[1] + wp->w_border_adj[3];
    191 }
    192 
    193 void win_config_float(win_T *wp, WinConfig fconfig)
    194 {
    195  // Process statusline changes before applying new height from config
    196  bool show_stl = *wp->w_p_stl != NUL && (p_ls == 1 || p_ls == 2);
    197  if (wp->w_status_height && !show_stl) {
    198    win_remove_status_line(wp, false);
    199  } else if (wp->w_status_height == 0 && show_stl) {
    200    wp->w_status_height = STATUS_HEIGHT;
    201  }
    202 
    203  wp->w_width = MAX(fconfig.width, 1);
    204  wp->w_height = MAX(fconfig.height, 1);
    205 
    206  if (fconfig.relative == kFloatRelativeCursor) {
    207    fconfig.relative = kFloatRelativeWindow;
    208    fconfig.row += curwin->w_wrow;
    209    fconfig.col += curwin->w_wcol;
    210    fconfig.window = curwin->handle;
    211  } else if (fconfig.relative == kFloatRelativeMouse) {
    212    int row = mouse_row;
    213    int col = mouse_col;
    214    int grid = mouse_grid;
    215    win_T *mouse_win = mouse_find_win_inner(&grid, &row, &col);
    216    if (mouse_win != NULL) {
    217      fconfig.relative = kFloatRelativeWindow;
    218      fconfig.row += row;
    219      fconfig.col += col;
    220      fconfig.window = mouse_win->handle;
    221    }
    222  }
    223 
    224  bool change_external = fconfig.external != wp->w_config.external;
    225  bool change_border = (fconfig.border != wp->w_config.border
    226                        || memcmp(fconfig.border_hl_ids,
    227                                  wp->w_config.border_hl_ids,
    228                                  sizeof fconfig.border_hl_ids) != 0);
    229 
    230  merge_win_config(&wp->w_config, fconfig);
    231 
    232  bool has_border = wp->w_floating && wp->w_config.border;
    233  for (int i = 0; i < 4; i++) {
    234    int new_adj = has_border && wp->w_config.border_chars[2 * i + 1][0];
    235    if (new_adj != wp->w_border_adj[i]) {
    236      change_border = true;
    237      wp->w_border_adj[i] = new_adj;
    238    }
    239  }
    240 
    241  if (!ui_has(kUIMultigrid)) {
    242    int above_ch = wp->w_config.zindex < kZIndexMessages ? (int)p_ch : 0;
    243    wp->w_height = MIN(wp->w_height, Rows - win_border_height(wp) - above_ch);
    244    wp->w_width = MIN(wp->w_width, Columns - win_border_width(wp));
    245  }
    246 
    247  win_set_inner_size(wp, true);
    248  set_must_redraw(UPD_VALID);
    249  wp->w_redr_status = wp->w_status_height;
    250  wp->w_pos_changed = true;
    251  if (change_external || change_border) {
    252    wp->w_hl_needs_update = true;
    253    redraw_later(wp, UPD_NOT_VALID);
    254  }
    255 
    256  // compute initial position
    257  if (wp->w_config.relative == kFloatRelativeWindow) {
    258    int row = (int)wp->w_config.row;
    259    int col = (int)wp->w_config.col;
    260    Error dummy = ERROR_INIT;
    261    win_T *parent = find_window_by_handle(wp->w_config.window, &dummy);
    262    if (parent) {
    263      row += parent->w_winrow;
    264      col += parent->w_wincol;
    265      grid_adjust(&parent->w_grid, &row, &col);
    266      if (wp->w_config.bufpos.lnum >= 0) {
    267        pos_T pos = { MIN(wp->w_config.bufpos.lnum + 1, parent->w_buffer->b_ml.ml_line_count),
    268                      wp->w_config.bufpos.col, 0 };
    269        int trow, tcol, tcolc, tcole;
    270        textpos2screenpos(parent, &pos, &trow, &tcol, &tcolc, &tcole, true);
    271        row += trow - 1;
    272        col += tcol - 1;
    273      }
    274    }
    275    api_clear_error(&dummy);
    276    wp->w_winrow = row;
    277    wp->w_wincol = col;
    278  } else {
    279    wp->w_winrow = (int)fconfig.row;
    280    wp->w_wincol = (int)fconfig.col;
    281  }
    282 
    283  // changing border style while keeping border only requires redrawing border
    284  if (fconfig.border) {
    285    wp->w_redr_border = true;
    286    redraw_later(wp, UPD_VALID);
    287  }
    288 }
    289 
    290 static int float_zindex_cmp(const void *a, const void *b)
    291 {
    292  int za = (*(win_T **)a)->w_config.zindex;
    293  int zb = (*(win_T **)b)->w_config.zindex;
    294  return za == zb ? 0 : za < zb ? 1 : -1;
    295 }
    296 
    297 void win_float_remove(bool bang, int count)
    298 {
    299  kvec_t(win_T *) float_win_arr = KV_INITIAL_VALUE;
    300  for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
    301    kv_push(float_win_arr, wp);
    302  }
    303  if (float_win_arr.size > 0) {
    304    qsort(float_win_arr.items, float_win_arr.size, sizeof(win_T *), float_zindex_cmp);
    305  }
    306  for (size_t i = 0; i < float_win_arr.size; i++) {
    307    win_T *wp = float_win_arr.items[i];
    308    if (win_valid(wp) && win_close(wp, false, false) == FAIL) {
    309      break;
    310    }
    311    if (!bang) {
    312      count--;
    313      if (count == 0) {
    314        break;
    315      }
    316    }
    317  }
    318  kv_destroy(float_win_arr);
    319 }
    320 
    321 void win_check_anchored_floats(win_T *win)
    322 {
    323  for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
    324    // float might be anchored to moved window
    325    if (wp->w_config.relative == kFloatRelativeWindow
    326        && wp->w_config.window == win->handle) {
    327      wp->w_pos_changed = true;
    328    }
    329  }
    330 }
    331 
    332 void win_float_update_statusline(void)
    333 {
    334  for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
    335    bool has_status = wp->w_status_height > 0;
    336    bool should_show = *wp->w_p_stl != NUL && (p_ls == 1 || p_ls == 2);
    337    if (should_show != has_status) {
    338      win_config_float(wp, wp->w_config);
    339    }
    340  }
    341 }
    342 
    343 void win_float_anchor_laststatus(void)
    344 {
    345  FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
    346    if (win->w_config.relative == kFloatRelativeLaststatus) {
    347      win->w_pos_changed = true;
    348    }
    349  }
    350 }
    351 
    352 void win_reconfig_floats(void)
    353 {
    354  for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
    355    win_config_float(wp, wp->w_config);
    356  }
    357 }
    358 
    359 /// Return true if "win" is floating window in the current tab page.
    360 ///
    361 /// @param  win  window to check
    362 bool win_float_valid(const win_T *win)
    363  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
    364 {
    365  if (win == NULL) {
    366    return false;
    367  }
    368 
    369  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
    370    if (wp == win) {
    371      return wp->w_floating;
    372    }
    373  }
    374  return false;
    375 }
    376 
    377 win_T *win_float_find_preview(void)
    378 {
    379  for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
    380    if (wp->w_float_is_info) {
    381      return wp;
    382    }
    383  }
    384  return NULL;
    385 }
    386 
    387 /// Select an alternative window to `win` (assumed floating) in tabpage `tp`.
    388 ///
    389 /// Useful for finding a window to switch to if `win` is the current window, but is then closed or
    390 /// moved to a different tabpage.
    391 ///
    392 /// @param  tp  `win`'s original tabpage, or NULL for current.
    393 win_T *win_float_find_altwin(const win_T *win, const tabpage_T *tp)
    394  FUNC_ATTR_NONNULL_ARG(1)
    395 {
    396  win_T *wp = prevwin;
    397  if (tp == NULL) {
    398    return (win_valid(wp) && wp != win && wp->w_config.focusable
    399            && !wp->w_config.hide) ? wp : firstwin;
    400  }
    401 
    402  assert(tp != curtab);
    403  wp = tabpage_win_valid(tp, tp->tp_prevwin) ? tp->tp_prevwin : tp->tp_firstwin;
    404  return (wp->w_config.focusable && !wp->w_config.hide) ? wp : tp->tp_firstwin;
    405 }
    406 
    407 /// Inline helper function for handling errors and cleanup in win_float_create_preview.
    408 static inline win_T *handle_error_and_cleanup(win_T *wp, Error *err)
    409 {
    410  if (ERROR_SET(err)) {
    411    emsg(err->msg);
    412    api_clear_error(err);
    413  }
    414  if (wp) {
    415    win_remove(wp, NULL);
    416    win_free(wp, NULL);
    417  }
    418  unblock_autocmds();
    419  return NULL;
    420 }
    421 
    422 /// create a floating preview window.
    423 ///
    424 /// @param[in] bool enter floating window.
    425 /// @param[in] bool create a new buffer for window.
    426 ///
    427 /// @return win_T
    428 win_T *win_float_create_preview(bool enter, bool new_buf)
    429 {
    430  WinConfig config = WIN_CONFIG_INIT;
    431  config.col = curwin->w_wcol;
    432  config.row = curwin->w_wrow;
    433  config.relative = kFloatRelativeEditor;
    434  config.focusable = false;
    435  config.mouse = false;
    436  config.anchor = 0;  // NW
    437  config.noautocmd = true;
    438  config.hide = true;
    439  config.style = kWinStyleMinimal;
    440  Error err = ERROR_INIT;
    441 
    442  block_autocmds();
    443  win_T *wp = win_new_float(NULL, false, config, &err);
    444  if (!wp) {
    445    return handle_error_and_cleanup(wp, &err);
    446  }
    447 
    448  if (new_buf) {
    449    Buffer b = nvim_create_buf(false, true, &err);
    450    if (!b) {
    451      return handle_error_and_cleanup(wp, &err);
    452    }
    453    buf_T *buf = find_buffer_by_handle(b, &err);
    454    if (!buf) {
    455      return handle_error_and_cleanup(wp, &err);
    456    }
    457    buf->b_p_bl = false;  // unlist
    458    set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("wipe"), OPT_LOCAL, 0,
    459                          kOptScopeBuf, buf);
    460    win_set_buf(wp, buf, &err);
    461    if (ERROR_SET(&err)) {
    462      return handle_error_and_cleanup(wp, &err);
    463    }
    464  }
    465  unblock_autocmds();
    466  wp->w_p_diff = false;
    467  wp->w_float_is_info = true;
    468  wp->w_p_wrap = true;  // 'wrap' is default on
    469  wp->w_p_so = 0;       // 'scrolloff' zero
    470  if (enter) {
    471    win_enter(wp, false);
    472  }
    473  return wp;
    474 }