Manual pages: mcmcdiffmceditmcview

root/lib/widget/input.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. get_history_length
  2. draw_history_button
  3. input_mark_cmd
  4. input_eval_marks
  5. do_show_hist
  6. input_history_strip_password
  7. input_push_history
  8. move_buffer_backward
  9. beginning_of_line
  10. end_of_line
  11. backward_char
  12. forward_char
  13. forward_word
  14. backward_word
  15. backward_delete
  16. copy_region
  17. delete_region
  18. insert_char
  19. delete_char
  20. kill_word
  21. back_kill_word
  22. yank
  23. kill_line
  24. clear_line
  25. ins_from_clip
  26. hist_prev
  27. hist_next
  28. port_region_marked_for_delete
  29. input_execute_cmd
  30. input_load_history
  31. input_save_history
  32. input_destroy
  33. input_screen_to_point
  34. input_mouse_callback
  35. input_new
  36. input_callback
  37. input_handle_char
  38. input_assign_text
  39. input_insert
  40. input_set_point
  41. input_update
  42. input_enable_update
  43. input_disable_update
  44. input_clean

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

/* [previous][next][first][last][top][bottom][index][help]  */