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 }