neovim

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

cursor_shape.c (12295B)


      1 #include <stdbool.h>
      2 #include <stdint.h>
      3 #include <string.h>
      4 
      5 #include "nvim/api/private/defs.h"
      6 #include "nvim/api/private/helpers.h"
      7 #include "nvim/ascii_defs.h"
      8 #include "nvim/charset.h"
      9 #include "nvim/cursor_shape.h"
     10 #include "nvim/ex_getln.h"
     11 #include "nvim/gettext_defs.h"
     12 #include "nvim/globals.h"
     13 #include "nvim/highlight_group.h"
     14 #include "nvim/log.h"
     15 #include "nvim/macros_defs.h"
     16 #include "nvim/option_vars.h"
     17 #include "nvim/state_defs.h"
     18 #include "nvim/strings.h"
     19 #include "nvim/ui.h"
     20 
     21 #include "cursor_shape.c.generated.h"
     22 
     23 static const char e_digit_expected[] = N_("E548: Digit expected");
     24 
     25 /// Handling of cursor and mouse pointer shapes in various modes.
     26 cursorentry_T shape_table[SHAPE_IDX_COUNT] = {
     27  // Values are set by 'guicursor' and 'mouseshape'.
     28  // Adjust the SHAPE_IDX_ defines when changing this!
     29  { "normal", 0, 0, 0, 700, 400, 250, 0, 0, "n", SHAPE_CURSOR + SHAPE_MOUSE },
     30  { "visual", 0, 0, 0, 700, 400, 250, 0, 0, "v", SHAPE_CURSOR + SHAPE_MOUSE },
     31  { "insert", 0, 0, 0, 700, 400, 250, 0, 0, "i", SHAPE_CURSOR + SHAPE_MOUSE },
     32  { "replace", 0, 0, 0, 700, 400, 250, 0, 0, "r", SHAPE_CURSOR + SHAPE_MOUSE },
     33  { "cmdline_normal", 0, 0, 0, 700, 400, 250, 0, 0, "c", SHAPE_CURSOR + SHAPE_MOUSE },
     34  { "cmdline_insert", 0, 0, 0, 700, 400, 250, 0, 0, "ci", SHAPE_CURSOR + SHAPE_MOUSE },
     35  { "cmdline_replace", 0, 0, 0, 700, 400, 250, 0, 0, "cr", SHAPE_CURSOR + SHAPE_MOUSE },
     36  { "operator", 0, 0, 0, 700, 400, 250, 0, 0, "o", SHAPE_CURSOR + SHAPE_MOUSE },
     37  { "visual_select", 0, 0, 0, 700, 400, 250, 0, 0, "ve", SHAPE_CURSOR + SHAPE_MOUSE },
     38  { "cmdline_hover", 0, 0, 0,   0,   0,   0, 0, 0, "e", SHAPE_MOUSE },
     39  { "statusline_hover", 0, 0, 0,   0,   0,   0, 0, 0, "s", SHAPE_MOUSE },
     40  { "statusline_drag", 0, 0, 0,   0,   0,   0, 0, 0, "sd", SHAPE_MOUSE },
     41  { "vsep_hover", 0, 0, 0,   0,   0,   0, 0, 0, "vs", SHAPE_MOUSE },
     42  { "vsep_drag", 0, 0, 0,   0,   0,   0, 0, 0, "vd", SHAPE_MOUSE },
     43  { "more", 0, 0, 0,   0,   0,   0, 0, 0, "m", SHAPE_MOUSE },
     44  { "more_lastline", 0, 0, 0,   0,   0,   0, 0, 0, "ml", SHAPE_MOUSE },
     45  { "showmatch", 0, 0, 0, 100, 100, 100, 0, 0, "sm", SHAPE_CURSOR },
     46  { "terminal", 0, 0, 0, 0, 0, 0, 0, 0, "t", SHAPE_CURSOR },
     47 };
     48 
     49 /// Converts cursor_shapes into an Array of Dictionaries
     50 /// @param arena initialized arena where memory will be allocated
     51 ///
     52 /// @return Array of the form {[ "cursor_shape": ... ], ...}
     53 Array mode_style_array(Arena *arena)
     54 {
     55  Array all = arena_array(arena, SHAPE_IDX_COUNT);
     56 
     57  for (int i = 0; i < SHAPE_IDX_COUNT; i++) {
     58    cursorentry_T *cur = &shape_table[i];
     59    Dict dic = arena_dict(arena, 3 + ((cur->used_for & SHAPE_CURSOR) ? 9 : 0));
     60    PUT_C(dic, "name", CSTR_AS_OBJ(cur->full_name));
     61    PUT_C(dic, "short_name", CSTR_AS_OBJ(cur->name));
     62    if (cur->used_for & SHAPE_MOUSE) {
     63      PUT_C(dic, "mouse_shape", INTEGER_OBJ(cur->mshape));
     64    }
     65    if (cur->used_for & SHAPE_CURSOR) {
     66      String shape_str;
     67      switch (cur->shape) {
     68      case SHAPE_BLOCK:
     69        shape_str = cstr_as_string("block"); break;
     70      case SHAPE_VER:
     71        shape_str = cstr_as_string("vertical"); break;
     72      case SHAPE_HOR:
     73        shape_str = cstr_as_string("horizontal"); break;
     74      default:
     75        shape_str = cstr_as_string("unknown");
     76      }
     77      PUT_C(dic, "cursor_shape", STRING_OBJ(shape_str));
     78      PUT_C(dic, "cell_percentage", INTEGER_OBJ(cur->percentage));
     79      PUT_C(dic, "blinkwait", INTEGER_OBJ(cur->blinkwait));
     80      PUT_C(dic, "blinkon", INTEGER_OBJ(cur->blinkon));
     81      PUT_C(dic, "blinkoff", INTEGER_OBJ(cur->blinkoff));
     82      PUT_C(dic, "hl_id", INTEGER_OBJ(cur->id));
     83      PUT_C(dic, "id_lm", INTEGER_OBJ(cur->id_lm));
     84      PUT_C(dic, "attr_id", INTEGER_OBJ(cur->id ? syn_id2attr(cur->id) : 0));
     85      PUT_C(dic, "attr_id_lm", INTEGER_OBJ(cur->id_lm ? syn_id2attr(cur->id_lm) : 0));
     86    }
     87 
     88    ADD_C(all, DICT_OBJ(dic));
     89  }
     90 
     91  return all;
     92 }
     93 
     94 /// Parses the 'guicursor' option.
     95 ///
     96 /// Clears `shape_table` if 'guicursor' is empty.
     97 ///
     98 /// @param what SHAPE_CURSOR or SHAPE_MOUSE ('mouseshape')
     99 ///
    100 /// @returns error message for an illegal option, NULL otherwise.
    101 const char *parse_shape_opt(int what)
    102 {
    103  char *p = NULL;
    104  int idx = 0;                          // init for GCC
    105  int len;
    106  bool found_ve = false;                 // found "ve" flag
    107 
    108  // First round: check for errors; second round: do it for real.
    109  for (int round = 1; round <= 2; round++) {
    110    if (round == 2 || *p_guicursor == NUL) {
    111      // Set all entries to default (block, blinkon0, default color).
    112      // This is the default for anything that is not set.
    113      clear_shape_table();
    114      if (*p_guicursor == NUL) {
    115        ui_mode_info_set();
    116        return NULL;
    117      }
    118    }
    119    // Repeat for all comma separated parts.
    120    char *modep = p_guicursor;
    121    while (modep != NULL && *modep != NUL) {
    122      char *colonp = vim_strchr(modep, ':');
    123      char *commap = vim_strchr(modep, ',');
    124 
    125      if (colonp == NULL || (commap != NULL && commap < colonp)) {
    126        return N_("E545: Missing colon");
    127      }
    128      if (colonp == modep) {
    129        return N_("E546: Illegal mode");
    130      }
    131 
    132      // Repeat for all modes before the colon.
    133      // For the 'a' mode, we loop to handle all the modes.
    134      int all_idx = -1;
    135      while (modep < colonp || all_idx >= 0) {
    136        if (all_idx < 0) {
    137          // Find the mode
    138          if (modep[1] == '-' || modep[1] == ':') {
    139            len = 1;
    140          } else {
    141            len = 2;
    142          }
    143 
    144          if (len == 1 && TOLOWER_ASC(modep[0]) == 'a') {
    145            all_idx = SHAPE_IDX_COUNT - 1;
    146          } else {
    147            for (idx = 0; idx < SHAPE_IDX_COUNT; idx++) {
    148              if (STRNICMP(modep, shape_table[idx].name, len) == 0) {
    149                break;
    150              }
    151            }
    152            if (idx == SHAPE_IDX_COUNT
    153                || (shape_table[idx].used_for & what) == 0) {
    154              return N_("E546: Illegal mode");
    155            }
    156            if (len == 2 && modep[0] == 'v' && modep[1] == 'e') {
    157              found_ve = true;
    158            }
    159          }
    160          modep += len + 1;
    161        }
    162 
    163        if (all_idx >= 0) {
    164          idx = all_idx--;
    165        }
    166 
    167        // Parse the part after the colon
    168        for (p = colonp + 1; *p && *p != ',';) {
    169          {
    170            // First handle the ones with a number argument.
    171            int i = (uint8_t)(*p);
    172            len = 0;
    173            if (STRNICMP(p, "ver", 3) == 0) {
    174              len = 3;
    175            } else if (STRNICMP(p, "hor", 3) == 0) {
    176              len = 3;
    177            } else if (STRNICMP(p, "blinkwait", 9) == 0) {
    178              len = 9;
    179            } else if (STRNICMP(p, "blinkon", 7) == 0) {
    180              len = 7;
    181            } else if (STRNICMP(p, "blinkoff", 8) == 0) {
    182              len = 8;
    183            }
    184            if (len != 0) {
    185              p += len;
    186              if (!ascii_isdigit(*p)) {
    187                return e_digit_expected;
    188              }
    189              int n = getdigits_int(&p, false, 0);
    190              if (len == 3) {               // "ver" or "hor"
    191                if (n == 0) {
    192                  return N_("E549: Illegal percentage");
    193                }
    194                if (round == 2) {
    195                  if (TOLOWER_ASC(i) == 'v') {
    196                    shape_table[idx].shape = SHAPE_VER;
    197                  } else {
    198                    shape_table[idx].shape = SHAPE_HOR;
    199                  }
    200                  shape_table[idx].percentage = n;
    201                }
    202              } else if (round == 2) {
    203                if (len == 9) {
    204                  shape_table[idx].blinkwait = n;
    205                } else if (len == 7) {
    206                  shape_table[idx].blinkon = n;
    207                } else {
    208                  shape_table[idx].blinkoff = n;
    209                }
    210              }
    211            } else if (STRNICMP(p, "block", 5) == 0) {
    212              if (round == 2) {
    213                shape_table[idx].shape = SHAPE_BLOCK;
    214              }
    215              p += 5;
    216            } else {          // must be a highlight group name then
    217              char *endp = vim_strchr(p, '-');
    218              if (commap == NULL) {                       // last part
    219                if (endp == NULL) {
    220                  endp = p + strlen(p);                  // find end of part
    221                }
    222              } else if (endp > commap || endp == NULL) {
    223                endp = commap;
    224              }
    225              char *slashp = vim_strchr(p, '/');
    226              if (slashp != NULL && slashp < endp) {
    227                // "group/langmap_group"
    228                i = syn_check_group(p, (size_t)(slashp - p));
    229                p = slashp + 1;
    230              }
    231              if (round == 2) {
    232                shape_table[idx].id = syn_check_group(p, (size_t)(endp - p));
    233                shape_table[idx].id_lm = shape_table[idx].id;
    234                if (slashp != NULL && slashp < endp) {
    235                  shape_table[idx].id = i;
    236                }
    237              }
    238              p = endp;
    239            }
    240          }           // if (what != SHAPE_MOUSE)
    241 
    242          if (*p == '-') {
    243            p++;
    244          }
    245        }
    246      }
    247      modep = p;
    248      if (modep != NULL && *modep == ',') {
    249        modep++;
    250      }
    251    }
    252  }
    253 
    254  // If the 's' flag is not given, use the 'v' cursor for 's'
    255  if (!found_ve) {
    256    {
    257      shape_table[SHAPE_IDX_VE].shape = shape_table[SHAPE_IDX_V].shape;
    258      shape_table[SHAPE_IDX_VE].percentage =
    259        shape_table[SHAPE_IDX_V].percentage;
    260      shape_table[SHAPE_IDX_VE].blinkwait =
    261        shape_table[SHAPE_IDX_V].blinkwait;
    262      shape_table[SHAPE_IDX_VE].blinkon =
    263        shape_table[SHAPE_IDX_V].blinkon;
    264      shape_table[SHAPE_IDX_VE].blinkoff =
    265        shape_table[SHAPE_IDX_V].blinkoff;
    266      shape_table[SHAPE_IDX_VE].id = shape_table[SHAPE_IDX_V].id;
    267      shape_table[SHAPE_IDX_VE].id_lm = shape_table[SHAPE_IDX_V].id_lm;
    268    }
    269  }
    270  ui_mode_info_set();
    271  return NULL;
    272 }
    273 
    274 /// Returns true if the cursor is non-blinking "block" shape during
    275 /// visual selection.
    276 ///
    277 /// @param exclusive If 'selection' option is "exclusive".
    278 bool cursor_is_block_during_visual(bool exclusive)
    279  FUNC_ATTR_PURE
    280 {
    281  int mode_idx = exclusive ? SHAPE_IDX_VE : SHAPE_IDX_V;
    282  return (SHAPE_BLOCK == shape_table[mode_idx].shape
    283          && 0 == shape_table[mode_idx].blinkon);
    284 }
    285 
    286 /// Map cursor mode from string to integer
    287 ///
    288 /// @param mode Fullname of the mode whose id we are looking for
    289 /// @return -1 in case of failure, else the matching SHAPE_ID* integer
    290 int cursor_mode_str2int(const char *mode)
    291 {
    292  for (int mode_idx = 0; mode_idx < SHAPE_IDX_COUNT; mode_idx++) {
    293    if (strcmp(shape_table[mode_idx].full_name, mode) == 0) {
    294      return mode_idx;
    295    }
    296  }
    297  WLOG("Unknown mode %s", mode);
    298  return -1;
    299 }
    300 
    301 /// Check if a syntax id is used as a cursor style.
    302 bool cursor_mode_uses_syn_id(int syn_id)
    303  FUNC_ATTR_PURE
    304 {
    305  if (*p_guicursor == NUL) {
    306    return false;
    307  }
    308  for (int mode_idx = 0; mode_idx < SHAPE_IDX_COUNT; mode_idx++) {
    309    if (shape_table[mode_idx].id == syn_id
    310        || shape_table[mode_idx].id_lm == syn_id) {
    311      return true;
    312    }
    313  }
    314  return false;
    315 }
    316 
    317 /// Return the index into shape_table[] for the current mode.
    318 int cursor_get_mode_idx(void)
    319  FUNC_ATTR_PURE
    320 {
    321  if (State == MODE_SHOWMATCH) {
    322    return SHAPE_IDX_SM;
    323  } else if (State == MODE_TERMINAL) {
    324    return SHAPE_IDX_TERM;
    325  } else if (State & VREPLACE_FLAG) {
    326    return SHAPE_IDX_R;
    327  } else if (State & REPLACE_FLAG) {
    328    return SHAPE_IDX_R;
    329  } else if (State & MODE_INSERT) {
    330    return SHAPE_IDX_I;
    331  } else if (State & MODE_CMDLINE) {
    332    if (cmdline_at_end()) {
    333      return SHAPE_IDX_C;
    334    } else if (cmdline_overstrike()) {
    335      return SHAPE_IDX_CR;
    336    } else {
    337      return SHAPE_IDX_CI;
    338    }
    339  } else if (finish_op) {
    340    return SHAPE_IDX_O;
    341  } else if (VIsual_active) {
    342    if (*p_sel == 'e') {
    343      return SHAPE_IDX_VE;
    344    } else {
    345      return SHAPE_IDX_V;
    346    }
    347  } else {
    348    return SHAPE_IDX_N;
    349  }
    350 }
    351 
    352 /// Clears all entries in shape_table to block, blinkon0, and default color.
    353 static void clear_shape_table(void)
    354 {
    355  for (int idx = 0; idx < SHAPE_IDX_COUNT; idx++) {
    356    shape_table[idx].shape = SHAPE_BLOCK;
    357    shape_table[idx].blinkwait = 0;
    358    shape_table[idx].blinkon = 0;
    359    shape_table[idx].blinkoff = 0;
    360    shape_table[idx].id = 0;
    361    shape_table[idx].id_lm = 0;
    362  }
    363 }