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