Merge branch '4549_subshell_execl_argv0'
[midnight-commander.git] / src / viewer / actions_cmd.c
blobb0980860445b12017d2c81018635f9bf6cc10e31
1 /*
2 Internal file viewer for the Midnight Commander
3 Callback function for some actions (hotkeys, menu)
5 Copyright (C) 1994-2024
6 Free Software Foundation, Inc.
8 Written by:
9 Miguel de Icaza, 1994, 1995, 1998
10 Janne Kukonlehto, 1994, 1995
11 Jakub Jelinek, 1995
12 Joseph M. Hinkle, 1996
13 Norbert Warmuth, 1997
14 Pavel Machek, 1998
15 Roland Illig <roland.illig@gmx.de>, 2004, 2005
16 Slava Zanko <slavazanko@google.com>, 2009, 2013
17 Andrew Borodin <aborodin@vmail.ru>, 2009-2022
18 Ilia Maslakov <il.smind@gmail.com>, 2009
20 This file is part of the Midnight Commander.
22 The Midnight Commander is free software: you can redistribute it
23 and/or modify it under the terms of the GNU General Public License as
24 published by the Free Software Foundation, either version 3 of the License,
25 or (at your option) any later version.
27 The Midnight Commander is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
32 You should have received a copy of the GNU General Public License
33 along with this program. If not, see <http://www.gnu.org/licenses/>.
37 The functions in this section can be bound to hotkeys. They are all
38 of the same type (taking a pointer to WView as parameter and
39 returning void). TODO: In the not-too-distant future, these commands
40 will become fully configurable, like they already are in the
41 internal editor. By convention, all the function names end in
42 "_cmd".
45 #include <config.h>
47 #include <stdlib.h>
49 #include "lib/global.h"
51 #include "lib/tty/tty.h"
52 #include "lib/tty/key.h" /* is_idle() */
53 #include "lib/lock.h" /* lock_file() */
54 #include "lib/file-entry.h"
55 #include "lib/widget.h"
56 #ifdef HAVE_CHARSET
57 #include "lib/charsets.h"
58 #endif
59 #include "lib/event.h" /* mc_event_raise() */
60 #include "lib/mcconfig.h" /* mc_config_history_get_recent_item() */
62 #include "src/filemanager/layout.h"
63 #include "src/filemanager/filemanager.h" /* current_panel */
64 #include "src/filemanager/ext.h" /* regex_command_for() */
66 #include "src/history.h" /* MC_HISTORY_SHARED_SEARCH */
67 #include "src/file_history.h" /* show_file_history() */
68 #include "src/execute.h"
69 #include "src/keymap.h"
71 #include "internal.h"
73 /*** global variables ****************************************************************************/
75 /*** file scope macro definitions ****************************************************************/
77 /*** file scope type declarations ****************************************************************/
79 /*** forward declarations (file scope functions) *************************************************/
81 /*** file scope variables ************************************************************************/
83 /* --------------------------------------------------------------------------------------------- */
84 /*** file scope functions ************************************************************************/
85 /* --------------------------------------------------------------------------------------------- */
87 static void
88 mcview_remove_ext_script (WView *view)
90 if (view->ext_script != NULL)
92 mc_unlink (view->ext_script);
93 vfs_path_free (view->ext_script, TRUE);
94 view->ext_script = NULL;
98 /* --------------------------------------------------------------------------------------------- */
100 /* Both views */
101 static void
102 mcview_search (WView *view, gboolean start_search)
104 off_t want_search_start = view->search_start;
106 if (start_search)
108 if (mcview_dialog_search (view))
110 if (view->mode_flags.hex)
111 want_search_start = view->hex_cursor;
113 mcview_do_search (view, want_search_start);
116 else
118 if (view->mode_flags.hex)
120 if (!mcview_search_options.backwards)
121 want_search_start = view->hex_cursor + 1;
122 else if (view->hex_cursor > 0)
123 want_search_start = view->hex_cursor - 1;
124 else
125 want_search_start = 0;
128 mcview_do_search (view, want_search_start);
132 /* --------------------------------------------------------------------------------------------- */
134 static void
135 mcview_continue_search_cmd (WView *view)
137 if (view->last_search_string != NULL)
138 mcview_search (view, FALSE);
139 else
141 /* find last search string in history */
142 char *s;
144 s = mc_config_history_get_recent_item (MC_HISTORY_SHARED_SEARCH);
145 if (s != NULL)
147 view->last_search_string = s;
149 if (mcview_search_init (view))
151 mcview_search (view, FALSE);
152 return;
155 /* found, but cannot init search */
156 MC_PTR_FREE (view->last_search_string);
159 /* if not... then ask for an expression */
160 mcview_search (view, TRUE);
164 /* --------------------------------------------------------------------------------------------- */
166 static void
167 mcview_hook (void *v)
169 WView *view = (WView *) v;
170 WPanel *panel;
172 /* If the user is busy typing, wait until he finishes to update the
173 screen */
174 if (!is_idle ())
176 if (!hook_present (idle_hook, mcview_hook))
177 add_hook (&idle_hook, mcview_hook, v);
178 return;
181 delete_hook (&idle_hook, mcview_hook);
183 if (get_current_type () == view_listing)
184 panel = current_panel;
185 else if (get_other_type () == view_listing)
186 panel = other_panel;
187 else
188 return;
190 mcview_done (view);
191 mcview_init (view);
192 mcview_load (view, 0, panel_current_entry (panel)->fname->str, 0, 0, 0);
193 mcview_display (view);
196 /* --------------------------------------------------------------------------------------------- */
198 static cb_ret_t
199 mcview_handle_editkey (WView *view, int key)
201 struct hexedit_change_node *node;
202 int byte_val = -1;
204 /* Has there been a change at this position? */
205 node = view->change_list;
206 while ((node != NULL) && (node->offset != view->hex_cursor))
207 node = node->next;
209 if (!view->hexview_in_text)
211 /* Hex editing */
212 unsigned int hexvalue = 0;
214 if (key >= '0' && key <= '9')
215 hexvalue = 0 + (key - '0');
216 else if (key >= 'A' && key <= 'F')
217 hexvalue = 10 + (key - 'A');
218 else if (key >= 'a' && key <= 'f')
219 hexvalue = 10 + (key - 'a');
220 else
221 return MSG_NOT_HANDLED;
223 if (node != NULL)
224 byte_val = node->value;
225 else
226 mcview_get_byte (view, view->hex_cursor, &byte_val);
228 if (view->hexedit_lownibble)
229 byte_val = (byte_val & 0xf0) | (hexvalue);
230 else
231 byte_val = (byte_val & 0x0f) | (hexvalue << 4);
233 else
235 /* Text editing */
236 if (key < 256 && key != '\t')
237 byte_val = key;
238 else
239 return MSG_NOT_HANDLED;
242 if ((view->filename_vpath != NULL)
243 && (*(vfs_path_get_last_path_str (view->filename_vpath)) != '\0')
244 && (view->change_list == NULL))
245 view->locked = lock_file (view->filename_vpath);
247 if (node == NULL)
249 node = g_new (struct hexedit_change_node, 1);
250 node->offset = view->hex_cursor;
251 node->value = byte_val;
252 mcview_enqueue_change (&view->change_list, node);
254 else
255 node->value = byte_val;
257 view->dirty++;
258 mcview_move_right (view, 1);
260 return MSG_HANDLED;
263 /* --------------------------------------------------------------------------------------------- */
265 static void
266 mcview_load_next_prev_init (WView *view)
268 if (mc_global.mc_run_mode != MC_RUN_VIEWER)
270 /* get file list from current panel. Update it each time */
271 view->dir = &current_panel->dir;
272 view->dir_idx = &current_panel->current;
274 else if (view->dir == NULL)
276 /* Run from command line */
277 /* Run 1st time. Load/get directory */
279 /* TODO: check mtime of directory to reload it */
281 dir_sort_options_t sort_op = { FALSE, TRUE, FALSE };
283 /* load directory where requested file is */
284 view->dir = g_new0 (dir_list, 1);
285 view->dir_idx = g_new (int, 1);
287 if (dir_list_load
288 (view->dir, view->workdir_vpath, (GCompareFunc) sort_name, &sort_op, NULL))
290 const char *fname;
291 size_t fname_len;
292 int i;
294 fname = x_basename (vfs_path_as_str (view->filename_vpath));
295 fname_len = strlen (fname);
297 /* search current file in the list */
298 for (i = 0; i != view->dir->len; i++)
300 const file_entry_t *fe = &view->dir->list[i];
302 if (fname_len == fe->fname->len && strncmp (fname, fe->fname->str, fname_len) == 0)
303 break;
306 *view->dir_idx = i;
308 else
310 message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
311 MC_PTR_FREE (view->dir);
312 MC_PTR_FREE (view->dir_idx);
317 /* --------------------------------------------------------------------------------------------- */
319 static void
320 mcview_scan_for_file (WView *view, int direction)
322 int i;
324 for (i = *view->dir_idx + direction; i != *view->dir_idx; i += direction)
326 if (i < 0)
327 i = view->dir->len - 1;
328 if (i == view->dir->len)
329 i = 0;
330 if (!S_ISDIR (view->dir->list[i].st.st_mode))
331 break;
334 *view->dir_idx = i;
337 /* --------------------------------------------------------------------------------------------- */
339 static void
340 mcview_load_next_prev (WView *view, int direction)
342 dir_list *dir;
343 int *dir_idx;
344 vfs_path_t *vfile;
345 vfs_path_t *ext_script = NULL;
347 mcview_load_next_prev_init (view);
348 mcview_scan_for_file (view, direction);
350 /* reinit view */
351 dir = view->dir;
352 dir_idx = view->dir_idx;
353 view->dir = NULL;
354 view->dir_idx = NULL;
355 vfile =
356 vfs_path_append_new (view->workdir_vpath, dir->list[*dir_idx].fname->str, (char *) NULL);
357 mcview_done (view);
358 mcview_remove_ext_script (view);
359 mcview_init (view);
360 if (regex_command_for (view, vfile, "View", &ext_script) == 0)
361 mcview_load (view, NULL, vfs_path_as_str (vfile), 0, 0, 0);
362 vfs_path_free (vfile, TRUE);
363 view->dir = dir;
364 view->dir_idx = dir_idx;
365 view->ext_script = ext_script;
367 view->dpy_bbar_dirty = FALSE; /* FIXME */
368 view->dirty++;
371 /* --------------------------------------------------------------------------------------------- */
373 static void
374 mcview_load_file_from_history (WView *view)
376 char *filename;
377 int action;
379 filename = show_file_history (CONST_WIDGET (view), &action);
381 if (filename != NULL && (action == CK_View || action == CK_Enter))
383 mcview_done (view);
384 mcview_init (view);
386 mcview_load (view, NULL, filename, 0, 0, 0);
388 view->dpy_bbar_dirty = FALSE; /* FIXME */
389 view->dirty++;
392 g_free (filename);
395 /* --------------------------------------------------------------------------------------------- */
397 static cb_ret_t
398 mcview_execute_cmd (WView *view, long command)
400 int res = MSG_HANDLED;
402 switch (command)
404 case CK_HexMode:
405 /* Toggle between hex view and text view */
406 mcview_toggle_hex_mode (view);
407 break;
408 case CK_HexEditMode:
409 /* Toggle between hexview and hexedit mode */
410 mcview_toggle_hexedit_mode (view);
411 break;
412 case CK_ToggleNavigation:
413 view->hexview_in_text = !view->hexview_in_text;
414 view->dirty++;
415 break;
416 case CK_LeftQuick:
417 if (!view->mode_flags.hex)
418 mcview_move_left (view, 10);
419 break;
420 case CK_RightQuick:
421 if (!view->mode_flags.hex)
422 mcview_move_right (view, 10);
423 break;
424 case CK_Goto:
426 off_t addr;
428 if (mcview_dialog_goto (view, &addr))
430 if (addr >= 0)
431 mcview_moveto_offset (view, addr);
432 else
434 message (D_ERROR, _("Warning"), "%s", _("Invalid value"));
435 view->dirty++;
438 break;
440 case CK_Save:
441 mcview_hexedit_save_changes (view);
442 break;
443 case CK_Search:
444 mcview_search (view, TRUE);
445 break;
446 case CK_SearchContinue:
447 mcview_continue_search_cmd (view);
448 break;
449 case CK_SearchForward:
450 mcview_search_options.backwards = FALSE;
451 mcview_search (view, TRUE);
452 break;
453 case CK_SearchForwardContinue:
454 mcview_search_options.backwards = FALSE;
455 mcview_continue_search_cmd (view);
456 break;
457 case CK_SearchBackward:
458 mcview_search_options.backwards = TRUE;
459 mcview_search (view, TRUE);
460 break;
461 case CK_SearchBackwardContinue:
462 mcview_search_options.backwards = TRUE;
463 mcview_continue_search_cmd (view);
464 break;
465 case CK_SearchOppositeContinue:
467 gboolean direction;
469 direction = mcview_search_options.backwards;
470 mcview_search_options.backwards = !direction;
471 mcview_continue_search_cmd (view);
472 mcview_search_options.backwards = direction;
474 break;
475 case CK_WrapMode:
476 /* Toggle between wrapped and unwrapped view */
477 mcview_toggle_wrap_mode (view);
478 break;
479 case CK_MagicMode:
480 mcview_toggle_magic_mode (view);
481 break;
482 case CK_NroffMode:
483 mcview_toggle_nroff_mode (view);
484 break;
485 case CK_Home:
486 mcview_moveto_bol (view);
487 break;
488 case CK_End:
489 mcview_moveto_eol (view);
490 break;
491 case CK_Left:
492 mcview_move_left (view, 1);
493 break;
494 case CK_Right:
495 mcview_move_right (view, 1);
496 break;
497 case CK_Up:
498 mcview_move_up (view, 1);
499 break;
500 case CK_Down:
501 mcview_move_down (view, 1);
502 break;
503 case CK_HalfPageUp:
504 mcview_move_up (view, (view->data_area.lines + 1) / 2);
505 break;
506 case CK_HalfPageDown:
507 mcview_move_down (view, (view->data_area.lines + 1) / 2);
508 break;
509 case CK_PageUp:
510 mcview_move_up (view, view->data_area.lines);
511 break;
512 case CK_PageDown:
513 mcview_move_down (view, view->data_area.lines);
514 break;
515 case CK_Top:
516 mcview_moveto_top (view);
517 break;
518 case CK_Bottom:
519 mcview_moveto_bottom (view);
520 break;
521 case CK_Shell:
522 toggle_subshell ();
523 break;
524 case CK_Ruler:
525 mcview_display_toggle_ruler (view);
526 break;
527 case CK_Bookmark:
528 view->dpy_start = view->marks[view->marker];
529 view->dpy_paragraph_skip_lines = 0; /* TODO: remember this value in the marker? */
530 view->dpy_wrap_dirty = TRUE;
531 view->dirty++;
532 break;
533 case CK_BookmarkGoto:
534 view->marks[view->marker] = view->dpy_start;
535 break;
536 #ifdef HAVE_CHARSET
537 case CK_SelectCodepage:
538 mcview_select_encoding (view);
539 view->dirty++;
540 break;
541 #endif
542 case CK_FileNext:
543 case CK_FilePrev:
544 /* Does not work in panel mode */
545 if (!mcview_is_in_panel (view))
546 mcview_load_next_prev (view, command == CK_FileNext ? 1 : -1);
547 break;
548 case CK_History:
549 mcview_load_file_from_history (view);
550 break;
551 case CK_Quit:
552 if (!mcview_is_in_panel (view))
553 dlg_close (DIALOG (WIDGET (view)->owner));
554 break;
555 case CK_Cancel:
556 /* don't close viewer due to SIGINT */
557 break;
558 default:
559 res = MSG_NOT_HANDLED;
561 return res;
564 /* --------------------------------------------------------------------------------------------- */
566 static long
567 mcview_lookup_key (WView *view, int key)
569 if (view->mode_flags.hex)
570 return keybind_lookup_keymap_command (view->hex_keymap, key);
572 return widget_lookup_key (WIDGET (view), key);
575 /* --------------------------------------------------------------------------------------------- */
576 /** Both views */
577 static cb_ret_t
578 mcview_handle_key (WView *view, int key)
580 long command;
582 #ifdef HAVE_CHARSET
583 key = convert_from_input_c (key);
584 #endif
586 if (view->hexedit_mode && view->mode_flags.hex
587 && mcview_handle_editkey (view, key) == MSG_HANDLED)
588 return MSG_HANDLED;
590 command = mcview_lookup_key (view, key);
591 if (command != CK_IgnoreKey && mcview_execute_cmd (view, command) == MSG_HANDLED)
592 return MSG_HANDLED;
594 #ifdef MC_ENABLE_DEBUGGING_CODE
595 if (key == 't')
596 { /* mnemonic: "test" */
597 mcview_ccache_dump (view);
598 return MSG_HANDLED;
600 #endif
601 if (key >= '0' && key <= '9')
602 view->marker = key - '0';
604 /* Key not used */
605 return MSG_NOT_HANDLED;
609 /* --------------------------------------------------------------------------------------------- */
611 static inline void
612 mcview_resize (WView *view)
614 view->dpy_wrap_dirty = TRUE;
615 mcview_compute_areas (view);
616 mcview_update_bytes_per_line (view);
619 /* --------------------------------------------------------------------------------------------- */
621 static gboolean
622 mcview_ok_to_quit (WView *view)
624 int r;
626 if (view->change_list == NULL)
627 return TRUE;
629 if (!mc_global.midnight_shutdown)
631 query_set_sel (2);
632 r = query_dialog (_("Quit"),
633 _("File was modified. Save with exit?"), D_NORMAL, 3,
634 _("&Yes"), _("&No"), _("&Cancel quit"));
636 else
638 r = query_dialog (_("Quit"),
639 _("Midnight Commander is being shut down.\nSave modified file?"),
640 D_NORMAL, 2, _("&Yes"), _("&No"));
641 /* Esc is No */
642 if (r == -1)
643 r = 1;
646 switch (r)
648 case 0: /* Yes */
649 return mcview_hexedit_save_changes (view) || mc_global.midnight_shutdown;
650 case 1: /* No */
651 mcview_hexedit_free_change_list (view);
652 return TRUE;
653 default:
654 return FALSE;
658 /* --------------------------------------------------------------------------------------------- */
659 /*** public functions ****************************************************************************/
660 /* --------------------------------------------------------------------------------------------- */
662 cb_ret_t
663 mcview_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
665 WView *view = (WView *) w;
666 cb_ret_t i;
668 mcview_compute_areas (view);
669 mcview_update_bytes_per_line (view);
671 switch (msg)
673 case MSG_INIT:
674 if (mcview_is_in_panel (view))
675 add_hook (&select_file_hook, mcview_hook, view);
676 else
677 view->dpy_bbar_dirty = TRUE;
678 return MSG_HANDLED;
680 case MSG_DRAW:
681 mcview_display (view);
682 return MSG_HANDLED;
684 case MSG_CURSOR:
685 if (view->mode_flags.hex)
686 mcview_place_cursor (view);
687 return MSG_HANDLED;
689 case MSG_KEY:
690 i = mcview_handle_key (view, parm);
691 mcview_update (view);
692 return i;
694 case MSG_ACTION:
695 i = mcview_execute_cmd (view, parm);
696 mcview_update (view);
697 return i;
699 case MSG_FOCUS:
700 view->dpy_bbar_dirty = TRUE;
701 /* TODO: get rid of draw here before MSG_DRAW */
702 mcview_update (view);
703 return MSG_HANDLED;
705 case MSG_RESIZE:
706 widget_default_callback (w, NULL, MSG_RESIZE, 0, data);
707 mcview_resize (view);
708 return MSG_HANDLED;
710 case MSG_DESTROY:
711 if (mcview_is_in_panel (view))
713 delete_hook (&select_file_hook, mcview_hook);
716 * In some cases when mc startup is very slow and one panel is in quick view mode,
717 * @view is registered in two hook lists at the same time:
718 * mcview_callback (MSG_INIT) -> add_hook (&select_file_hook)
719 * mcview_hook () -> add_hook (&idle_hook).
720 * If initialization of file manager is not completed yet, but user switches
721 * panel mode from qick view to another one (by pressing C-x q), the following
722 * occurs:
723 * view hook is deleted from select_file_hook list via following call chain:
724 * create_panel (view_listing) -> widget_replace () ->
725 * send_message (MSG_DESTROY) -> mcview_callback (MSG_DESTROY) ->
726 * delete_hook (&select_file_hook);
727 * @view object is free'd:
728 * create_panel (view_listing) -> g_free (old_widget);
729 * but @view still is in idle_hook list and tried to be executed:
730 * frontend_dlg_run () -> execute_hooks (idle_hook).
731 * Thus here we have access to free'd @view object. To prevent this, remove view hook
732 * from idle_hook list.
734 delete_hook (&idle_hook, mcview_hook);
736 if (mc_global.midnight_shutdown)
737 mcview_ok_to_quit (view);
739 mcview_done (view);
740 mcview_remove_ext_script (view);
741 return MSG_HANDLED;
743 default:
744 return widget_default_callback (w, sender, msg, parm, data);
748 /* --------------------------------------------------------------------------------------------- */
750 cb_ret_t
751 mcview_dialog_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
753 WDialog *h = DIALOG (w);
754 WView *view;
756 switch (msg)
758 case MSG_ACTION:
759 /* Handle shortcuts. */
761 /* Note: the buttonbar sends messages directly to the the WView, not to
762 * here, which is why we can pass NULL in the following call. */
763 return mcview_execute_cmd (NULL, parm);
765 case MSG_VALIDATE:
766 view = (WView *) widget_find_by_type (w, mcview_callback);
767 /* don't stop the dialog before final decision */
768 widget_set_state (w, WST_ACTIVE, TRUE);
769 if (mcview_ok_to_quit (view))
770 dlg_close (h);
771 else
772 mcview_update (view);
773 return MSG_HANDLED;
775 default:
776 return dlg_default_callback (w, sender, msg, parm, data);
780 /* --------------------------------------------------------------------------------------------- */