summaryrefslogtreecommitdiffstats
path: root/src/viewer/actions_cmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/viewer/actions_cmd.c')
-rw-r--r--src/viewer/actions_cmd.c790
1 files changed, 790 insertions, 0 deletions
diff --git a/src/viewer/actions_cmd.c b/src/viewer/actions_cmd.c
new file mode 100644
index 0000000..465f0f0
--- /dev/null
+++ b/src/viewer/actions_cmd.c
@@ -0,0 +1,790 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Callback function for some actions (hotkeys, menu)
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ The functions in this section can be bound to hotkeys. They are all
+ of the same type (taking a pointer to WView as parameter and
+ returning void). TODO: In the not-too-distant future, these commands
+ will become fully configurable, like they already are in the
+ internal editor. By convention, all the function names end in
+ "_cmd".
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* is_idle() */
+#include "lib/lock.h" /* lock_file() */
+#include "lib/file-entry.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+#include "lib/event.h" /* mc_event_raise() */
+#include "lib/mcconfig.h" /* mc_config_history_get() */
+
+#include "src/filemanager/layout.h"
+#include "src/filemanager/filemanager.h" /* current_panel */
+#include "src/filemanager/ext.h" /* regex_command_for() */
+
+#include "src/history.h"
+#include "src/file_history.h" /* show_file_history() */
+#include "src/execute.h"
+#include "src/keymap.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_remove_ext_script (WView * view)
+{
+ if (view->ext_script != NULL)
+ {
+ mc_unlink (view->ext_script);
+ vfs_path_free (view->ext_script, TRUE);
+ view->ext_script = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Both views */
+static void
+mcview_search (WView * view, gboolean start_search)
+{
+ off_t want_search_start = view->search_start;
+
+ if (start_search)
+ {
+ if (mcview_dialog_search (view))
+ {
+ if (view->mode_flags.hex)
+ want_search_start = view->hex_cursor;
+
+ mcview_do_search (view, want_search_start);
+ }
+ }
+ else
+ {
+ if (view->mode_flags.hex)
+ {
+ if (!mcview_search_options.backwards)
+ want_search_start = view->hex_cursor + 1;
+ else if (view->hex_cursor > 0)
+ want_search_start = view->hex_cursor - 1;
+ else
+ want_search_start = 0;
+ }
+
+ mcview_do_search (view, want_search_start);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_continue_search_cmd (WView * view)
+{
+ if (view->last_search_string != NULL)
+ mcview_search (view, FALSE);
+ else
+ {
+ /* find last search string in history */
+ GList *history;
+
+ history = mc_config_history_get (MC_HISTORY_SHARED_SEARCH);
+ if (history != NULL)
+ {
+ /* FIXME: is it possible that history->data == NULL? */
+ view->last_search_string = (gchar *) history->data;
+ history->data = NULL;
+ history = g_list_first (history);
+ g_list_free_full (history, g_free);
+
+ if (mcview_search_init (view))
+ {
+ mcview_search (view, FALSE);
+ return;
+ }
+
+ /* found, but cannot init search */
+ MC_PTR_FREE (view->last_search_string);
+ }
+
+ /* if not... then ask for an expression */
+ mcview_search (view, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_hook (void *v)
+{
+ WView *view = (WView *) v;
+ WPanel *panel;
+
+ /* If the user is busy typing, wait until he finishes to update the
+ screen */
+ if (!is_idle ())
+ {
+ if (!hook_present (idle_hook, mcview_hook))
+ add_hook (&idle_hook, mcview_hook, v);
+ return;
+ }
+
+ delete_hook (&idle_hook, mcview_hook);
+
+ if (get_current_type () == view_listing)
+ panel = current_panel;
+ else if (get_other_type () == view_listing)
+ panel = other_panel;
+ else
+ return;
+
+ mcview_done (view);
+ mcview_init (view);
+ mcview_load (view, 0, panel_current_entry (panel)->fname->str, 0, 0, 0);
+ mcview_display (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+mcview_handle_editkey (WView * view, int key)
+{
+ struct hexedit_change_node *node;
+ int byte_val = -1;
+
+ /* Has there been a change at this position? */
+ node = view->change_list;
+ while ((node != NULL) && (node->offset != view->hex_cursor))
+ node = node->next;
+
+ if (!view->hexview_in_text)
+ {
+ /* Hex editing */
+ unsigned int hexvalue = 0;
+
+ if (key >= '0' && key <= '9')
+ hexvalue = 0 + (key - '0');
+ else if (key >= 'A' && key <= 'F')
+ hexvalue = 10 + (key - 'A');
+ else if (key >= 'a' && key <= 'f')
+ hexvalue = 10 + (key - 'a');
+ else
+ return MSG_NOT_HANDLED;
+
+ if (node != NULL)
+ byte_val = node->value;
+ else
+ mcview_get_byte (view, view->hex_cursor, &byte_val);
+
+ if (view->hexedit_lownibble)
+ byte_val = (byte_val & 0xf0) | (hexvalue);
+ else
+ byte_val = (byte_val & 0x0f) | (hexvalue << 4);
+ }
+ else
+ {
+ /* Text editing */
+ if (key < 256 && key != '\t')
+ byte_val = key;
+ else
+ return MSG_NOT_HANDLED;
+ }
+
+ if ((view->filename_vpath != NULL)
+ && (*(vfs_path_get_last_path_str (view->filename_vpath)) != '\0')
+ && (view->change_list == NULL))
+ view->locked = lock_file (view->filename_vpath);
+
+ if (node == NULL)
+ {
+ node = g_new (struct hexedit_change_node, 1);
+ node->offset = view->hex_cursor;
+ node->value = byte_val;
+ mcview_enqueue_change (&view->change_list, node);
+ }
+ else
+ node->value = byte_val;
+
+ view->dirty++;
+ mcview_move_right (view, 1);
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_load_next_prev_init (WView * view)
+{
+ if (mc_global.mc_run_mode != MC_RUN_VIEWER)
+ {
+ /* get file list from current panel. Update it each time */
+ view->dir = &current_panel->dir;
+ view->dir_idx = &current_panel->current;
+ }
+ else if (view->dir == NULL)
+ {
+ /* Run from command line */
+ /* Run 1st time. Load/get directory */
+
+ /* TODO: check mtime of directory to reload it */
+
+ dir_sort_options_t sort_op = { FALSE, TRUE, FALSE };
+
+ /* load directory where requested file is */
+ view->dir = g_new0 (dir_list, 1);
+ view->dir_idx = g_new (int, 1);
+
+ if (dir_list_load
+ (view->dir, view->workdir_vpath, (GCompareFunc) sort_name, &sort_op, NULL))
+ {
+ const char *fname;
+ size_t fname_len;
+ int i;
+
+ fname = x_basename (vfs_path_as_str (view->filename_vpath));
+ fname_len = strlen (fname);
+
+ /* search current file in the list */
+ for (i = 0; i != view->dir->len; i++)
+ {
+ const file_entry_t *fe = &view->dir->list[i];
+
+ if (fname_len == fe->fname->len && strncmp (fname, fe->fname->str, fname_len) == 0)
+ break;
+ }
+
+ *view->dir_idx = i;
+ }
+ else
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
+ MC_PTR_FREE (view->dir);
+ MC_PTR_FREE (view->dir_idx);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_scan_for_file (WView * view, int direction)
+{
+ int i;
+
+ for (i = *view->dir_idx + direction; i != *view->dir_idx; i += direction)
+ {
+ if (i < 0)
+ i = view->dir->len - 1;
+ if (i == view->dir->len)
+ i = 0;
+ if (!S_ISDIR (view->dir->list[i].st.st_mode))
+ break;
+ }
+
+ *view->dir_idx = i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_load_next_prev (WView * view, int direction)
+{
+ dir_list *dir;
+ int *dir_idx;
+ vfs_path_t *vfile;
+ vfs_path_t *ext_script = NULL;
+
+ mcview_load_next_prev_init (view);
+ mcview_scan_for_file (view, direction);
+
+ /* reinit view */
+ dir = view->dir;
+ dir_idx = view->dir_idx;
+ view->dir = NULL;
+ view->dir_idx = NULL;
+ vfile =
+ vfs_path_append_new (view->workdir_vpath, dir->list[*dir_idx].fname->str, (char *) NULL);
+ mcview_done (view);
+ mcview_remove_ext_script (view);
+ mcview_init (view);
+ if (regex_command_for (view, vfile, "View", &ext_script) == 0)
+ mcview_load (view, NULL, vfs_path_as_str (vfile), 0, 0, 0);
+ vfs_path_free (vfile, TRUE);
+ view->dir = dir;
+ view->dir_idx = dir_idx;
+ view->ext_script = ext_script;
+
+ view->dpy_bbar_dirty = FALSE; /* FIXME */
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_load_file_from_history (WView * view)
+{
+ char *filename;
+ int action;
+
+ filename = show_file_history (CONST_WIDGET (view), &action);
+
+ if (filename != NULL && (action == CK_View || action == CK_Enter))
+ {
+ mcview_done (view);
+ mcview_init (view);
+
+ mcview_load (view, NULL, filename, 0, 0, 0);
+
+ view->dpy_bbar_dirty = FALSE; /* FIXME */
+ view->dirty++;
+ }
+
+ g_free (filename);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+mcview_execute_cmd (WView * view, long command)
+{
+ int res = MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_Help:
+ {
+ ev_help_t event_data = { NULL, "[Internal File Viewer]" };
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+ }
+ break;
+ case CK_HexMode:
+ /* Toggle between hex view and text view */
+ mcview_toggle_hex_mode (view);
+ break;
+ case CK_HexEditMode:
+ /* Toggle between hexview and hexedit mode */
+ mcview_toggle_hexedit_mode (view);
+ break;
+ case CK_ToggleNavigation:
+ view->hexview_in_text = !view->hexview_in_text;
+ view->dirty++;
+ break;
+ case CK_LeftQuick:
+ if (!view->mode_flags.hex)
+ mcview_move_left (view, 10);
+ break;
+ case CK_RightQuick:
+ if (!view->mode_flags.hex)
+ mcview_move_right (view, 10);
+ break;
+ case CK_Goto:
+ {
+ off_t addr;
+
+ if (mcview_dialog_goto (view, &addr))
+ {
+ if (addr >= 0)
+ mcview_moveto_offset (view, addr);
+ else
+ {
+ message (D_ERROR, _("Warning"), "%s", _("Invalid value"));
+ view->dirty++;
+ }
+ }
+ break;
+ }
+ case CK_Save:
+ mcview_hexedit_save_changes (view);
+ break;
+ case CK_Search:
+ mcview_search (view, TRUE);
+ break;
+ case CK_SearchContinue:
+ mcview_continue_search_cmd (view);
+ break;
+ case CK_SearchForward:
+ mcview_search_options.backwards = FALSE;
+ mcview_search (view, TRUE);
+ break;
+ case CK_SearchForwardContinue:
+ mcview_search_options.backwards = FALSE;
+ mcview_continue_search_cmd (view);
+ break;
+ case CK_SearchBackward:
+ mcview_search_options.backwards = TRUE;
+ mcview_search (view, TRUE);
+ break;
+ case CK_SearchBackwardContinue:
+ mcview_search_options.backwards = TRUE;
+ mcview_continue_search_cmd (view);
+ break;
+ case CK_SearchOppositeContinue:
+ {
+ gboolean direction;
+
+ direction = mcview_search_options.backwards;
+ mcview_search_options.backwards = !direction;
+ mcview_continue_search_cmd (view);
+ mcview_search_options.backwards = direction;
+ }
+ break;
+ case CK_WrapMode:
+ /* Toggle between wrapped and unwrapped view */
+ mcview_toggle_wrap_mode (view);
+ break;
+ case CK_MagicMode:
+ mcview_toggle_magic_mode (view);
+ break;
+ case CK_NroffMode:
+ mcview_toggle_nroff_mode (view);
+ break;
+ case CK_Home:
+ mcview_moveto_bol (view);
+ break;
+ case CK_End:
+ mcview_moveto_eol (view);
+ break;
+ case CK_Left:
+ mcview_move_left (view, 1);
+ break;
+ case CK_Right:
+ mcview_move_right (view, 1);
+ break;
+ case CK_Up:
+ mcview_move_up (view, 1);
+ break;
+ case CK_Down:
+ mcview_move_down (view, 1);
+ break;
+ case CK_HalfPageUp:
+ mcview_move_up (view, (view->data_area.lines + 1) / 2);
+ break;
+ case CK_HalfPageDown:
+ mcview_move_down (view, (view->data_area.lines + 1) / 2);
+ break;
+ case CK_PageUp:
+ mcview_move_up (view, view->data_area.lines);
+ break;
+ case CK_PageDown:
+ mcview_move_down (view, view->data_area.lines);
+ break;
+ case CK_Top:
+ mcview_moveto_top (view);
+ break;
+ case CK_Bottom:
+ mcview_moveto_bottom (view);
+ break;
+ case CK_Shell:
+ toggle_subshell ();
+ break;
+ case CK_Ruler:
+ mcview_display_toggle_ruler (view);
+ break;
+ case CK_Bookmark:
+ view->dpy_start = view->marks[view->marker];
+ view->dpy_paragraph_skip_lines = 0; /* TODO: remember this value in the marker? */
+ view->dpy_wrap_dirty = TRUE;
+ view->dirty++;
+ break;
+ case CK_BookmarkGoto:
+ view->marks[view->marker] = view->dpy_start;
+ break;
+#ifdef HAVE_CHARSET
+ case CK_SelectCodepage:
+ mcview_select_encoding (view);
+ view->dirty++;
+ break;
+#endif
+ case CK_FileNext:
+ case CK_FilePrev:
+ /* Does not work in panel mode */
+ if (!mcview_is_in_panel (view))
+ mcview_load_next_prev (view, command == CK_FileNext ? 1 : -1);
+ break;
+ case CK_History:
+ mcview_load_file_from_history (view);
+ break;
+ case CK_Quit:
+ if (!mcview_is_in_panel (view))
+ dlg_close (DIALOG (WIDGET (view)->owner));
+ break;
+ case CK_Cancel:
+ /* don't close viewer due to SIGINT */
+ break;
+ default:
+ res = MSG_NOT_HANDLED;
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static long
+mcview_lookup_key (WView * view, int key)
+{
+ if (view->mode_flags.hex)
+ return keybind_lookup_keymap_command (view->hex_keymap, key);
+
+ return widget_lookup_key (WIDGET (view), key);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Both views */
+static cb_ret_t
+mcview_handle_key (WView * view, int key)
+{
+ long command;
+
+#ifdef HAVE_CHARSET
+ key = convert_from_input_c (key);
+#endif
+
+ if (view->hexedit_mode && view->mode_flags.hex
+ && mcview_handle_editkey (view, key) == MSG_HANDLED)
+ return MSG_HANDLED;
+
+ command = mcview_lookup_key (view, key);
+ if (command != CK_IgnoreKey && mcview_execute_cmd (view, command) == MSG_HANDLED)
+ return MSG_HANDLED;
+
+#ifdef MC_ENABLE_DEBUGGING_CODE
+ if (key == 't')
+ { /* mnemonic: "test" */
+ mcview_ccache_dump (view);
+ return MSG_HANDLED;
+ }
+#endif
+ if (key >= '0' && key <= '9')
+ view->marker = key - '0';
+
+ /* Key not used */
+ return MSG_NOT_HANDLED;
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+mcview_resize (WView * view)
+{
+ view->dpy_wrap_dirty = TRUE;
+ mcview_compute_areas (view);
+ mcview_update_bytes_per_line (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_ok_to_quit (WView * view)
+{
+ int r;
+
+ if (view->change_list == NULL)
+ return TRUE;
+
+ if (!mc_global.midnight_shutdown)
+ {
+ query_set_sel (2);
+ r = query_dialog (_("Quit"),
+ _("File was modified. Save with exit?"), D_NORMAL, 3,
+ _("&Yes"), _("&No"), _("&Cancel quit"));
+ }
+ else
+ {
+ r = query_dialog (_("Quit"),
+ _("Midnight Commander is being shut down.\nSave modified file?"),
+ D_NORMAL, 2, _("&Yes"), _("&No"));
+ /* Esc is No */
+ if (r == -1)
+ r = 1;
+ }
+
+ switch (r)
+ {
+ case 0: /* Yes */
+ return mcview_hexedit_save_changes (view) || mc_global.midnight_shutdown;
+ case 1: /* No */
+ mcview_hexedit_free_change_list (view);
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+mcview_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WView *view = (WView *) w;
+ cb_ret_t i;
+
+ mcview_compute_areas (view);
+ mcview_update_bytes_per_line (view);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ if (mcview_is_in_panel (view))
+ add_hook (&select_file_hook, mcview_hook, view);
+ else
+ view->dpy_bbar_dirty = TRUE;
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ mcview_display (view);
+ return MSG_HANDLED;
+
+ case MSG_CURSOR:
+ if (view->mode_flags.hex)
+ mcview_place_cursor (view);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ i = mcview_handle_key (view, parm);
+ mcview_update (view);
+ return i;
+
+ case MSG_ACTION:
+ i = mcview_execute_cmd (view, parm);
+ mcview_update (view);
+ return i;
+
+ case MSG_FOCUS:
+ view->dpy_bbar_dirty = TRUE;
+ /* TODO: get rid of draw here before MSG_DRAW */
+ mcview_update (view);
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ widget_default_callback (w, NULL, MSG_RESIZE, 0, data);
+ mcview_resize (view);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ if (mcview_is_in_panel (view))
+ {
+ delete_hook (&select_file_hook, mcview_hook);
+
+ /*
+ * In some cases when mc startup is very slow and one panel is in quick view mode,
+ * @view is registered in two hook lists at the same time:
+ * mcview_callback (MSG_INIT) -> add_hook (&select_file_hook)
+ * mcview_hook () -> add_hook (&idle_hook).
+ * If initialization of file manager is not completed yet, but user switches
+ * panel mode from qick view to another one (by pressing C-x q), the following
+ * occurs:
+ * view hook is deleted from select_file_hook list via following call chain:
+ * create_panel (view_listing) -> widget_replace () ->
+ * send_message (MSG_DESTROY) -> mcview_callback (MSG_DESTROY) ->
+ * delete_hook (&select_file_hook);
+ * @view object is free'd:
+ * create_panel (view_listing) -> g_free (old_widget);
+ * but @view still is in idle_hook list and tried to be executed:
+ * frontend_dlg_run () -> execute_hooks (idle_hook).
+ * Thus here we have access to free'd @view object. To prevent this, remove view hook
+ * from idle_hook list.
+ */
+ delete_hook (&idle_hook, mcview_hook);
+
+ if (mc_global.midnight_shutdown)
+ mcview_ok_to_quit (view);
+ }
+ mcview_done (view);
+ mcview_remove_ext_script (view);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+mcview_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+ WView *view;
+
+ switch (msg)
+ {
+ case MSG_ACTION:
+ /* Handle shortcuts. */
+
+ /* Note: the buttonbar sends messages directly to the the WView, not to
+ * here, which is why we can pass NULL in the following call. */
+ return mcview_execute_cmd (NULL, parm);
+
+ case MSG_VALIDATE:
+ view = (WView *) widget_find_by_type (w, mcview_callback);
+ /* don't stop the dialog before final decision */
+ widget_set_state (w, WST_ACTIVE, TRUE);
+ if (mcview_ok_to_quit (view))
+ dlg_close (h);
+ else
+ mcview_update (view);
+ return MSG_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */