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