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. delete_region
  6. do_show_hist
  7. input_history_strip_password
  8. push_history
  9. move_buffer_backward
  10. insert_char
  11. beginning_of_line
  12. end_of_line
  13. backward_char
  14. forward_char
  15. forward_word
  16. backward_word
  17. backward_delete
  18. delete_char
  19. copy_region
  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_set_default_colors
  38. input_handle_char
  39. input_key_is_in_map
  40. input_assign_text
  41. input_is_empty
  42. input_insert
  43. input_set_point
  44. input_update
  45. input_enable_update
  46. input_disable_update
  47. input_clean
  48. input_free_completions

   1 /*
   2    Widgets for the Midnight Commander
   3 
   4    Copyright (C) 1994-2019
   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/keybind.h"        /* global_keymap_t */
  50 #include "lib/widget.h"
  51 #include "lib/event.h"          /* mc_event_raise() */
  52 #include "lib/mcconfig.h"       /* mc_config_history_*() */
  53 
  54 #include "input_complete.h"
  55 
  56 /*** global variables ****************************************************************************/
  57 
  58 gboolean quote = FALSE;
  59 
  60 const global_keymap_t *input_map = NULL;
  61 
  62 /* Color styles for input widgets */
  63 input_colors_t input_colors;
  64 
  65 /*** file scope macro definitions ****************************************************************/
  66 
  67 #define LARGE_HISTORY_BUTTON 1
  68 
  69 #ifdef LARGE_HISTORY_BUTTON
  70 #define HISTORY_BUTTON_WIDTH 3
  71 #else
  72 #define HISTORY_BUTTON_WIDTH 1
  73 #endif
  74 
  75 #define should_show_history_button(in) \
  76     (in->history.list != NULL && WIDGET (in)->cols > HISTORY_BUTTON_WIDTH * 2 + 1 \
  77          && WIDGET (in)->owner != NULL)
  78 
  79 /*** file scope type declarations ****************************************************************/
  80 
  81 /*** file scope variables ************************************************************************/
  82 
  83 /* Input widgets have a global kill ring */
  84 /* Pointer to killed data */
  85 static char *kill_buffer = NULL;
  86 
  87 /*** file scope functions ************************************************************************/
  88 /* --------------------------------------------------------------------------------------------- */
  89 
  90 static size_t
  91 get_history_length (const GList * history)
     /* [previous][next][first][last][top][bottom][index][help]  */
  92 {
  93     size_t len = 0;
  94 
  95     for (; history != NULL; history = (const GList *) g_list_previous (history))
  96         len++;
  97 
  98     return len;
  99 }
 100 
 101 /* --------------------------------------------------------------------------------------------- */
 102 
 103 static void
 104 draw_history_button (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 105 {
 106     char c;
 107     gboolean disabled;
 108 
 109     if (g_list_next (in->history.current) == NULL)
 110         c = '^';
 111     else if (g_list_previous (in->history.current) == NULL)
 112         c = 'v';
 113     else
 114         c = '|';
 115 
 116     widget_move (in, 0, WIDGET (in)->cols - HISTORY_BUTTON_WIDTH);
 117     disabled = widget_get_state (WIDGET (in), WST_DISABLED);
 118     tty_setcolor (disabled ? DISABLED_COLOR : in->color[WINPUTC_HISTORY]);
 119 
 120 #ifdef LARGE_HISTORY_BUTTON
 121     tty_print_string ("[ ]");
 122     widget_move (in, 0, WIDGET (in)->cols - HISTORY_BUTTON_WIDTH + 1);
 123 #endif
 124 
 125     tty_print_char (c);
 126 }
 127 
 128 /* --------------------------------------------------------------------------------------------- */
 129 
 130 static void
 131 input_mark_cmd (WInput * in, gboolean mark)
     /* [previous][next][first][last][top][bottom][index][help]  */
 132 {
 133     in->mark = mark ? in->point : -1;
 134 }
 135 
 136 /* --------------------------------------------------------------------------------------------- */
 137 
 138 static gboolean
 139 input_eval_marks (WInput * in, long *start_mark, long *end_mark)
     /* [previous][next][first][last][top][bottom][index][help]  */
 140 {
 141     if (in->mark >= 0)
 142     {
 143         *start_mark = MIN (in->mark, in->point);
 144         *end_mark = MAX (in->mark, in->point);
 145         return TRUE;
 146     }
 147 
 148     *start_mark = *end_mark = -1;
 149     return FALSE;
 150 }
 151 
 152 /* --------------------------------------------------------------------------------------------- */
 153 
 154 static void
 155 delete_region (WInput * in, int x_first, int x_last)
     /* [previous][next][first][last][top][bottom][index][help]  */
 156 {
 157     int first = MIN (x_first, x_last);
 158     int last = MAX (x_first, x_last);
 159 
 160     input_mark_cmd (in, FALSE);
 161     in->point = first;
 162     last = str_offset_to_pos (in->buffer, last);
 163     first = str_offset_to_pos (in->buffer, first);
 164     str_move (in->buffer + first, in->buffer + last);
 165     in->charpoint = 0;
 166     in->need_push = TRUE;
 167 }
 168 
 169 /* --------------------------------------------------------------------------------------------- */
 170 
 171 static void
 172 do_show_hist (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 173 {
 174     size_t len;
 175     history_descriptor_t hd;
 176 
 177     len = get_history_length (in->history.list);
 178 
 179     history_descriptor_init (&hd, WIDGET (in)->y, WIDGET (in)->x, in->history.list,
 180                              g_list_position (in->history.list, in->history.list));
 181     history_show (&hd);
 182 
 183     in->history.list = hd.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 *
 204 input_history_strip_password (char *url)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 235 push_history (WInput * in, const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 278 move_buffer_backward (WInput * in, int start, int end)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 297 insert_char (WInput * in, int c_code)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 360 beginning_of_line (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 361 {
 362     in->point = 0;
 363     in->charpoint = 0;
 364 }
 365 
 366 /* --------------------------------------------------------------------------------------------- */
 367 
 368 static void
 369 end_of_line (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 370 {
 371     in->point = str_length (in->buffer);
 372     in->charpoint = 0;
 373 }
 374 
 375 /* --------------------------------------------------------------------------------------------- */
 376 
 377 static void
 378 backward_char (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 391 forward_char (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 404 forward_word (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 424 backward_word (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 456 backward_delete (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 474 delete_char (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 490 copy_region (WInput * in, int x_first, int x_last)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 519 kill_word (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 536 back_kill_word (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 552 yank (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 568 kill_line (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 582 clear_line (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 594 ins_from_clip (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 618 hist_prev (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 641 hist_next (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 673 port_region_marked_for_delete (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 684 input_execute_cmd (WInput * in, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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         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
 837 input_load_history (const gchar * event_group_name, const gchar * event_name,
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 866 input_save_history (const gchar * event_group_name, const gchar * event_name,
     /* [previous][next][first][last][top][bottom][index][help]  */
 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 && (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
 890 input_destroy (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
 891 {
 892     if (in == NULL)
 893     {
 894         fprintf (stderr, "Internal error: null Input *\n");
 895         exit (EXIT_FAILURE);
 896     }
 897 
 898     input_free_completions (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
 918 input_screen_to_point (const WInput * in, int x)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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
 934 input_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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         in->first = FALSE;
 945 
 946         if (event->x >= w->cols - HISTORY_BUTTON_WIDTH && should_show_history_button (in))
 947             do_show_hist (in);
 948         else
 949         {
 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 *
 988 input_new (int y, int x, const int *colors, int width, const char *def_text,
     /* [previous][next][first][last][top][bottom][index][help]  */
 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 
 999     in->color = colors;
1000     in->first = TRUE;
1001     in->mark = -1;
1002     in->term_first_shown = 0;
1003     in->disable_update = 0;
1004     in->is_password = FALSE;
1005     in->strip_password = FALSE;
1006 
1007     /* in->buffer will be corrected in "history_load" event handler */
1008     in->current_max_size = width + 1;
1009     in->buffer = g_new0 (char, in->current_max_size);
1010 
1011     /* init completions before input_assign_text() call */
1012     in->completions = NULL;
1013     in->completion_flags = completion_flags;
1014 
1015     in->init_from_history = (def_text == INPUT_LAST_TEXT);
1016 
1017     if (in->init_from_history || def_text == NULL)
1018         def_text = "";
1019 
1020     input_assign_text (in, def_text);
1021 
1022     /* prepare to history setup */
1023     in->history.list = NULL;
1024     in->history.current = NULL;
1025     in->history.changed = FALSE;
1026     in->history.name = NULL;
1027     if ((histname != NULL) && (*histname != '\0'))
1028         in->history.name = g_strdup (histname);
1029     /* history will be loaded later */
1030 
1031     in->label = NULL;
1032 
1033     return in;
1034 }
1035 
1036 /* --------------------------------------------------------------------------------------------- */
1037 
1038 cb_ret_t
1039 input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
1040 {
1041     WInput *in = INPUT (w);
1042     cb_ret_t v;
1043 
1044     switch (msg)
1045     {
1046     case MSG_INIT:
1047         /* subscribe to "history_load" event */
1048         mc_event_add (w->owner->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w, NULL);
1049         /* subscribe to "history_save" event */
1050         mc_event_add (w->owner->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w, NULL);
1051         if (in->label != NULL)
1052             widget_set_state (WIDGET (in->label), WST_DISABLED, widget_get_state (w, WST_DISABLED));
1053         return MSG_HANDLED;
1054 
1055     case MSG_KEY:
1056         if (parm == XCTRL ('q'))
1057         {
1058             quote = TRUE;
1059             v = input_handle_char (in, ascii_alpha_to_cntrl (tty_getch ()));
1060             quote = FALSE;
1061             return v;
1062         }
1063 
1064         /* Keys we want others to handle */
1065         if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR
1066             || parm == KEY_F (10) || parm == '\n')
1067             return MSG_NOT_HANDLED;
1068 
1069         /* When pasting multiline text, insert literal Enter */
1070         if ((parm & ~KEY_M_MASK) == '\n')
1071         {
1072             quote = TRUE;
1073             v = input_handle_char (in, '\n');
1074             quote = FALSE;
1075             return v;
1076         }
1077 
1078         return input_handle_char (in, parm);
1079 
1080     case MSG_ACTION:
1081         return input_execute_cmd (in, parm);
1082 
1083     case MSG_DRAW:
1084         input_update (in, FALSE);
1085         return MSG_HANDLED;
1086 
1087     case MSG_ENABLE:
1088     case MSG_DISABLE:
1089         if (in->label != NULL)
1090             widget_set_state (WIDGET (in->label), WST_DISABLED, msg == MSG_DISABLE);
1091         return MSG_HANDLED;
1092 
1093     case MSG_CURSOR:
1094         widget_move (in, 0, str_term_width2 (in->buffer, in->point) - in->term_first_shown);
1095         return MSG_HANDLED;
1096 
1097     case MSG_DESTROY:
1098         /* unsubscribe from "history_load" event */
1099         mc_event_del (w->owner->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w);
1100         /* unsubscribe from "history_save" event */
1101         mc_event_del (w->owner->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w);
1102         input_destroy (in);
1103         return MSG_HANDLED;
1104 
1105     default:
1106         return widget_default_callback (w, sender, msg, parm, data);
1107     }
1108 }
1109 
1110 /* --------------------------------------------------------------------------------------------- */
1111 
1112 void
1113 input_set_default_colors (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1114 {
1115     input_colors[WINPUTC_MAIN] = INPUT_COLOR;
1116     input_colors[WINPUTC_MARK] = INPUT_MARK_COLOR;
1117     input_colors[WINPUTC_UNCHANGED] = INPUT_UNCHANGED_COLOR;
1118     input_colors[WINPUTC_HISTORY] = INPUT_HISTORY_COLOR;
1119 }
1120 
1121 /* --------------------------------------------------------------------------------------------- */
1122 
1123 cb_ret_t
1124 input_handle_char (WInput * in, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
1125 {
1126     cb_ret_t v;
1127     long command;
1128 
1129     if (quote)
1130     {
1131         input_free_completions (in);
1132         v = insert_char (in, key);
1133         input_update (in, TRUE);
1134         quote = FALSE;
1135         return v;
1136     }
1137 
1138     command = keybind_lookup_keymap_command (input_map, key);
1139 
1140     if (command == CK_IgnoreKey)
1141     {
1142         if (key > 255)
1143             return MSG_NOT_HANDLED;
1144         if (in->first)
1145             port_region_marked_for_delete (in);
1146         input_free_completions (in);
1147         v = insert_char (in, key);
1148     }
1149     else
1150     {
1151         if (command != CK_Complete)
1152             input_free_completions (in);
1153         input_execute_cmd (in, command);
1154         v = MSG_HANDLED;
1155         if (in->first)
1156             input_update (in, TRUE);    /* needed to clear in->first */
1157     }
1158 
1159     input_update (in, TRUE);
1160     return v;
1161 }
1162 
1163 /* --------------------------------------------------------------------------------------------- */
1164 
1165 /* This function is a test for a special input key used in complete.c */
1166 /* Returns 0 if it is not a special key, 1 if it is a non-complete key
1167    and 2 if it is a complete key */
1168 int
1169 input_key_is_in_map (WInput * in, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
1170 {
1171     long command;
1172 
1173     (void) in;
1174 
1175     command = keybind_lookup_keymap_command (input_map, key);
1176     if (command == CK_IgnoreKey)
1177         return 0;
1178 
1179     return (command == CK_Complete) ? 2 : 1;
1180 }
1181 
1182 /* --------------------------------------------------------------------------------------------- */
1183 
1184 void
1185 input_assign_text (WInput * in, const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
1186 {
1187     Widget *w = WIDGET (in);
1188     size_t text_len, buffer_len;
1189 
1190     if (text == NULL)
1191         text = "";
1192 
1193     input_free_completions (in);
1194     in->mark = -1;
1195     in->need_push = TRUE;
1196     in->charpoint = 0;
1197 
1198     text_len = strlen (text);
1199     buffer_len = 1 + MAX ((size_t) w->cols, text_len);
1200     in->current_max_size = buffer_len;
1201     if (buffer_len > (size_t) w->cols)
1202         in->buffer = g_realloc (in->buffer, buffer_len);
1203     memmove (in->buffer, text, text_len + 1);
1204     in->point = str_length (in->buffer);
1205     input_update (in, TRUE);
1206 }
1207 
1208 /* --------------------------------------------------------------------------------------------- */
1209 
1210 gboolean
1211 input_is_empty (const WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1212 {
1213     return (in == NULL || in->buffer == NULL || in->buffer[0] == '\0');
1214 }
1215 
1216 /* --------------------------------------------------------------------------------------------- */
1217 
1218 /* Inserts text in input line */
1219 void
1220 input_insert (WInput * in, const char *text, gboolean insert_extra_space)
     /* [previous][next][first][last][top][bottom][index][help]  */
1221 {
1222     input_disable_update (in);
1223     while (*text != '\0')
1224         input_handle_char (in, (unsigned char) *text++);        /* unsigned extension char->int */
1225     if (insert_extra_space)
1226         input_handle_char (in, ' ');
1227     input_enable_update (in);
1228     input_update (in, TRUE);
1229 }
1230 
1231 /* --------------------------------------------------------------------------------------------- */
1232 
1233 void
1234 input_set_point (WInput * in, int pos)
     /* [previous][next][first][last][top][bottom][index][help]  */
1235 {
1236     int max_pos;
1237 
1238     max_pos = str_length (in->buffer);
1239     pos = MIN (pos, max_pos);
1240     if (pos != in->point)
1241         input_free_completions (in);
1242     in->point = pos;
1243     in->charpoint = 0;
1244     input_update (in, TRUE);
1245 }
1246 
1247 /* --------------------------------------------------------------------------------------------- */
1248 
1249 void
1250 input_update (WInput * in, gboolean clear_first)
     /* [previous][next][first][last][top][bottom][index][help]  */
1251 {
1252     Widget *w = WIDGET (in);
1253     int has_history = 0;
1254     int buf_len;
1255     const char *cp;
1256     int pw;
1257 
1258     if (in->disable_update != 0)
1259         return;
1260 
1261     /* don't draw widget not put into dialog */
1262     if (w->owner == NULL || !widget_get_state (WIDGET (w->owner), WST_ACTIVE))
1263         return;
1264 
1265     if (should_show_history_button (in))
1266         has_history = HISTORY_BUTTON_WIDTH;
1267 
1268     buf_len = str_length (in->buffer);
1269 
1270     /* Adjust the mark */
1271     in->mark = MIN (in->mark, buf_len);
1272 
1273     pw = str_term_width2 (in->buffer, in->point);
1274 
1275     /* Make the point visible */
1276     if ((pw < in->term_first_shown) || (pw >= in->term_first_shown + w->cols - has_history))
1277     {
1278         in->term_first_shown = pw - (w->cols / 3);
1279         if (in->term_first_shown < 0)
1280             in->term_first_shown = 0;
1281     }
1282 
1283     if (has_history != 0)
1284         draw_history_button (in);
1285 
1286     if (widget_get_state (w, WST_DISABLED))
1287         tty_setcolor (DISABLED_COLOR);
1288     else if (in->first)
1289         tty_setcolor (in->color[WINPUTC_UNCHANGED]);
1290     else
1291         tty_setcolor (in->color[WINPUTC_MAIN]);
1292 
1293     widget_move (in, 0, 0);
1294 
1295     if (!in->is_password)
1296     {
1297         if (in->mark < 0)
1298             tty_print_string (str_term_substring (in->buffer, in->term_first_shown,
1299                                                   w->cols - has_history));
1300         else
1301         {
1302             long m1, m2;
1303 
1304             if (input_eval_marks (in, &m1, &m2))
1305             {
1306                 tty_setcolor (in->color[WINPUTC_MAIN]);
1307                 cp = str_term_substring (in->buffer, in->term_first_shown, w->cols - has_history);
1308                 tty_print_string (cp);
1309                 tty_setcolor (in->color[WINPUTC_MARK]);
1310                 if (m1 < in->term_first_shown)
1311                 {
1312                     widget_move (in, 0, 0);
1313                     tty_print_string (str_term_substring
1314                                       (in->buffer, in->term_first_shown,
1315                                        m2 - in->term_first_shown));
1316                 }
1317                 else
1318                 {
1319                     int sel_width, buf_width;
1320 
1321                     widget_move (in, 0, m1 - in->term_first_shown);
1322                     buf_width = str_term_width2 (in->buffer, m1);
1323                     sel_width =
1324                         MIN (m2 - m1, (w->cols - has_history) - (buf_width - in->term_first_shown));
1325                     tty_print_string (str_term_substring (in->buffer, m1, sel_width));
1326                 }
1327             }
1328         }
1329     }
1330     else
1331     {
1332         int i;
1333 
1334         cp = str_term_substring (in->buffer, in->term_first_shown, w->cols - has_history);
1335         tty_setcolor (in->color[WINPUTC_MAIN]);
1336         for (i = 0; i < w->cols - has_history; i++)
1337         {
1338             if (i < (buf_len - in->term_first_shown) && cp[0] != '\0')
1339                 tty_print_char ('*');
1340             else
1341                 tty_print_char (' ');
1342             if (cp[0] != '\0')
1343                 str_cnext_char (&cp);
1344         }
1345     }
1346 
1347     if (clear_first)
1348         in->first = FALSE;
1349 }
1350 
1351 /* --------------------------------------------------------------------------------------------- */
1352 
1353 void
1354 input_enable_update (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1355 {
1356     in->disable_update--;
1357     input_update (in, FALSE);
1358 }
1359 
1360 /* --------------------------------------------------------------------------------------------- */
1361 
1362 void
1363 input_disable_update (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1364 {
1365     in->disable_update++;
1366 }
1367 
1368 /* --------------------------------------------------------------------------------------------- */
1369 
1370 /**
1371   *  Cleans the input line and adds the current text to the history
1372   *
1373   *  @param in the input line
1374   */
1375 void
1376 input_clean (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1377 {
1378     push_history (in, in->buffer);
1379     in->need_push = TRUE;
1380     in->buffer[0] = '\0';
1381     in->point = 0;
1382     in->charpoint = 0;
1383     in->mark = -1;
1384     input_free_completions (in);
1385     input_update (in, FALSE);
1386 }
1387 
1388 /* --------------------------------------------------------------------------------------------- */
1389 
1390 void
1391 input_free_completions (WInput * in)
     /* [previous][next][first][last][top][bottom][index][help]  */
1392 {
1393     g_strfreev (in->completions);
1394     in->completions = NULL;
1395 }
1396 
1397 /* --------------------------------------------------------------------------------------------- */

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