1 /*******************************************************************************
2 * Copyright (c) 2013-2021, Andrés Martinelli <andmarti@gmail.com> *
3 * All rights reserved. *
4 * *
5 * This file is a part of SC-IM *
6 * *
7 * SC-IM is a spreadsheet program that is based on SC. The original authors *
8 * of SC are James Gosling and Mark Weiser, and mods were later added by *
9 * Chuck Martin. *
10 * *
11 * Redistribution and use in source and binary forms, with or without *
12 * modification, are permitted provided that the following conditions are met: *
13 * 1. Redistributions of source code must retain the above copyright *
14 * notice, this list of conditions and the following disclaimer. *
15 * 2. Redistributions in binary form must reproduce the above copyright *
16 * notice, this list of conditions and the following disclaimer in the *
17 * documentation and/or other materials provided with the distribution. *
18 * 3. All advertising materials mentioning features or use of this software *
19 * must display the following acknowledgement: *
20 * This product includes software developed by Andrés Martinelli *
21 * <andmarti@gmail.com>. *
22 * 4. Neither the name of the Andrés Martinelli nor the *
23 * names of other contributors may be used to endorse or promote products *
24 * derived from this software without specific prior written permission. *
25 * *
26 * THIS SOFTWARE IS PROVIDED BY ANDRES MARTINELLI ''AS IS'' AND ANY *
27 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED *
28 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE *
29 * DISCLAIMED. IN NO EVENT SHALL ANDRES MARTINELLI BE LIABLE FOR ANY *
30 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES *
31 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;*
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
33 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE *
35 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
36 *******************************************************************************/
37
38 /**
39 * \file input.c
40 * \author Andrés Martinelli <andmarti@gmail.com>
41 * \date 2021-04-02
42 * \brief functions to handle stdin
43 */
44
45 #include <sys/time.h>
46 #include <string.h>
47 #include <ctype.h> // for isdigit
48 #include <stdlib.h>
49 #include <wchar.h>
50 #include <wctype.h>
51
52 #include "main.h"
53 #include "tui.h"
54 #include "maps.h"
55 #include "cmds.h"
56 #include "history.h"
57 #include "conf.h"
58 #include "utils/string.h"
59 #include "cmds_visual.h"
60 #include "buffer.h"
61 #include "digraphs.h"
62
63 static wint_t wd; // char read from stdin
64 static int d; // char read from stdin
65 int return_value; // return value of getch()
66 int cmd_multiplier = 0; // Multiplier
67 int cmd_pending = 0; // Command pending
68 int cmd_digraph = 0;
69 static wint_t digraph;
70 #ifdef MOUSE
71 MEVENT event; // mouse event
72 #endif
73
74 /**
75 * \brief Reads stdin for a valid command
76 *
77 * \details Read characters from stdin to an input buffer. When filled,
78 * validate the command and call the appropriate handler. When a timeout
79 * is reached, flush the buffer.
80 *
81 * \param buffer
82 *
83 * \return none
84 */
85
handle_input(struct block * buffer)86 void handle_input(struct block * buffer) {
87 struct timeval start_tv, m_tv, init_tv; // For measuring timeout
88 gettimeofday(&start_tv, NULL);
89 gettimeofday(&m_tv, NULL);
90 gettimeofday(&init_tv, NULL); // keep time when entering handle_input
91 long msec = (m_tv.tv_sec - start_tv.tv_sec) * 1000L +
92 (m_tv.tv_usec - start_tv.tv_usec) / 1000L;
93 long msec_init = (m_tv.tv_sec - init_tv.tv_sec) * 1000L +
94 (m_tv.tv_usec - init_tv.tv_usec) / 1000L;
95
96 cmd_multiplier = 0;
97 cmd_pending = 0;
98
99 /* add char to buffer until valid command found.
100 * important: buffer may contain a valid command (for instance,
101 * just a letter in insert mode) but that buffer start may also
102 * be a possible mapping! (for instace kj in insert mode to exit the mode).
103 * if so, wait a mapping_timeout (1500ms), before triggering has_cmd..
104 */
105 while (
106 ( ! has_cmd(buffer, msec) && msec <= get_conf_int("command_timeout")) ||
107 ( could_be_mapping(buffer) && msec_init < get_conf_int("mapping_timeout"))
108 ) {
109
110 // if command pending, refresh 'ef' only. Multiplier and cmd pending
111 if (cmd_pending) ui_print_mult_pend();
112
113 // Modify cursor state according to the current mode
114 ui_handle_cursor();
115
116 // Read new character from stdin
117 return_value = ui_getch(&wd);
118 d = wd;
119 #ifdef MOUSE
120 if (d == KEY_MOUSE) {
121 getmouse (&event);
122 ui_handle_mouse(event);
123 return;
124 }
125 #endif
126
127 if ( d == OKEY_ESC || d == ctl('g')) {
128 break_waitcmd_loop(buffer);
129 ui_show_header();
130 return;
131 }
132
133 // Handle multiplier of commands in NORMAL VISUAL and EDIT modes
134 if ( return_value != -1 && isdigit(d)
135 && ( buffer->value == L'\0' || iswdigit((wchar_t) buffer->value))
136 && ( curmode == NORMAL_MODE || curmode == VISUAL_MODE || curmode == EDIT_MODE )
137 && ( cmd_multiplier || d != L'0' )
138 && ( ! get_conf_int("numeric"))
139 ) {
140 cmd_multiplier *= 10;
141 cmd_multiplier += (int) (d - '0');
142 if (cmd_multiplier > MAX_MULTIPLIER) cmd_multiplier = 0;
143
144 gettimeofday(&start_tv, NULL);
145 msec = (m_tv.tv_sec - start_tv.tv_sec) * 1000L +
146 (m_tv.tv_usec - start_tv.tv_usec) / 1000L;
147
148 ui_print_mult_pend();
149 continue;
150 }
151
152
153 /*
154 * Handle special characters input: BS TAB ENTER HOME END DEL PGUP
155 * PGDOWN and alphanumeric characters
156 */
157 if (is_idchar(d) || return_value != -1) {
158 // If in NORMAL, VISUAL or EDITION mode, added '?' cmd_pending at the left of MODE
159 if ( (curmode == NORMAL_MODE && d >= ' ') || //FIXME
160 (curmode == EDIT_MODE && d >= ' ') ||
161 (curmode == VISUAL_MODE && d >= ' ') ) {
162 cmd_pending = 1;
163 }
164 if (cmd_digraph) {
165 if (digraph == 0) {
166 digraph = wd;
167 continue;
168 }
169 wd = get_digraph(digraph, wd);
170 cmd_digraph = 0;
171 digraph = 0;
172
173 } else if (! cmd_digraph && wd == ctl('k')) {
174 cmd_digraph = 1;
175 continue;
176 }
177 addto_buf(buffer, wd);
178
179 // Replace maps in buffer
180 // break if nore mapping
181 if (replace_maps(buffer) == 1) break;
182 }
183
184 /*
185 * Update time stamp to reset timeout after each loop
186 * (start_tv changes only if current mode is COMMAND, INSERT or
187 * EDIT) and for each user input as well.
188 */
189 gettimeofday(&m_tv, NULL);
190 msec = (m_tv.tv_sec - start_tv.tv_sec) * 1000L +
191 (m_tv.tv_usec - start_tv.tv_usec) / 1000L;
192
193 msec_init = (m_tv.tv_sec - init_tv.tv_sec) * 1000L +
194 (m_tv.tv_usec - init_tv.tv_usec) / 1000L;
195 if (msec_init > 4000) gettimeofday(&init_tv, NULL); // just to avoid overload
196 fix_timeout(&start_tv);
197
198 // to handle map of ESC
199 if ( buffer->value == OKEY_ESC || buffer->value == ctl('g')) {
200 break_waitcmd_loop(buffer);
201 ui_print_mult_pend();
202 ui_refresh_pad(0);
203 return;
204 }
205
206 }
207 if (msec >= get_conf_int("command_timeout")) { // timeout. Command incomplete
208 cmd_pending = 0; // No longer wait for a command, set flag.
209 cmd_multiplier = 0; // Reset multiplier
210 } else { // Execute command or mapping
211 cmd_pending = 0;
212 ui_clr_header(1); // Clean second line
213 handle_mult( &cmd_multiplier, buffer, msec ); // Handle command and repeat as many times as the multiplier dictates
214 }
215 ui_print_mult_pend();
216 flush_buf(buffer); // Flush the buffer
217 return;
218 }
219
220 /**
221 * \brief Break waiting command loop
222 *
223 * \return none
224 */
break_waitcmd_loop(struct block * buffer)225 void break_waitcmd_loop(struct block * buffer) {
226 if (curmode == COMMAND_MODE) {
227 #ifdef HISTORY_FILE
228 del_item_from_history(commandline_history, 0);
229 commandline_history->pos = 0;
230 set_comp(0);
231 #endif
232 } else if (curmode == INSERT_MODE) {
233 #ifdef INS_HISTORY_FILE
234 del_item_from_history(insert_history, 0);
235 insert_history->pos = 0;
236 #endif
237 } else if (curmode == VISUAL_MODE) {
238 exit_visualmode();
239 }
240 if (curmode == INSERT_MODE && lastmode == EDIT_MODE) {
241 if (inputline_pos && wcslen(inputline) >= inputline_pos) {
242 real_inputline_pos--;
243 int l = wcwidth(inputline[real_inputline_pos]);
244 inputline_pos -= l;
245 }
246 chg_mode(insert_edit_submode == '=' ? 'e' : 'E');
247 lastmode=NORMAL_MODE;
248 ui_show_header();
249 } else if (curmode == EDIT_MODE && lastmode == INSERT_MODE) {
250 chg_mode(insert_edit_submode);
251 lastmode=NORMAL_MODE;
252 ui_show_header();
253 } else {
254 chg_mode('.');
255 lastmode=NORMAL_MODE;
256 inputline[0] = L'\0'; // clean inputline
257 flush_buf(buffer);
258 ui_update(TRUE);
259 }
260 cmd_pending = 0; // No longer wait for command. Set flag.
261 cmd_multiplier = 0; // Reset the multiplier
262 return;
263 }
264
265 /**
266 * \brief Handle timeout depending on the current mode
267 *
268 * Handle timeout depending on the current mode. There is NO timeout
269 * for COMMAND, INSERT, and EDIT modes.
270 *
271 * \param[in] start_tv
272 *
273 * \return none
274 */
275
fix_timeout(struct timeval * start_tv)276 void fix_timeout(struct timeval * start_tv) {
277 switch (curmode) {
278 case COMMAND_MODE:
279 case INSERT_MODE:
280 gettimeofday(start_tv, NULL);
281 break;
282 case VISUAL_MODE:
283 case EDIT_MODE:
284 case NORMAL_MODE:
285 if (d != 0) gettimeofday(start_tv, NULL);
286 }
287 return;
288 }
289
290 /**
291 * \brief Traverse 'stuffbuff' and determine if there is a valid command.
292 *
293 * Traverse 'stuffbuff' and determine if there is a valid
294 * command (e.g. buffer = "diw").
295 *
296 * \param[in]buf
297 * \param[in]timeout
298 * \return none
299 */
300
has_cmd(struct block * buf,long timeout)301 int has_cmd (struct block * buf, long timeout) {
302 int len = get_bufsize(buf);
303 if ( ! len ) return 0;
304 int k, found = 0;
305
306 struct block * auxb = (struct block *) create_buf();
307
308 for (k = 0; k < len; k++) {
309 addto_buf(auxb, get_bufval(buf, k));
310 if ( is_single_command(auxb, timeout)) { found = 1; break; }
311 }
312 erase_buf(auxb);
313 auxb = NULL;
314 return found;
315 }
316
317 void do_commandmode(struct block * sb);
318 void do_normalmode (struct block * buf);
319 void do_insertmode(struct block * sb);
320 void do_editmode(struct block * sb);
321 void do_visualmode(struct block * sb);
322
323 /**
324 * \brief Use specific functions for every command on each mode
325 *
326 * \param[in] sb
327 *
328 * \return none
329 */
330
exec_single_cmd(struct block * sb)331 void exec_single_cmd (struct block * sb) {
332 switch (curmode) {
333 case NORMAL_MODE:
334 do_normalmode(sb);
335 break;
336 case INSERT_MODE:
337 do_insertmode(sb);
338 break;
339 case COMMAND_MODE:
340 do_commandmode(sb);
341 break;
342 case EDIT_MODE:
343 do_editmode(sb);
344 break;
345 case VISUAL_MODE:
346 do_visualmode(sb);
347 break;
348 }
349 return;
350 }
351
352 /**
353 * \brief Handle the final command to be executed, using the multiplier
354 *
355 * \param[in] cmd_multiplier
356 * \param[in] buf
357 * \param[in] timeout
358 *
359 * \return none
360 */
361
handle_mult(int * cmd_multiplier,struct block * buf,long timeout)362 void handle_mult(int * cmd_multiplier, struct block * buf, long timeout) {
363 int j, k;
364 struct block * b_copy = buf;
365 int lenbuf = get_bufsize(b_copy);
366 if ( ! *cmd_multiplier) *cmd_multiplier = 1;
367
368 for (j = 1; j < *cmd_multiplier; j++) {
369 for (k = 0; k < lenbuf; k++) {
370 addto_buf(buf, b_copy->value);
371 b_copy = b_copy->pnext;
372 }
373 }
374 //if (is_single_command(buf, timeout) == EDITION_CMD)
375 // copybuffer(buf, lastcmd_buffer); // save stdin buffer content in lastcmd buffer
376 exec_mult(buf, timeout);
377 if (*cmd_multiplier > 1) {
378 *cmd_multiplier = 1;
379 if (curmode != EDIT_MODE) ui_update(TRUE);
380 }
381
382 *cmd_multiplier = 0;
383
384 return;
385 }
386
387 /**
388 * \brief Handle multiple command execution in sequence
389 *
390 * \return none
391 */
392
exec_mult(struct block * buf,long timeout)393 void exec_mult (struct block * buf, long timeout) {
394 int k, res, len = get_bufsize(buf);
395 if ( ! len ) return;
396
397 // Try to execute the whole buffer content
398 if ((res = is_single_command(buf, timeout))) {
399 if (res == EDITION_CMD) copybuffer(buf, lastcmd_buffer); // save stdin buffer content in lastcmd buffer
400 exec_single_cmd(buf);
401
402 // If not possible, traverse blockwise
403 } else {
404 struct block * auxb = (struct block *) create_buf();
405 for (k = 0; k < len; k++) {
406 addto_buf(auxb, get_bufval(buf, k));
407
408 if ((res = is_single_command(auxb, timeout))) {
409 if (res == EDITION_CMD) copybuffer(buf, lastcmd_buffer); // save stdin buffer content in lastcmd buffer
410 exec_single_cmd(auxb);
411 flush_buf(auxb);
412 k++; // Take the first K values from 'buf'
413 while ( k-- ) buf = dequeue(buf);
414 // Execute again
415 if (cmd_multiplier == 0) break;
416 exec_mult (buf, timeout);
417 break;
418 }
419 }
420 erase_buf(auxb);
421 auxb = NULL;
422 }
423 return;
424 }
425