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.
9 Miguel de Icaza, 1994, 1995, 1998
10 Janne Kukonlehto, 1994, 1995
12 Joseph M. Hinkle, 1996
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
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"
57 #include "lib/charsets.h"
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"
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 /* --------------------------------------------------------------------------------------------- */
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 /* --------------------------------------------------------------------------------------------- */
102 mcview_search (WView
*view
, gboolean start_search
)
104 off_t want_search_start
= view
->search_start
;
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
);
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;
125 want_search_start
= 0;
128 mcview_do_search (view
, want_search_start
);
132 /* --------------------------------------------------------------------------------------------- */
135 mcview_continue_search_cmd (WView
*view
)
137 if (view
->last_search_string
!= NULL
)
138 mcview_search (view
, FALSE
);
141 /* find last search string in history */
144 s
= mc_config_history_get_recent_item (MC_HISTORY_SHARED_SEARCH
);
147 view
->last_search_string
= s
;
149 if (mcview_search_init (view
))
151 mcview_search (view
, FALSE
);
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 /* --------------------------------------------------------------------------------------------- */
167 mcview_hook (void *v
)
169 WView
*view
= (WView
*) v
;
171 const file_entry_t
*fe
;
173 /* If the user is busy typing, wait until he finishes to update the
177 if (!hook_present (idle_hook
, mcview_hook
))
178 add_hook (&idle_hook
, mcview_hook
, v
);
182 delete_hook (&idle_hook
, mcview_hook
);
184 if (get_current_type () == view_listing
)
185 panel
= current_panel
;
186 else if (get_other_type () == view_listing
)
191 fe
= panel_current_entry (panel
);
197 mcview_load (view
, 0, fe
->fname
->str
, 0, 0, 0);
198 mcview_display (view
);
201 /* --------------------------------------------------------------------------------------------- */
204 mcview_handle_editkey (WView
*view
, int key
)
206 struct hexedit_change_node
*node
;
209 /* Has there been a change at this position? */
210 node
= view
->change_list
;
211 while ((node
!= NULL
) && (node
->offset
!= view
->hex_cursor
))
214 if (!view
->hexview_in_text
)
217 unsigned int hexvalue
= 0;
219 if (key
>= '0' && key
<= '9')
220 hexvalue
= 0 + (key
- '0');
221 else if (key
>= 'A' && key
<= 'F')
222 hexvalue
= 10 + (key
- 'A');
223 else if (key
>= 'a' && key
<= 'f')
224 hexvalue
= 10 + (key
- 'a');
226 return MSG_NOT_HANDLED
;
229 byte_val
= node
->value
;
231 mcview_get_byte (view
, view
->hex_cursor
, &byte_val
);
233 if (view
->hexedit_lownibble
)
234 byte_val
= (byte_val
& 0xf0) | (hexvalue
);
236 byte_val
= (byte_val
& 0x0f) | (hexvalue
<< 4);
241 if (key
< 256 && key
!= '\t')
244 return MSG_NOT_HANDLED
;
247 if ((view
->filename_vpath
!= NULL
)
248 && (*(vfs_path_get_last_path_str (view
->filename_vpath
)) != '\0')
249 && (view
->change_list
== NULL
))
250 view
->locked
= lock_file (view
->filename_vpath
);
254 node
= g_new (struct hexedit_change_node
, 1);
255 node
->offset
= view
->hex_cursor
;
256 node
->value
= byte_val
;
257 mcview_enqueue_change (&view
->change_list
, node
);
260 node
->value
= byte_val
;
263 mcview_move_right (view
, 1);
268 /* --------------------------------------------------------------------------------------------- */
271 mcview_load_next_prev_init (WView
*view
)
273 if (mc_global
.mc_run_mode
!= MC_RUN_VIEWER
)
275 /* get file list from current panel. Update it each time */
276 view
->dir
= ¤t_panel
->dir
;
277 view
->dir_idx
= ¤t_panel
->current
;
279 else if (view
->dir
== NULL
)
281 /* Run from command line */
282 /* Run 1st time. Load/get directory */
284 /* TODO: check mtime of directory to reload it */
286 dir_sort_options_t sort_op
= { FALSE
, TRUE
, FALSE
};
288 /* load directory where requested file is */
289 view
->dir
= g_new0 (dir_list
, 1);
290 view
->dir_idx
= g_new (int, 1);
293 (view
->dir
, view
->workdir_vpath
, (GCompareFunc
) sort_name
, &sort_op
, NULL
))
299 fname
= x_basename (vfs_path_as_str (view
->filename_vpath
));
300 fname_len
= strlen (fname
);
302 /* search current file in the list */
303 for (i
= 0; i
!= view
->dir
->len
; i
++)
305 const file_entry_t
*fe
= &view
->dir
->list
[i
];
307 if (fname_len
== fe
->fname
->len
&& strncmp (fname
, fe
->fname
->str
, fname_len
) == 0)
315 message (D_ERROR
, MSG_ERROR
, _("Cannot read directory contents"));
316 MC_PTR_FREE (view
->dir
);
317 MC_PTR_FREE (view
->dir_idx
);
322 /* --------------------------------------------------------------------------------------------- */
325 mcview_scan_for_file (WView
*view
, int direction
)
329 for (i
= *view
->dir_idx
+ direction
; i
!= *view
->dir_idx
; i
+= direction
)
332 i
= view
->dir
->len
- 1;
333 if (i
== view
->dir
->len
)
335 if (!S_ISDIR (view
->dir
->list
[i
].st
.st_mode
))
342 /* --------------------------------------------------------------------------------------------- */
345 mcview_load_next_prev (WView
*view
, int direction
)
350 vfs_path_t
*ext_script
= NULL
;
352 mcview_load_next_prev_init (view
);
353 mcview_scan_for_file (view
, direction
);
357 dir_idx
= view
->dir_idx
;
359 view
->dir_idx
= NULL
;
361 vfs_path_append_new (view
->workdir_vpath
, dir
->list
[*dir_idx
].fname
->str
, (char *) NULL
);
363 mcview_remove_ext_script (view
);
365 if (regex_command_for (view
, vfile
, "View", &ext_script
) == 0)
366 mcview_load (view
, NULL
, vfs_path_as_str (vfile
), 0, 0, 0);
367 vfs_path_free (vfile
, TRUE
);
369 view
->dir_idx
= dir_idx
;
370 view
->ext_script
= ext_script
;
372 view
->dpy_bbar_dirty
= FALSE
; /* FIXME */
376 /* --------------------------------------------------------------------------------------------- */
379 mcview_load_file_from_history (WView
*view
)
384 filename
= show_file_history (CONST_WIDGET (view
), &action
);
386 if (filename
!= NULL
&& (action
== CK_View
|| action
== CK_Enter
))
391 mcview_load (view
, NULL
, filename
, 0, 0, 0);
393 view
->dpy_bbar_dirty
= FALSE
; /* FIXME */
400 /* --------------------------------------------------------------------------------------------- */
403 mcview_execute_cmd (WView
*view
, long command
)
405 int res
= MSG_HANDLED
;
410 /* Toggle between hex view and text view */
411 mcview_toggle_hex_mode (view
);
414 /* Toggle between hexview and hexedit mode */
415 mcview_toggle_hexedit_mode (view
);
417 case CK_ToggleNavigation
:
418 view
->hexview_in_text
= !view
->hexview_in_text
;
422 if (!view
->mode_flags
.hex
)
423 mcview_move_left (view
, 10);
426 if (!view
->mode_flags
.hex
)
427 mcview_move_right (view
, 10);
433 if (mcview_dialog_goto (view
, &addr
))
436 mcview_moveto_offset (view
, addr
);
439 message (D_ERROR
, _("Warning"), "%s", _("Invalid value"));
446 mcview_hexedit_save_changes (view
);
449 mcview_search (view
, TRUE
);
451 case CK_SearchContinue
:
452 mcview_continue_search_cmd (view
);
454 case CK_SearchForward
:
455 mcview_search_options
.backwards
= FALSE
;
456 mcview_search (view
, TRUE
);
458 case CK_SearchForwardContinue
:
459 mcview_search_options
.backwards
= FALSE
;
460 mcview_continue_search_cmd (view
);
462 case CK_SearchBackward
:
463 mcview_search_options
.backwards
= TRUE
;
464 mcview_search (view
, TRUE
);
466 case CK_SearchBackwardContinue
:
467 mcview_search_options
.backwards
= TRUE
;
468 mcview_continue_search_cmd (view
);
470 case CK_SearchOppositeContinue
:
474 direction
= mcview_search_options
.backwards
;
475 mcview_search_options
.backwards
= !direction
;
476 mcview_continue_search_cmd (view
);
477 mcview_search_options
.backwards
= direction
;
481 /* Toggle between wrapped and unwrapped view */
482 mcview_toggle_wrap_mode (view
);
485 mcview_toggle_magic_mode (view
);
488 mcview_toggle_nroff_mode (view
);
491 mcview_moveto_bol (view
);
494 mcview_moveto_eol (view
);
497 mcview_move_left (view
, 1);
500 mcview_move_right (view
, 1);
503 mcview_move_up (view
, 1);
506 mcview_move_down (view
, 1);
509 mcview_move_up (view
, (view
->data_area
.lines
+ 1) / 2);
511 case CK_HalfPageDown
:
512 mcview_move_down (view
, (view
->data_area
.lines
+ 1) / 2);
515 mcview_move_up (view
, view
->data_area
.lines
);
518 mcview_move_down (view
, view
->data_area
.lines
);
521 mcview_moveto_top (view
);
524 mcview_moveto_bottom (view
);
530 mcview_display_toggle_ruler (view
);
533 view
->dpy_start
= view
->marks
[view
->marker
];
534 view
->dpy_paragraph_skip_lines
= 0; /* TODO: remember this value in the marker? */
535 view
->dpy_wrap_dirty
= TRUE
;
538 case CK_BookmarkGoto
:
539 view
->marks
[view
->marker
] = view
->dpy_start
;
542 case CK_SelectCodepage
:
543 mcview_select_encoding (view
);
549 /* Does not work in panel mode */
550 if (!mcview_is_in_panel (view
))
551 mcview_load_next_prev (view
, command
== CK_FileNext
? 1 : -1);
554 mcview_load_file_from_history (view
);
557 if (!mcview_is_in_panel (view
))
558 dlg_close (DIALOG (WIDGET (view
)->owner
));
561 /* don't close viewer due to SIGINT */
564 res
= MSG_NOT_HANDLED
;
569 /* --------------------------------------------------------------------------------------------- */
572 mcview_lookup_key (WView
*view
, int key
)
574 if (view
->mode_flags
.hex
)
575 return keybind_lookup_keymap_command (view
->hex_keymap
, key
);
577 return widget_lookup_key (WIDGET (view
), key
);
580 /* --------------------------------------------------------------------------------------------- */
583 mcview_handle_key (WView
*view
, int key
)
588 key
= convert_from_input_c (key
);
591 if (view
->hexedit_mode
&& view
->mode_flags
.hex
592 && mcview_handle_editkey (view
, key
) == MSG_HANDLED
)
595 command
= mcview_lookup_key (view
, key
);
596 if (command
!= CK_IgnoreKey
&& mcview_execute_cmd (view
, command
) == MSG_HANDLED
)
599 #ifdef MC_ENABLE_DEBUGGING_CODE
601 { /* mnemonic: "test" */
602 mcview_ccache_dump (view
);
606 if (key
>= '0' && key
<= '9')
607 view
->marker
= key
- '0';
610 return MSG_NOT_HANDLED
;
614 /* --------------------------------------------------------------------------------------------- */
617 mcview_resize (WView
*view
)
619 view
->dpy_wrap_dirty
= TRUE
;
620 mcview_compute_areas (view
);
621 mcview_update_bytes_per_line (view
);
624 /* --------------------------------------------------------------------------------------------- */
627 mcview_ok_to_quit (WView
*view
)
631 if (view
->change_list
== NULL
)
634 if (!mc_global
.midnight_shutdown
)
637 r
= query_dialog (_("Quit"),
638 _("File was modified. Save with exit?"), D_NORMAL
, 3,
639 _("&Yes"), _("&No"), _("&Cancel quit"));
643 r
= query_dialog (_("Quit"),
644 _("Midnight Commander is being shut down.\nSave modified file?"),
645 D_NORMAL
, 2, _("&Yes"), _("&No"));
654 return mcview_hexedit_save_changes (view
) || mc_global
.midnight_shutdown
;
656 mcview_hexedit_free_change_list (view
);
663 /* --------------------------------------------------------------------------------------------- */
664 /*** public functions ****************************************************************************/
665 /* --------------------------------------------------------------------------------------------- */
668 mcview_callback (Widget
*w
, Widget
*sender
, widget_msg_t msg
, int parm
, void *data
)
670 WView
*view
= (WView
*) w
;
673 mcview_compute_areas (view
);
674 mcview_update_bytes_per_line (view
);
679 if (mcview_is_in_panel (view
))
680 add_hook (&select_file_hook
, mcview_hook
, view
);
682 view
->dpy_bbar_dirty
= TRUE
;
686 mcview_display (view
);
690 if (view
->mode_flags
.hex
)
691 mcview_place_cursor (view
);
695 i
= mcview_handle_key (view
, parm
);
696 mcview_update (view
);
700 i
= mcview_execute_cmd (view
, parm
);
701 mcview_update (view
);
705 view
->dpy_bbar_dirty
= TRUE
;
706 /* TODO: get rid of draw here before MSG_DRAW */
707 mcview_update (view
);
711 widget_default_callback (w
, NULL
, MSG_RESIZE
, 0, data
);
712 mcview_resize (view
);
716 if (mcview_is_in_panel (view
))
718 delete_hook (&select_file_hook
, mcview_hook
);
721 * In some cases when mc startup is very slow and one panel is in quick view mode,
722 * @view is registered in two hook lists at the same time:
723 * mcview_callback (MSG_INIT) -> add_hook (&select_file_hook)
724 * mcview_hook () -> add_hook (&idle_hook).
725 * If initialization of file manager is not completed yet, but user switches
726 * panel mode from qick view to another one (by pressing C-x q), the following
728 * view hook is deleted from select_file_hook list via following call chain:
729 * create_panel (view_listing) -> widget_replace () ->
730 * send_message (MSG_DESTROY) -> mcview_callback (MSG_DESTROY) ->
731 * delete_hook (&select_file_hook);
732 * @view object is free'd:
733 * create_panel (view_listing) -> g_free (old_widget);
734 * but @view still is in idle_hook list and tried to be executed:
735 * frontend_dlg_run () -> execute_hooks (idle_hook).
736 * Thus here we have access to free'd @view object. To prevent this, remove view hook
737 * from idle_hook list.
739 delete_hook (&idle_hook
, mcview_hook
);
741 if (mc_global
.midnight_shutdown
)
742 mcview_ok_to_quit (view
);
745 mcview_remove_ext_script (view
);
749 return widget_default_callback (w
, sender
, msg
, parm
, data
);
753 /* --------------------------------------------------------------------------------------------- */
756 mcview_dialog_callback (Widget
*w
, Widget
*sender
, widget_msg_t msg
, int parm
, void *data
)
758 WDialog
*h
= DIALOG (w
);
764 /* Handle shortcuts. */
766 /* Note: the buttonbar sends messages directly to the the WView, not to
767 * here, which is why we can pass NULL in the following call. */
768 return mcview_execute_cmd (NULL
, parm
);
771 view
= (WView
*) widget_find_by_type (w
, mcview_callback
);
772 /* don't stop the dialog before final decision */
773 widget_set_state (w
, WST_ACTIVE
, TRUE
);
774 if (mcview_ok_to_quit (view
))
777 mcview_update (view
);
781 return dlg_default_callback (w
, sender
, msg
, parm
, data
);
785 /* --------------------------------------------------------------------------------------------- */