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