1 /*
2    Widgets for the Midnight Commander
3 
4    Copyright (C) 1994-2021
5    Free Software Foundation, Inc.
6 
7    Authors:
8    Radek Doulik, 1994, 1995
9    Miguel de Icaza, 1994, 1995
10    Jakub Jelinek, 1995
11    Andrej Borsenkow, 1996
12    Norbert Warmuth, 1997
13    Andrew Borodin <aborodin@vmail.ru>, 2009-2016
14 
15    This file is part of the Midnight Commander.
16 
17    The Midnight Commander is free software: you can redistribute it
18    and/or modify it under the terms of the GNU General Public License as
19    published by the Free Software Foundation, either version 3 of the License,
20    or (at your option) any later version.
21 
22    The Midnight Commander is distributed in the hope that it will be useful,
23    but WITHOUT ANY WARRANTY; without even the implied warranty of
24    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25    GNU General Public License for more details.
26 
27    You should have received a copy of the GNU General Public License
28    along with this program.  If not, see <http://www.gnu.org/licenses/>.
29  */
30 
31 /** \file input.c
32  *  \brief Source: WInput widget
33  */
34 
35 #include <config.h>
36 
37 #include <stdlib.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 
41 #include "lib/global.h"
42 
43 #include "lib/tty/tty.h"
44 #include "lib/tty/key.h"        /* XCTRL and ALT macros  */
45 #include "lib/fileloc.h"
46 #include "lib/skin.h"
47 #include "lib/strutil.h"
48 #include "lib/util.h"
49 #include "lib/widget.h"
50 #include "lib/event.h"          /* mc_event_raise() */
51 #include "lib/mcconfig.h"       /* mc_config_history_*() */
52 
53 /*** global variables ****************************************************************************/
54 
55 gboolean quote = FALSE;
56 
57 const global_keymap_t *input_map = NULL;
58 
59 /* Color styles for input widgets */
60 input_colors_t input_colors;
61 
62 /*** file scope macro definitions ****************************************************************/
63 
64 #define LARGE_HISTORY_BUTTON 1
65 
66 #ifdef LARGE_HISTORY_BUTTON
67 #define HISTORY_BUTTON_WIDTH 3
68 #else
69 #define HISTORY_BUTTON_WIDTH 1
70 #endif
71 
72 #define should_show_history_button(in) \
73     (in->history.list != NULL && WIDGET (in)->cols > HISTORY_BUTTON_WIDTH * 2 + 1 \
74          && WIDGET (in)->owner != NULL)
75 
76 /*** file scope type declarations ****************************************************************/
77 
78 /*** file scope variables ************************************************************************/
79 
80 /* Input widgets have a global kill ring */
81 /* Pointer to killed data */
82 static char *kill_buffer = NULL;
83 
84 /*** file scope functions ************************************************************************/
85 /* --------------------------------------------------------------------------------------------- */
86 
87 static size_t
get_history_length(GList * history)88 get_history_length (GList * history)
89 {
90     size_t len = 0;
91 
92     for (; history != NULL; history = g_list_previous (history))
93         len++;
94 
95     return len;
96 }
97 
98 /* --------------------------------------------------------------------------------------------- */
99 
100 static void
draw_history_button(WInput * in)101 draw_history_button (WInput * in)
102 {
103     char c;
104     gboolean disabled;
105 
106     if (g_list_next (in->history.current) == NULL)
107         c = '^';
108     else if (g_list_previous (in->history.current) == NULL)
109         c = 'v';
110     else
111         c = '|';
112 
113     widget_gotoyx (in, 0, WIDGET (in)->cols - HISTORY_BUTTON_WIDTH);
114     disabled = widget_get_state (WIDGET (in), WST_DISABLED);
115     tty_setcolor (disabled ? DISABLED_COLOR : in->color[WINPUTC_HISTORY]);
116 
117 #ifdef LARGE_HISTORY_BUTTON
118     tty_print_string ("[ ]");
119     widget_gotoyx (in, 0, WIDGET (in)->cols - HISTORY_BUTTON_WIDTH + 1);
120 #endif
121 
122     tty_print_char (c);
123 }
124 
125 /* --------------------------------------------------------------------------------------------- */
126 
127 static void
input_mark_cmd(WInput * in,gboolean mark)128 input_mark_cmd (WInput * in, gboolean mark)
129 {
130     in->mark = mark ? in->point : -1;
131 }
132 
133 /* --------------------------------------------------------------------------------------------- */
134 
135 static gboolean
input_eval_marks(WInput * in,long * start_mark,long * end_mark)136 input_eval_marks (WInput * in, long *start_mark, long *end_mark)
137 {
138     if (in->mark >= 0)
139     {
140         *start_mark = MIN (in->mark, in->point);
141         *end_mark = MAX (in->mark, in->point);
142         return TRUE;
143     }
144 
145     *start_mark = *end_mark = -1;
146     return FALSE;
147 }
148 
149 /* --------------------------------------------------------------------------------------------- */
150 
151 static void
delete_region(WInput * in,int x_first,int x_last)152 delete_region (WInput * in, int x_first, int x_last)
153 {
154     int first = MIN (x_first, x_last);
155     int last = MAX (x_first, x_last);
156 
157     input_mark_cmd (in, FALSE);
158     in->point = first;
159     last = str_offset_to_pos (in->buffer, last);
160     first = str_offset_to_pos (in->buffer, first);
161     str_move (in->buffer + first, in->buffer + last);
162     in->charpoint = 0;
163     in->need_push = TRUE;
164 }
165 
166 /* --------------------------------------------------------------------------------------------- */
167 
168 static void
do_show_hist(WInput * in)169 do_show_hist (WInput * in)
170 {
171     size_t len;
172     history_descriptor_t hd;
173 
174     len = get_history_length (in->history.list);
175 
176     history_descriptor_init (&hd, WIDGET (in)->y, WIDGET (in)->x, in->history.list,
177                              g_list_position (in->history.list, in->history.list));
178     history_show (&hd);
179 
180     /* in->history.list was destroyed in history_show().
181      * Apply new history and current postition to avoid use-after-free. */
182     in->history.list = hd.list;
183     in->history.current = in->history.list;
184     if (hd.text != NULL)
185     {
186         input_assign_text (in, hd.text);
187         g_free (hd.text);
188     }
189 
190     /* Has history cleaned up or not? */
191     if (len != get_history_length (in->history.list))
192         in->history.changed = TRUE;
193 }
194 
195 /* --------------------------------------------------------------------------------------------- */
196 /**
197  * Strip password from incomplete url (just user:pass@host without VFS prefix).
198  *
199  * @param url partial URL
200  * @return newly allocated string without password
201  */
202 
203 static char *
input_history_strip_password(char * url)204 input_history_strip_password (char *url)
205 {
206     char *at, *delim, *colon;
207 
208     at = strrchr (url, '@');
209     if (at == NULL)
210         return g_strdup (url);
211 
212     /* TODO: handle ':' and '@' in password */
213 
214     delim = strstr (url, VFS_PATH_URL_DELIMITER);
215     if (delim != NULL)
216         colon = strchr (delim + strlen (VFS_PATH_URL_DELIMITER), ':');
217     else
218         colon = strchr (url, ':');
219 
220     /* if 'colon' before 'at', 'colon' delimits user and password: user:password@host */
221     /* if 'colon' after 'at', 'colon' delimits host and port: user@host:port */
222     if (colon != NULL && colon > at)
223         colon = NULL;
224 
225     if (colon == NULL)
226         return g_strdup (url);
227     *colon = '\0';
228 
229     return g_strconcat (url, at, (char *) NULL);
230 }
231 
232 /* --------------------------------------------------------------------------------------------- */
233 
234 static void
push_history(WInput * in,const char * text)235 push_history (WInput * in, const char *text)
236 {
237     char *t;
238     gboolean empty;
239 
240     if (text == NULL)
241         return;
242 
243     t = g_strstrip (g_strdup (text));
244     empty = *t == '\0';
245     g_free (t);
246     t = g_strdup (empty ? "" : text);
247 
248     if (!empty && in->history.name != NULL && in->strip_password)
249     {
250         /*
251            We got string user:pass@host without any VFS prefixes
252            and vfs_path_to_str_flags (t, VPF_STRIP_PASSWORD) doesn't work.
253            Therefore we want to strip password in separate algorithm
254          */
255         char *url_with_stripped_password;
256 
257         url_with_stripped_password = input_history_strip_password (t);
258         g_free (t);
259         t = url_with_stripped_password;
260     }
261 
262     if (in->history.list == NULL || in->history.list->data == NULL
263         || strcmp (in->history.list->data, t) != 0 || in->history.changed)
264     {
265         in->history.list = list_append_unique (in->history.list, t);
266         in->history.current = in->history.list;
267         in->history.changed = TRUE;
268     }
269     else
270         g_free (t);
271 
272     in->need_push = FALSE;
273 }
274 
275 /* --------------------------------------------------------------------------------------------- */
276 
277 static void
move_buffer_backward(WInput * in,int start,int end)278 move_buffer_backward (WInput * in, int start, int end)
279 {
280     int i, pos, len;
281     int str_len;
282 
283     str_len = str_length (in->buffer);
284     if (start >= str_len || end > str_len + 1)
285         return;
286 
287     pos = str_offset_to_pos (in->buffer, start);
288     len = str_offset_to_pos (in->buffer, end) - pos;
289 
290     for (i = pos; in->buffer[i + len - 1]; i++)
291         in->buffer[i] = in->buffer[i + len];
292 }
293 
294 /* --------------------------------------------------------------------------------------------- */
295 
296 static cb_ret_t
insert_char(WInput * in,int c_code)297 insert_char (WInput * in, int c_code)
298 {
299     int res;
300     long m1, m2;
301 
302     if (input_eval_marks (in, &m1, &m2))
303         delete_region (in, m1, m2);
304 
305     if (c_code == -1)
306         return MSG_NOT_HANDLED;
307 
308     if (in->charpoint >= MB_LEN_MAX)
309         return MSG_HANDLED;
310 
311     in->charbuf[in->charpoint] = c_code;
312     in->charpoint++;
313 
314     res = str_is_valid_char (in->charbuf, in->charpoint);
315     if (res < 0)
316     {
317         if (res != -2)
318             in->charpoint = 0;  /* broken multibyte char, skip */
319         return MSG_HANDLED;
320     }
321 
322     in->need_push = TRUE;
323     if (strlen (in->buffer) + 1 + in->charpoint >= in->current_max_size)
324     {
325         /* Expand the buffer */
326         size_t new_length;
327         char *narea;
328 
329         new_length = in->current_max_size + WIDGET (in)->cols + in->charpoint;
330         narea = g_try_renew (char, in->buffer, new_length);
331         if (narea != NULL)
332         {
333             in->buffer = narea;
334             in->current_max_size = new_length;
335         }
336     }
337 
338     if (strlen (in->buffer) + in->charpoint < in->current_max_size)
339     {
340         size_t i;
341         /* bytes from begin */
342         size_t ins_point = str_offset_to_pos (in->buffer, in->point);
343         /* move chars */
344         size_t rest_bytes = strlen (in->buffer + ins_point);
345 
346         for (i = rest_bytes + 1; i > 0; i--)
347             in->buffer[ins_point + i + in->charpoint - 1] = in->buffer[ins_point + i - 1];
348 
349         memcpy (in->buffer + ins_point, in->charbuf, in->charpoint);
350         in->point++;
351     }
352 
353     in->charpoint = 0;
354     return MSG_HANDLED;
355 }
356 
357 /* --------------------------------------------------------------------------------------------- */
358 
359 static void
beginning_of_line(WInput * in)360 beginning_of_line (WInput * in)
361 {
362     in->point = 0;
363     in->charpoint = 0;
364 }
365 
366 /* --------------------------------------------------------------------------------------------- */
367 
368 static void
end_of_line(WInput * in)369 end_of_line (WInput * in)
370 {
371     in->point = str_length (in->buffer);
372     in->charpoint = 0;
373 }
374 
375 /* --------------------------------------------------------------------------------------------- */
376 
377 static void
backward_char(WInput * in)378 backward_char (WInput * in)
379 {
380     const char *act;
381 
382     act = in->buffer + str_offset_to_pos (in->buffer, in->point);
383     if (in->point > 0)
384         in->point -= str_cprev_noncomb_char (&act, in->buffer);
385     in->charpoint = 0;
386 }
387 
388 /* --------------------------------------------------------------------------------------------- */
389 
390 static void
forward_char(WInput * in)391 forward_char (WInput * in)
392 {
393     const char *act;
394 
395     act = in->buffer + str_offset_to_pos (in->buffer, in->point);
396     if (act[0] != '\0')
397         in->point += str_cnext_noncomb_char (&act);
398     in->charpoint = 0;
399 }
400 
401 /* --------------------------------------------------------------------------------------------- */
402 
403 static void
forward_word(WInput * in)404 forward_word (WInput * in)
405 {
406     const char *p;
407 
408     p = in->buffer + str_offset_to_pos (in->buffer, in->point);
409     while (p[0] != '\0' && (str_isspace (p) || str_ispunct (p)))
410     {
411         str_cnext_char (&p);
412         in->point++;
413     }
414     while (p[0] != '\0' && !str_isspace (p) && !str_ispunct (p))
415     {
416         str_cnext_char (&p);
417         in->point++;
418     }
419 }
420 
421 /* --------------------------------------------------------------------------------------------- */
422 
423 static void
backward_word(WInput * in)424 backward_word (WInput * in)
425 {
426     const char *p;
427 
428     p = in->buffer + str_offset_to_pos (in->buffer, in->point);
429 
430     while (p != in->buffer)
431     {
432         const char *p_tmp;
433 
434         p_tmp = p;
435         str_cprev_char (&p);
436         if (!str_isspace (p) && !str_ispunct (p))
437         {
438             p = p_tmp;
439             break;
440         }
441         in->point--;
442     }
443     while (p != in->buffer)
444     {
445         str_cprev_char (&p);
446         if (str_isspace (p) || str_ispunct (p))
447             break;
448 
449         in->point--;
450     }
451 }
452 
453 /* --------------------------------------------------------------------------------------------- */
454 
455 static void
backward_delete(WInput * in)456 backward_delete (WInput * in)
457 {
458     const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
459     int start;
460 
461     if (in->point == 0)
462         return;
463 
464     start = in->point - str_cprev_noncomb_char (&act, in->buffer);
465     move_buffer_backward (in, start, in->point);
466     in->charpoint = 0;
467     in->need_push = TRUE;
468     in->point = start;
469 }
470 
471 /* --------------------------------------------------------------------------------------------- */
472 
473 static void
delete_char(WInput * in)474 delete_char (WInput * in)
475 {
476     const char *act;
477     int end = in->point;
478 
479     act = in->buffer + str_offset_to_pos (in->buffer, in->point);
480     end += str_cnext_noncomb_char (&act);
481 
482     move_buffer_backward (in, in->point, end);
483     in->charpoint = 0;
484     in->need_push = TRUE;
485 }
486 
487 /* --------------------------------------------------------------------------------------------- */
488 
489 static void
copy_region(WInput * in,int x_first,int x_last)490 copy_region (WInput * in, int x_first, int x_last)
491 {
492     int first = MIN (x_first, x_last);
493     int last = MAX (x_first, x_last);
494 
495     if (last == first)
496     {
497         /* Copy selected files to clipboard */
498         mc_event_raise (MCEVENT_GROUP_FILEMANAGER, "panel_save_current_file_to_clip_file", NULL);
499         /* try use external clipboard utility */
500         mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
501         return;
502     }
503 
504     g_free (kill_buffer);
505 
506     first = str_offset_to_pos (in->buffer, first);
507     last = str_offset_to_pos (in->buffer, last);
508 
509     kill_buffer = g_strndup (in->buffer + first, last - first);
510 
511     mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file", kill_buffer);
512     /* try use external clipboard utility */
513     mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
514 }
515 
516 /* --------------------------------------------------------------------------------------------- */
517 
518 static void
kill_word(WInput * in)519 kill_word (WInput * in)
520 {
521     int old_point = in->point;
522     int new_point;
523 
524     forward_word (in);
525     new_point = in->point;
526     in->point = old_point;
527 
528     delete_region (in, old_point, new_point);
529     in->need_push = TRUE;
530     in->charpoint = 0;
531 }
532 
533 /* --------------------------------------------------------------------------------------------- */
534 
535 static void
back_kill_word(WInput * in)536 back_kill_word (WInput * in)
537 {
538     int old_point = in->point;
539     int new_point;
540 
541     backward_word (in);
542     new_point = in->point;
543     in->point = old_point;
544 
545     delete_region (in, old_point, new_point);
546     in->need_push = TRUE;
547 }
548 
549 /* --------------------------------------------------------------------------------------------- */
550 
551 static void
yank(WInput * in)552 yank (WInput * in)
553 {
554     if (kill_buffer != NULL)
555     {
556         char *p;
557 
558         in->charpoint = 0;
559         for (p = kill_buffer; *p != '\0'; p++)
560             insert_char (in, *p);
561         in->charpoint = 0;
562     }
563 }
564 
565 /* --------------------------------------------------------------------------------------------- */
566 
567 static void
kill_line(WInput * in)568 kill_line (WInput * in)
569 {
570     int chp;
571 
572     chp = str_offset_to_pos (in->buffer, in->point);
573     g_free (kill_buffer);
574     kill_buffer = g_strdup (&in->buffer[chp]);
575     in->buffer[chp] = '\0';
576     in->charpoint = 0;
577 }
578 
579 /* --------------------------------------------------------------------------------------------- */
580 
581 static void
clear_line(WInput * in)582 clear_line (WInput * in)
583 {
584     in->need_push = TRUE;
585     in->buffer[0] = '\0';
586     in->point = 0;
587     in->mark = -1;
588     in->charpoint = 0;
589 }
590 
591 /* --------------------------------------------------------------------------------------------- */
592 
593 static void
ins_from_clip(WInput * in)594 ins_from_clip (WInput * in)
595 {
596     char *p = NULL;
597     ev_clipboard_text_from_file_t event_data;
598 
599     /* try use external clipboard utility */
600     mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_from_ext_clip", NULL);
601 
602     event_data.text = &p;
603     mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_from_file", &event_data);
604     if (event_data.ret)
605     {
606         char *pp;
607 
608         for (pp = p; *pp != '\0'; pp++)
609             insert_char (in, *pp);
610 
611         g_free (p);
612     }
613 }
614 
615 /* --------------------------------------------------------------------------------------------- */
616 
617 static void
hist_prev(WInput * in)618 hist_prev (WInput * in)
619 {
620     GList *prev;
621 
622     if (in->history.list == NULL)
623         return;
624 
625     if (in->need_push)
626         push_history (in, in->buffer);
627 
628     prev = g_list_previous (in->history.current);
629     if (prev != NULL)
630     {
631         input_assign_text (in, (char *) prev->data);
632         in->history.current = prev;
633         in->history.changed = TRUE;
634         in->need_push = FALSE;
635     }
636 }
637 
638 /* --------------------------------------------------------------------------------------------- */
639 
640 static void
hist_next(WInput * in)641 hist_next (WInput * in)
642 {
643     GList *next;
644 
645     if (in->need_push)
646     {
647         push_history (in, in->buffer);
648         input_assign_text (in, "");
649         return;
650     }
651 
652     if (in->history.list == NULL)
653         return;
654 
655     next = g_list_next (in->history.current);
656     if (next == NULL)
657     {
658         input_assign_text (in, "");
659         in->history.current = in->history.list;
660     }
661     else
662     {
663         input_assign_text (in, (char *) next->data);
664         in->history.current = next;
665         in->history.changed = TRUE;
666         in->need_push = FALSE;
667     }
668 }
669 
670 /* --------------------------------------------------------------------------------------------- */
671 
672 static void
port_region_marked_for_delete(WInput * in)673 port_region_marked_for_delete (WInput * in)
674 {
675     in->buffer[0] = '\0';
676     in->point = 0;
677     in->first = FALSE;
678     in->charpoint = 0;
679 }
680 
681 /* --------------------------------------------------------------------------------------------- */
682 
683 static cb_ret_t
input_execute_cmd(WInput * in,long command)684 input_execute_cmd (WInput * in, long command)
685 {
686     cb_ret_t res = MSG_HANDLED;
687 
688     switch (command)
689     {
690     case CK_MarkLeft:
691     case CK_MarkRight:
692     case CK_MarkToWordBegin:
693     case CK_MarkToWordEnd:
694     case CK_MarkToHome:
695     case CK_MarkToEnd:
696         /* a highlight command like shift-arrow */
697         if (in->mark < 0)
698         {
699             input_mark_cmd (in, FALSE); /* clear */
700             input_mark_cmd (in, TRUE);  /* marking on */
701         }
702         break;
703     case CK_WordRight:
704     case CK_WordLeft:
705     case CK_Right:
706     case CK_Left:
707         if (in->mark >= 0)
708             input_mark_cmd (in, FALSE);
709         break;
710     default:
711         break;
712     }
713 
714     switch (command)
715     {
716     case CK_Home:
717     case CK_MarkToHome:
718         beginning_of_line (in);
719         break;
720     case CK_End:
721     case CK_MarkToEnd:
722         end_of_line (in);
723         break;
724     case CK_Left:
725     case CK_MarkLeft:
726         backward_char (in);
727         break;
728     case CK_WordLeft:
729     case CK_MarkToWordBegin:
730         backward_word (in);
731         break;
732     case CK_Right:
733     case CK_MarkRight:
734         forward_char (in);
735         break;
736     case CK_WordRight:
737     case CK_MarkToWordEnd:
738         forward_word (in);
739         break;
740     case CK_BackSpace:
741         {
742             long m1, m2;
743 
744             if (input_eval_marks (in, &m1, &m2))
745                 delete_region (in, m1, m2);
746             else
747                 backward_delete (in);
748         }
749         break;
750     case CK_Delete:
751         if (in->first)
752             port_region_marked_for_delete (in);
753         else
754         {
755             long m1, m2;
756 
757             if (input_eval_marks (in, &m1, &m2))
758                 delete_region (in, m1, m2);
759             else
760                 delete_char (in);
761         }
762         break;
763     case CK_DeleteToWordEnd:
764         kill_word (in);
765         break;
766     case CK_DeleteToWordBegin:
767         back_kill_word (in);
768         break;
769     case CK_Mark:
770         input_mark_cmd (in, TRUE);
771         break;
772     case CK_Remove:
773         delete_region (in, in->point, MAX (in->mark, 0));
774         break;
775     case CK_DeleteToEnd:
776         kill_line (in);
777         break;
778     case CK_Clear:
779         clear_line (in);
780         break;
781     case CK_Store:
782         copy_region (in, MAX (in->mark, 0), in->point);
783         break;
784     case CK_Cut:
785         {
786             long m;
787 
788             m = MAX (in->mark, 0);
789             copy_region (in, m, in->point);
790             delete_region (in, in->point, m);
791         }
792         break;
793     case CK_Yank:
794         yank (in);
795         break;
796     case CK_Paste:
797         ins_from_clip (in);
798         break;
799     case CK_HistoryPrev:
800         hist_prev (in);
801         break;
802     case CK_HistoryNext:
803         hist_next (in);
804         break;
805     case CK_History:
806         do_show_hist (in);
807         break;
808     case CK_Complete:
809         input_complete (in);
810         break;
811     default:
812         res = MSG_NOT_HANDLED;
813     }
814 
815     switch (command)
816     {
817     case CK_MarkLeft:
818     case CK_MarkRight:
819     case CK_MarkToWordBegin:
820     case CK_MarkToWordEnd:
821     case CK_MarkToHome:
822     case CK_MarkToEnd:
823         /* do nothing */
824         break;
825     default:
826         in->mark = -1;
827         break;
828     }
829 
830     return res;
831 }
832 
833 /* --------------------------------------------------------------------------------------------- */
834 
835 /* "history_load" event handler */
836 static gboolean
input_load_history(const gchar * event_group_name,const gchar * event_name,gpointer init_data,gpointer data)837 input_load_history (const gchar * event_group_name, const gchar * event_name,
838                     gpointer init_data, gpointer data)
839 {
840     WInput *in = INPUT (init_data);
841     ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
842 
843     (void) event_group_name;
844     (void) event_name;
845 
846     in->history.list = mc_config_history_load (ev->cfg, in->history.name);
847     in->history.current = in->history.list;
848 
849     if (in->init_from_history)
850     {
851         const char *def_text = "";
852 
853         if (in->history.list != NULL && in->history.list->data != NULL)
854             def_text = (const char *) in->history.list->data;
855 
856         input_assign_text (in, def_text);
857     }
858 
859     return TRUE;
860 }
861 
862 /* --------------------------------------------------------------------------------------------- */
863 
864 /* "history_save" event handler */
865 static gboolean
input_save_history(const gchar * event_group_name,const gchar * event_name,gpointer init_data,gpointer data)866 input_save_history (const gchar * event_group_name, const gchar * event_name,
867                     gpointer init_data, gpointer data)
868 {
869     WInput *in = INPUT (init_data);
870 
871     (void) event_group_name;
872     (void) event_name;
873 
874     if (!in->is_password && (DIALOG (WIDGET (in)->owner)->ret_value != B_CANCEL))
875     {
876         ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
877 
878         push_history (in, in->buffer);
879         if (in->history.changed)
880             mc_config_history_save (ev->cfg, in->history.name, in->history.list);
881         in->history.changed = FALSE;
882     }
883 
884     return TRUE;
885 }
886 
887 /* --------------------------------------------------------------------------------------------- */
888 
889 static void
input_destroy(WInput * in)890 input_destroy (WInput * in)
891 {
892     if (in == NULL)
893     {
894         fprintf (stderr, "Internal error: null Input *\n");
895         exit (EXIT_FAILURE);
896     }
897 
898     input_complete_free (in);
899 
900     /* clean history */
901     if (in->history.list != NULL)
902     {
903         /* history is already saved before this moment */
904         in->history.list = g_list_first (in->history.list);
905         g_list_free_full (in->history.list, g_free);
906     }
907     g_free (in->history.name);
908     g_free (in->buffer);
909     MC_PTR_FREE (kill_buffer);
910 }
911 
912 /* --------------------------------------------------------------------------------------------- */
913 
914 /**
915  * Calculates the buffer index (aka "point") corresponding to some screen coordinate.
916  */
917 static int
input_screen_to_point(const WInput * in,int x)918 input_screen_to_point (const WInput * in, int x)
919 {
920     x += in->term_first_shown;
921 
922     if (x < 0)
923         return 0;
924 
925     if (x < str_term_width1 (in->buffer))
926         return str_column_to_pos (in->buffer, x);
927 
928     return str_length (in->buffer);
929 }
930 
931 /* --------------------------------------------------------------------------------------------- */
932 
933 static void
input_mouse_callback(Widget * w,mouse_msg_t msg,mouse_event_t * event)934 input_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
935 {
936     /* save point between MSG_MOUSE_DOWN and MSG_MOUSE_DRAG */
937     static int prev_point = 0;
938     WInput *in = INPUT (w);
939 
940     switch (msg)
941     {
942     case MSG_MOUSE_DOWN:
943         widget_select (w);
944 
945         if (event->x >= w->cols - HISTORY_BUTTON_WIDTH && should_show_history_button (in))
946             do_show_hist (in);
947         else
948         {
949             in->first = FALSE;
950             input_mark_cmd (in, FALSE);
951             input_set_point (in, input_screen_to_point (in, event->x));
952             /* save point for the possible following MSG_MOUSE_DRAG action */
953             prev_point = in->point;
954         }
955         break;
956 
957     case MSG_MOUSE_DRAG:
958         /* start point: set marker using point before first MSG_MOUSE_DRAG action */
959         if (in->mark < 0)
960             in->mark = prev_point;
961 
962         input_set_point (in, input_screen_to_point (in, event->x));
963         break;
964 
965     default:
966         /* don't create highlight region of 0 length */
967         if (in->mark == in->point)
968             input_mark_cmd (in, FALSE);
969         break;
970     }
971 }
972 
973 /* --------------------------------------------------------------------------------------------- */
974 /*** public functions ****************************************************************************/
975 /* --------------------------------------------------------------------------------------------- */
976 
977 /** Create new instance of WInput object.
978   * @param y                    Y coordinate
979   * @param x                    X coordinate
980   * @param input_colors         Array of used colors
981   * @param width                Widget width
982   * @param def_text             Default text filled in widget
983   * @param histname             Name of history
984   * @param completion_flags     Flags for specify type of completions
985   * @return                     WInput object
986   */
987 WInput *
input_new(int y,int x,const int * colors,int width,const char * def_text,const char * histname,input_complete_t completion_flags)988 input_new (int y, int x, const int *colors, int width, const char *def_text,
989            const char *histname, input_complete_t completion_flags)
990 {
991     WInput *in;
992     Widget *w;
993 
994     in = g_new (WInput, 1);
995     w = WIDGET (in);
996     widget_init (w, y, x, 1, width, input_callback, input_mouse_callback);
997     w->options |= WOP_SELECTABLE | WOP_IS_INPUT | WOP_WANT_CURSOR;
998     w->keymap = input_map;
999 
1000     in->color = colors;
1001     in->first = TRUE;
1002     in->mark = -1;
1003     in->term_first_shown = 0;
1004     in->disable_update = 0;
1005     in->is_password = FALSE;
1006     in->strip_password = FALSE;
1007 
1008     /* in->buffer will be corrected in "history_load" event handler */
1009     in->current_max_size = width + 1;
1010     in->buffer = g_new0 (char, in->current_max_size);
1011 
1012     /* init completions before input_assign_text() call */
1013     in->completions = NULL;
1014     in->completion_flags = completion_flags;
1015 
1016     in->init_from_history = (def_text == INPUT_LAST_TEXT);
1017 
1018     if (in->init_from_history || def_text == NULL)
1019         def_text = "";
1020 
1021     input_assign_text (in, def_text);
1022 
1023     /* prepare to history setup */
1024     in->history.list = NULL;
1025     in->history.current = NULL;
1026     in->history.changed = FALSE;
1027     in->history.name = NULL;
1028     if ((histname != NULL) && (*histname != '\0'))
1029         in->history.name = g_strdup (histname);
1030     /* history will be loaded later */
1031 
1032     in->label = NULL;
1033 
1034     return in;
1035 }
1036 
1037 /* --------------------------------------------------------------------------------------------- */
1038 
1039 cb_ret_t
input_callback(Widget * w,Widget * sender,widget_msg_t msg,int parm,void * data)1040 input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
1041 {
1042     WInput *in = INPUT (w);
1043     WDialog *h = DIALOG (w->owner);
1044     cb_ret_t v;
1045 
1046     switch (msg)
1047     {
1048     case MSG_INIT:
1049         /* subscribe to "history_load" event */
1050         mc_event_add (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w, NULL);
1051         /* subscribe to "history_save" event */
1052         mc_event_add (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w, NULL);
1053         if (in->label != NULL)
1054             widget_set_state (WIDGET (in->label), WST_DISABLED, widget_get_state (w, WST_DISABLED));
1055         return MSG_HANDLED;
1056 
1057     case MSG_KEY:
1058         if (parm == XCTRL ('q'))
1059         {
1060             quote = TRUE;
1061             v = input_handle_char (in, ascii_alpha_to_cntrl (tty_getch ()));
1062             quote = FALSE;
1063             return v;
1064         }
1065 
1066         /* Keys we want others to handle */
1067         if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR
1068             || parm == KEY_F (10) || parm == '\n')
1069             return MSG_NOT_HANDLED;
1070 
1071         /* When pasting multiline text, insert literal Enter */
1072         if ((parm & ~KEY_M_MASK) == '\n')
1073         {
1074             quote = TRUE;
1075             v = input_handle_char (in, '\n');
1076             quote = FALSE;
1077             return v;
1078         }
1079 
1080         return input_handle_char (in, parm);
1081 
1082     case MSG_ACTION:
1083         return input_execute_cmd (in, parm);
1084 
1085     case MSG_DRAW:
1086         input_update (in, FALSE);
1087         return MSG_HANDLED;
1088 
1089     case MSG_ENABLE:
1090     case MSG_DISABLE:
1091         if (in->label != NULL)
1092             widget_set_state (WIDGET (in->label), WST_DISABLED, msg == MSG_DISABLE);
1093         return MSG_HANDLED;
1094 
1095     case MSG_CURSOR:
1096         widget_gotoyx (in, 0, str_term_width2 (in->buffer, in->point) - in->term_first_shown);
1097         return MSG_HANDLED;
1098 
1099     case MSG_DESTROY:
1100         /* unsubscribe from "history_load" event */
1101         mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w);
1102         /* unsubscribe from "history_save" event */
1103         mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w);
1104         input_destroy (in);
1105         return MSG_HANDLED;
1106 
1107     default:
1108         return widget_default_callback (w, sender, msg, parm, data);
1109     }
1110 }
1111 
1112 /* --------------------------------------------------------------------------------------------- */
1113 
1114 void
input_set_default_colors(void)1115 input_set_default_colors (void)
1116 {
1117     input_colors[WINPUTC_MAIN] = INPUT_COLOR;
1118     input_colors[WINPUTC_MARK] = INPUT_MARK_COLOR;
1119     input_colors[WINPUTC_UNCHANGED] = INPUT_UNCHANGED_COLOR;
1120     input_colors[WINPUTC_HISTORY] = INPUT_HISTORY_COLOR;
1121 }
1122 
1123 /* --------------------------------------------------------------------------------------------- */
1124 
1125 cb_ret_t
input_handle_char(WInput * in,int key)1126 input_handle_char (WInput * in, int key)
1127 {
1128     cb_ret_t v;
1129     long command;
1130 
1131     if (quote)
1132     {
1133         input_complete_free (in);
1134         v = insert_char (in, key);
1135         input_update (in, TRUE);
1136         quote = FALSE;
1137         return v;
1138     }
1139 
1140     command = widget_lookup_key (WIDGET (in), key);
1141     if (command == CK_IgnoreKey)
1142     {
1143         if (key > 255)
1144             return MSG_NOT_HANDLED;
1145         if (in->first)
1146             port_region_marked_for_delete (in);
1147         input_complete_free (in);
1148         v = insert_char (in, key);
1149         input_update (in, TRUE);
1150     }
1151     else
1152     {
1153         gboolean keep_first;
1154 
1155         if (command != CK_Complete)
1156             input_complete_free (in);
1157         input_execute_cmd (in, command);
1158         v = MSG_HANDLED;
1159         /* if in->first == TRUE and history or completion window was cancelled,
1160            keep "first" state */
1161         keep_first = in->first && (command == CK_History || command == CK_Complete);
1162         input_update (in, !keep_first);
1163     }
1164 
1165     return v;
1166 }
1167 
1168 /* --------------------------------------------------------------------------------------------- */
1169 
1170 void
input_assign_text(WInput * in,const char * text)1171 input_assign_text (WInput * in, const char *text)
1172 {
1173     Widget *w = WIDGET (in);
1174     size_t text_len, buffer_len;
1175 
1176     if (text == NULL)
1177         text = "";
1178 
1179     input_complete_free (in);
1180     in->mark = -1;
1181     in->need_push = TRUE;
1182     in->charpoint = 0;
1183 
1184     text_len = strlen (text);
1185     buffer_len = 1 + MAX ((size_t) w->cols, text_len);
1186     in->current_max_size = buffer_len;
1187     if (buffer_len > (size_t) w->cols)
1188         in->buffer = g_realloc (in->buffer, buffer_len);
1189     memmove (in->buffer, text, text_len + 1);
1190     in->point = str_length (in->buffer);
1191     input_update (in, TRUE);
1192 }
1193 
1194 /* --------------------------------------------------------------------------------------------- */
1195 
1196 gboolean
input_is_empty(const WInput * in)1197 input_is_empty (const WInput * in)
1198 {
1199     return (in == NULL || in->buffer == NULL || in->buffer[0] == '\0');
1200 }
1201 
1202 /* --------------------------------------------------------------------------------------------- */
1203 
1204 /* Inserts text in input line */
1205 void
input_insert(WInput * in,const char * text,gboolean insert_extra_space)1206 input_insert (WInput * in, const char *text, gboolean insert_extra_space)
1207 {
1208     input_disable_update (in);
1209     while (*text != '\0')
1210         input_handle_char (in, (unsigned char) *text++);        /* unsigned extension char->int */
1211     if (insert_extra_space)
1212         input_handle_char (in, ' ');
1213     input_enable_update (in);
1214     input_update (in, TRUE);
1215 }
1216 
1217 /* --------------------------------------------------------------------------------------------- */
1218 
1219 void
input_set_point(WInput * in,int pos)1220 input_set_point (WInput * in, int pos)
1221 {
1222     int max_pos;
1223 
1224     max_pos = str_length (in->buffer);
1225     pos = MIN (pos, max_pos);
1226     if (pos != in->point)
1227         input_complete_free (in);
1228     in->point = pos;
1229     in->charpoint = 0;
1230     input_update (in, TRUE);
1231 }
1232 
1233 /* --------------------------------------------------------------------------------------------- */
1234 
1235 void
input_update(WInput * in,gboolean clear_first)1236 input_update (WInput * in, gboolean clear_first)
1237 {
1238     Widget *w = WIDGET (in);
1239     int has_history = 0;
1240     int buf_len;
1241     const char *cp;
1242     int pw;
1243 
1244     if (in->disable_update != 0)
1245         return;
1246 
1247     /* don't draw widget not put into dialog */
1248     if (w->owner == NULL || !widget_get_state (WIDGET (w->owner), WST_ACTIVE))
1249         return;
1250 
1251     if (clear_first)
1252         in->first = FALSE;
1253 
1254     if (should_show_history_button (in))
1255         has_history = HISTORY_BUTTON_WIDTH;
1256 
1257     buf_len = str_length (in->buffer);
1258 
1259     /* Adjust the mark */
1260     in->mark = MIN (in->mark, buf_len);
1261 
1262     pw = str_term_width2 (in->buffer, in->point);
1263 
1264     /* Make the point visible */
1265     if ((pw < in->term_first_shown) || (pw >= in->term_first_shown + w->cols - has_history))
1266     {
1267         in->term_first_shown = pw - (w->cols / 3);
1268         if (in->term_first_shown < 0)
1269             in->term_first_shown = 0;
1270     }
1271 
1272     if (has_history != 0)
1273         draw_history_button (in);
1274 
1275     if (widget_get_state (w, WST_DISABLED))
1276         tty_setcolor (DISABLED_COLOR);
1277     else if (in->first)
1278         tty_setcolor (in->color[WINPUTC_UNCHANGED]);
1279     else
1280         tty_setcolor (in->color[WINPUTC_MAIN]);
1281 
1282     widget_gotoyx (in, 0, 0);
1283 
1284     if (!in->is_password)
1285     {
1286         if (in->mark < 0)
1287             tty_print_string (str_term_substring (in->buffer, in->term_first_shown,
1288                                                   w->cols - has_history));
1289         else
1290         {
1291             long m1, m2;
1292 
1293             if (input_eval_marks (in, &m1, &m2))
1294             {
1295                 tty_setcolor (in->color[WINPUTC_MAIN]);
1296                 cp = str_term_substring (in->buffer, in->term_first_shown, w->cols - has_history);
1297                 tty_print_string (cp);
1298                 tty_setcolor (in->color[WINPUTC_MARK]);
1299                 if (m1 < in->term_first_shown)
1300                 {
1301                     widget_gotoyx (in, 0, 0);
1302                     tty_print_string (str_term_substring
1303                                       (in->buffer, in->term_first_shown,
1304                                        m2 - in->term_first_shown));
1305                 }
1306                 else
1307                 {
1308                     int sel_width, buf_width;
1309 
1310                     widget_gotoyx (in, 0, m1 - in->term_first_shown);
1311                     buf_width = str_term_width2 (in->buffer, m1);
1312                     sel_width =
1313                         MIN (m2 - m1, (w->cols - has_history) - (buf_width - in->term_first_shown));
1314                     tty_print_string (str_term_substring (in->buffer, m1, sel_width));
1315                 }
1316             }
1317         }
1318     }
1319     else
1320     {
1321         int i;
1322 
1323         cp = str_term_substring (in->buffer, in->term_first_shown, w->cols - has_history);
1324         tty_setcolor (in->color[WINPUTC_MAIN]);
1325         for (i = 0; i < w->cols - has_history; i++)
1326         {
1327             if (i < (buf_len - in->term_first_shown) && cp[0] != '\0')
1328                 tty_print_char ('*');
1329             else
1330                 tty_print_char (' ');
1331             if (cp[0] != '\0')
1332                 str_cnext_char (&cp);
1333         }
1334     }
1335 }
1336 
1337 /* --------------------------------------------------------------------------------------------- */
1338 
1339 void
input_enable_update(WInput * in)1340 input_enable_update (WInput * in)
1341 {
1342     in->disable_update--;
1343     input_update (in, FALSE);
1344 }
1345 
1346 /* --------------------------------------------------------------------------------------------- */
1347 
1348 void
input_disable_update(WInput * in)1349 input_disable_update (WInput * in)
1350 {
1351     in->disable_update++;
1352 }
1353 
1354 /* --------------------------------------------------------------------------------------------- */
1355 
1356 /**
1357   *  Cleans the input line and adds the current text to the history
1358   *
1359   *  @param in the input line
1360   */
1361 void
input_clean(WInput * in)1362 input_clean (WInput * in)
1363 {
1364     push_history (in, in->buffer);
1365     in->need_push = TRUE;
1366     in->buffer[0] = '\0';
1367     in->point = 0;
1368     in->charpoint = 0;
1369     in->mark = -1;
1370     input_complete_free (in);
1371     input_update (in, FALSE);
1372 }
1373 
1374 /* --------------------------------------------------------------------------------------------- */
1375