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