neovim

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

cursor.c (15295B)


      1 #include <assert.h>
      2 #include <inttypes.h>
      3 #include <stdbool.h>
      4 #include <string.h>
      5 
      6 #include "nvim/ascii_defs.h"
      7 #include "nvim/assert_defs.h"
      8 #include "nvim/buffer_defs.h"
      9 #include "nvim/change.h"
     10 #include "nvim/cursor.h"
     11 #include "nvim/drawscreen.h"
     12 #include "nvim/fold.h"
     13 #include "nvim/globals.h"
     14 #include "nvim/macros_defs.h"
     15 #include "nvim/mark.h"
     16 #include "nvim/mbyte.h"
     17 #include "nvim/mbyte_defs.h"
     18 #include "nvim/memline.h"
     19 #include "nvim/memory.h"
     20 #include "nvim/move.h"
     21 #include "nvim/option.h"
     22 #include "nvim/option_vars.h"
     23 #include "nvim/plines.h"
     24 #include "nvim/pos_defs.h"
     25 #include "nvim/state.h"
     26 #include "nvim/state_defs.h"
     27 #include "nvim/types_defs.h"
     28 #include "nvim/vim_defs.h"
     29 
     30 #include "cursor.c.generated.h"
     31 
     32 /// @return  the screen position of the cursor.
     33 int getviscol(void)
     34 {
     35  colnr_T x;
     36 
     37  getvvcol(curwin, &curwin->w_cursor, &x, NULL, NULL);
     38  return (int)x;
     39 }
     40 
     41 /// @return the screen position of character col with a coladd in the cursor line.
     42 int getviscol2(colnr_T col, colnr_T coladd)
     43 {
     44  colnr_T x;
     45  pos_T pos;
     46 
     47  pos.lnum = curwin->w_cursor.lnum;
     48  pos.col = col;
     49  pos.coladd = coladd;
     50  getvvcol(curwin, &pos, &x, NULL, NULL);
     51  return (int)x;
     52 }
     53 
     54 /// Go to column "wcol", and add/insert white space as necessary to get the
     55 /// cursor in that column.
     56 /// The caller must have saved the cursor line for undo!
     57 int coladvance_force(colnr_T wcol)
     58 {
     59  int rc = coladvance2(curwin, &curwin->w_cursor, true, false, wcol);
     60 
     61  if (wcol == MAXCOL) {
     62    curwin->w_valid &= ~VALID_VIRTCOL;
     63  } else {
     64    // Virtcol is valid
     65    set_valid_virtcol(curwin, wcol);
     66  }
     67  return rc;
     68 }
     69 
     70 /// Try to advance the Cursor to the specified screen column.
     71 /// If virtual editing: fine tune the cursor position.
     72 /// Note that all virtual positions off the end of a line should share
     73 /// a curwin->w_cursor.col value (n.b. this is equal to strlen(line)),
     74 /// beginning at coladd 0.
     75 ///
     76 /// @return  OK if desired column is reached, FAIL if not
     77 int coladvance(win_T *wp, colnr_T wcol)
     78 {
     79  int rc = getvpos(wp, &wp->w_cursor, wcol);
     80 
     81  if (wcol == MAXCOL || rc == FAIL) {
     82    wp->w_valid &= ~VALID_VIRTCOL;
     83  } else if (*(ml_get_buf(wp->w_buffer, wp->w_cursor.lnum) + wp->w_cursor.col) != TAB) {
     84    // Virtcol is valid when not on a TAB
     85    set_valid_virtcol(curwin, wcol);
     86  }
     87  return rc;
     88 }
     89 
     90 /// @param addspaces  change the text to achieve our goal? only for wp=curwin!
     91 /// @param finetune  change char offset for the exact column
     92 /// @param wcol_arg  column to move to (can be negative)
     93 static int coladvance2(win_T *wp, pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_arg)
     94 {
     95  assert(wp == curwin || !addspaces);
     96  colnr_T wcol = wcol_arg;
     97  int idx;
     98  colnr_T col = 0;
     99  int head = 0;
    100 
    101  int one_more = (State & MODE_INSERT)
    102                 || (State & MODE_TERMINAL)
    103                 || restart_edit != NUL
    104                 || (VIsual_active && *p_sel != 'o')
    105                 || ((get_ve_flags(wp) & kOptVeFlagOnemore) && wcol < MAXCOL);
    106 
    107  char *line = ml_get_buf(wp->w_buffer, pos->lnum);
    108  int linelen = ml_get_buf_len(wp->w_buffer, pos->lnum);
    109 
    110  if (wcol >= MAXCOL) {
    111    idx = linelen - 1 + one_more;
    112    col = wcol;
    113 
    114    if ((addspaces || finetune) && !VIsual_active) {
    115      wp->w_curswant = linetabsize(wp, pos->lnum) + one_more;
    116      if (wp->w_curswant > 0) {
    117        wp->w_curswant--;
    118      }
    119    }
    120  } else {
    121    int width = wp->w_view_width - win_col_off(wp);
    122    int csize = 0;
    123 
    124    if (finetune
    125        && wp->w_p_wrap
    126        && wp->w_view_width != 0
    127        && wcol >= (colnr_T)width
    128        && width > 0) {
    129      csize = linetabsize_eol(wp, pos->lnum);
    130      if (csize > 0) {
    131        csize--;
    132      }
    133 
    134      if (wcol / width > (colnr_T)csize / width
    135          && ((State & MODE_INSERT) == 0 || (int)wcol > csize + 1)) {
    136        // In case of line wrapping don't move the cursor beyond the
    137        // right screen edge.  In Insert mode allow going just beyond
    138        // the last character (like what happens when typing and
    139        // reaching the right window edge).
    140        wcol = (csize / width + 1) * width - 1;
    141      }
    142    }
    143 
    144    CharsizeArg csarg;
    145    CSType cstype = init_charsize_arg(&csarg, wp, pos->lnum, line);
    146    StrCharInfo ci = utf_ptr2StrCharInfo(line);
    147    col = 0;
    148    while (col <= wcol && *ci.ptr != NUL) {
    149      CharSize cs = win_charsize(cstype, col, ci.ptr, ci.chr.value, &csarg);
    150      csize = cs.width;
    151      head = cs.head;
    152      col += cs.width;
    153      ci = utfc_next(ci);
    154    }
    155    idx = (int)(ci.ptr - line);
    156 
    157    // Handle all the special cases.  The virtual_active() check
    158    // is needed to ensure that a virtual position off the end of
    159    // a line has the correct indexing.  The one_more comparison
    160    // replaces an explicit add of one_more later on.
    161    if (col > wcol || (!virtual_active(wp) && one_more == 0)) {
    162      idx -= 1;
    163      // Don't count the chars from 'showbreak'.
    164      csize -= head;
    165      col -= csize;
    166    }
    167 
    168    if (virtual_active(wp)
    169        && addspaces
    170        && wcol >= 0
    171        && ((col != wcol && col != wcol + 1) || csize > 1)) {
    172      // 'virtualedit' is set: The difference between wcol and col is filled with spaces.
    173 
    174      if (line[idx] == NUL) {
    175        // Append spaces
    176        int correct = wcol - col;
    177        size_t newline_size;
    178        STRICT_ADD(idx, correct, &newline_size, size_t);
    179        char *newline = xmallocz(newline_size);
    180        memcpy(newline, line, (size_t)idx);
    181        memset(newline + idx, ' ', (size_t)correct);
    182 
    183        ml_replace(pos->lnum, newline, false);
    184        inserted_bytes(pos->lnum, (colnr_T)idx, 0, correct);
    185        idx += correct;
    186        col = wcol;
    187      } else {
    188        // Break a tab
    189        int correct = wcol - col - csize + 1;             // negative!!
    190        char *newline;
    191 
    192        if (-correct > csize) {
    193          return FAIL;
    194        }
    195 
    196        size_t n;
    197        STRICT_ADD(linelen - 1, csize, &n, size_t);
    198        newline = xmallocz(n);
    199        // Copy first idx chars
    200        memcpy(newline, line, (size_t)idx);
    201        // Replace idx'th char with csize spaces
    202        memset(newline + idx, ' ', (size_t)csize);
    203        // Copy the rest of the line
    204        STRICT_SUB(linelen, idx, &n, size_t);
    205        STRICT_SUB(n, 1, &n, size_t);
    206        memcpy(newline + idx + csize, line + idx + 1, n);
    207 
    208        ml_replace(pos->lnum, newline, false);
    209        inserted_bytes(pos->lnum, idx, 1, csize);
    210        idx += (csize - 1 + correct);
    211        col += correct;
    212      }
    213    }
    214  }
    215 
    216  pos->col = MAX(idx, 0);
    217  pos->coladd = 0;
    218 
    219  if (finetune) {
    220    if (wcol == MAXCOL) {
    221      // The width of the last character is used to set coladd.
    222      if (!one_more) {
    223        colnr_T scol, ecol;
    224 
    225        getvcol(wp, pos, &scol, NULL, &ecol);
    226        pos->coladd = ecol - scol;
    227      }
    228    } else {
    229      int b = (int)wcol - (int)col;
    230 
    231      // The difference between wcol and col is used to set coladd.
    232      if (b > 0 && b < (MAXCOL - 2 * wp->w_view_width)) {
    233        pos->coladd = b;
    234      }
    235 
    236      col += b;
    237    }
    238  }
    239 
    240  // Prevent from moving onto a trail byte.
    241  mark_mb_adjustpos(wp->w_buffer, pos);
    242 
    243  if (wcol < 0 || col < wcol) {
    244    return FAIL;
    245  }
    246  return OK;
    247 }
    248 
    249 /// Return in "pos" the position of the cursor advanced to screen column "wcol".
    250 ///
    251 /// @return  OK if desired column is reached, FAIL if not
    252 int getvpos(win_T *wp, pos_T *pos, colnr_T wcol)
    253 {
    254  return coladvance2(wp, pos, false, virtual_active(wp), wcol);
    255 }
    256 
    257 /// Increment the cursor position.  See inc() for return values.
    258 int inc_cursor(void)
    259 {
    260  return inc(&curwin->w_cursor);
    261 }
    262 
    263 /// Decrement the line pointer 'p' crossing line boundaries as necessary.
    264 ///
    265 /// @return  1 when crossing a line, -1 when at start of file, 0 otherwise.
    266 int dec_cursor(void)
    267 {
    268  return dec(&curwin->w_cursor);
    269 }
    270 
    271 /// Get the line number relative to the current cursor position, i.e. the
    272 /// difference between line number and cursor position. Only look for lines that
    273 /// can be visible, folded lines don't count.
    274 ///
    275 /// @param lnum line number to get the result for
    276 linenr_T get_cursor_rel_lnum(win_T *wp, linenr_T lnum)
    277 {
    278  linenr_T cursor = wp->w_cursor.lnum;
    279  if (lnum == cursor || !hasAnyFolding(wp)) {
    280    return lnum - cursor;
    281  }
    282 
    283  linenr_T from_line = lnum < cursor ? lnum : cursor;
    284  linenr_T to_line = lnum > cursor ? lnum : cursor;
    285  linenr_T retval = 0;
    286 
    287  // Loop until we reach to_line, skipping folds.
    288  for (; from_line < to_line; from_line++, retval++) {
    289    // If from_line is in a fold, set it to the last line of that fold.
    290    hasFolding(wp, from_line, NULL, &from_line);
    291  }
    292 
    293  // If to_line is in a closed fold, the line count is off by +1. Correct it.
    294  if (from_line > to_line) {
    295    retval--;
    296  }
    297 
    298  return (lnum < cursor) ? -retval : retval;
    299 }
    300 
    301 /// Make sure "pos.lnum" and "pos.col" are valid in "buf".
    302 /// This allows for the col to be on the NUL byte.
    303 void check_pos(buf_T *buf, pos_T *pos)
    304 {
    305  pos->lnum = MIN(pos->lnum, buf->b_ml.ml_line_count);
    306  if (pos->col > 0) {
    307    pos->col = MIN(pos->col, ml_get_buf_len(buf, pos->lnum));
    308  }
    309 }
    310 
    311 /// Make sure win->w_cursor.lnum is valid.
    312 void check_cursor_lnum(win_T *win)
    313 {
    314  buf_T *buf = win->w_buffer;
    315  if (win->w_cursor.lnum > buf->b_ml.ml_line_count) {
    316    // If there is a closed fold at the end of the file, put the cursor in
    317    // its first line.  Otherwise in the last line.
    318    if (!hasFolding(win, buf->b_ml.ml_line_count, &win->w_cursor.lnum, NULL)) {
    319      win->w_cursor.lnum = buf->b_ml.ml_line_count;
    320    }
    321  }
    322  if (win->w_cursor.lnum <= 0) {
    323    win->w_cursor.lnum = 1;
    324  }
    325 }
    326 
    327 /// Make sure win->w_cursor.col is valid. Special handling of insert-mode.
    328 /// @see mb_check_adjust_col
    329 void check_cursor_col(win_T *win)
    330 {
    331  colnr_T oldcol = win->w_cursor.col;
    332  colnr_T oldcoladd = win->w_cursor.col + win->w_cursor.coladd;
    333  unsigned cur_ve_flags = get_ve_flags(win);
    334 
    335  colnr_T len = ml_get_buf_len(win->w_buffer, win->w_cursor.lnum);
    336  if (len == 0) {
    337    win->w_cursor.col = 0;
    338  } else if (win->w_cursor.col >= len) {
    339    // Allow cursor past end-of-line when:
    340    // - in Insert mode or restarting Insert mode
    341    // - in Terminal mode
    342    // - in Visual mode and 'selection' isn't "old"
    343    // - 'virtualedit' is set
    344    if ((State & MODE_INSERT) || restart_edit
    345        || (State & MODE_TERMINAL)
    346        || (VIsual_active && *p_sel != 'o')
    347        || (cur_ve_flags & kOptVeFlagOnemore)
    348        || virtual_active(win)) {
    349      win->w_cursor.col = len;
    350    } else {
    351      win->w_cursor.col = len - 1;
    352      // Move the cursor to the head byte.
    353      mark_mb_adjustpos(win->w_buffer, &win->w_cursor);
    354    }
    355  } else if (win->w_cursor.col < 0) {
    356    win->w_cursor.col = 0;
    357  }
    358 
    359  // If virtual editing is on, we can leave the cursor on the old position,
    360  // only we must set it to virtual.  But don't do it when at the end of the
    361  // line.
    362  if (oldcol == MAXCOL) {
    363    win->w_cursor.coladd = 0;
    364  } else if (cur_ve_flags == kOptVeFlagAll) {
    365    if (oldcoladd > win->w_cursor.col) {
    366      win->w_cursor.coladd = oldcoladd - win->w_cursor.col;
    367 
    368      // Make sure that coladd is not more than the char width.
    369      // Not for the last character, coladd is then used when the cursor
    370      // is actually after the last character.
    371      if (win->w_cursor.col + 1 < len) {
    372        assert(win->w_cursor.coladd > 0);
    373        int cs, ce;
    374 
    375        getvcol(win, &win->w_cursor, &cs, NULL, &ce);
    376        win->w_cursor.coladd = MIN(win->w_cursor.coladd, ce - cs);
    377      }
    378    } else {
    379      // avoid weird number when there is a miscalculation or overflow
    380      win->w_cursor.coladd = 0;
    381    }
    382  }
    383 }
    384 
    385 /// Make sure curwin->w_cursor in on a valid character
    386 void check_cursor(win_T *wp)
    387 {
    388  check_cursor_lnum(wp);
    389  check_cursor_col(wp);
    390 }
    391 
    392 /// Check if VIsual position is valid, correct it if not.
    393 /// Can be called when in Visual mode and a change has been made.
    394 void check_visual_pos(void)
    395 {
    396  if (VIsual.lnum > curbuf->b_ml.ml_line_count) {
    397    VIsual.lnum = curbuf->b_ml.ml_line_count;
    398    VIsual.col = 0;
    399    VIsual.coladd = 0;
    400  } else {
    401    int len = ml_get_len(VIsual.lnum);
    402 
    403    if (VIsual.col > len) {
    404      VIsual.col = len;
    405      VIsual.coladd = 0;
    406    }
    407  }
    408 }
    409 
    410 /// Make sure curwin->w_cursor is not on the NUL at the end of the line.
    411 /// Allow it when in Visual mode and 'selection' is not "old".
    412 void adjust_cursor_col(void)
    413 {
    414  if (curwin->w_cursor.col > 0
    415      && (!VIsual_active || *p_sel == 'o')
    416      && gchar_cursor() == NUL) {
    417    curwin->w_cursor.col--;
    418  }
    419 }
    420 
    421 /// Set "curwin->w_leftcol" to "leftcol".
    422 /// Adjust the cursor position if needed.
    423 ///
    424 /// @return  true if the cursor was moved.
    425 bool set_leftcol(colnr_T leftcol)
    426 {
    427  // Return quickly when there is no change.
    428  if (curwin->w_leftcol == leftcol) {
    429    return false;
    430  }
    431  curwin->w_leftcol = leftcol;
    432 
    433  changed_cline_bef_curs(curwin);
    434  // TODO(hinidu): I think it should be colnr_T or int, but p_siso is long.
    435  // Perhaps we can change p_siso to int.
    436  int64_t lastcol = curwin->w_leftcol + curwin->w_view_width - win_col_off(curwin) - 1;
    437  validate_virtcol(curwin);
    438 
    439  bool retval = false;
    440  // If the cursor is right or left of the screen, move it to last or first
    441  // visible character.
    442  int siso = get_sidescrolloff_value(curwin);
    443  if (curwin->w_virtcol > (colnr_T)(lastcol - siso)) {
    444    retval = true;
    445    coladvance(curwin, (colnr_T)(lastcol - siso));
    446  } else if (curwin->w_virtcol < curwin->w_leftcol + siso) {
    447    retval = true;
    448    coladvance(curwin, (colnr_T)(curwin->w_leftcol + siso));
    449  }
    450 
    451  // If the start of the character under the cursor is not on the screen,
    452  // advance the cursor one more char.  If this fails (last char of the
    453  // line) adjust the scrolling.
    454  colnr_T s, e;
    455  getvvcol(curwin, &curwin->w_cursor, &s, NULL, &e);
    456  if (e > (colnr_T)lastcol) {
    457    retval = true;
    458    coladvance(curwin, s - 1);
    459  } else if (s < curwin->w_leftcol) {
    460    retval = true;
    461    if (coladvance(curwin, e + 1) == FAIL) {    // there isn't another character
    462      curwin->w_leftcol = s;            // adjust w_leftcol instead
    463      changed_cline_bef_curs(curwin);
    464    }
    465  }
    466 
    467  if (retval) {
    468    curwin->w_set_curswant = true;
    469  }
    470  redraw_later(curwin, UPD_NOT_VALID);
    471  return retval;
    472 }
    473 
    474 int gchar_cursor(void)
    475 {
    476  return utf_ptr2char(get_cursor_pos_ptr());
    477 }
    478 
    479 /// Return the character immediately before the cursor.
    480 int char_before_cursor(void)
    481 {
    482  if (curwin->w_cursor.col == 0) {
    483    return -1;
    484  }
    485 
    486  char *line = get_cursor_line_ptr();
    487  char *p = line + curwin->w_cursor.col;
    488  int prev_len = utf_head_off(line, p - 1) + 1;
    489  return utf_ptr2char(p - prev_len);
    490 }
    491 
    492 /// Write a character at the current cursor position.
    493 /// It is directly written into the block.
    494 void pchar_cursor(char c)
    495 {
    496  *(ml_get_buf_mut(curbuf, curwin->w_cursor.lnum) + curwin->w_cursor.col) = c;
    497 }
    498 
    499 /// @return  pointer to cursor line.
    500 char *get_cursor_line_ptr(void)
    501 {
    502  return ml_get_buf(curbuf, curwin->w_cursor.lnum);
    503 }
    504 
    505 /// @return  pointer to cursor position.
    506 char *get_cursor_pos_ptr(void)
    507 {
    508  return ml_get_buf(curbuf, curwin->w_cursor.lnum) + curwin->w_cursor.col;
    509 }
    510 
    511 /// @return  length (excluding the NUL) of the cursor line.
    512 colnr_T get_cursor_line_len(void)
    513 {
    514  return ml_get_buf_len(curbuf, curwin->w_cursor.lnum);
    515 }
    516 
    517 /// @return  length (excluding the NUL) of the cursor position.
    518 colnr_T get_cursor_pos_len(void)
    519 {
    520  return ml_get_buf_len(curbuf, curwin->w_cursor.lnum) - curwin->w_cursor.col;
    521 }