mcview: allow set start and end of search result...
[midnight-commander.git] / src / viewer / actions_cmd.c
blob492dcfad18af300935c448cfce4cfc2a7fdf9606
1 /*
2 Internal file viewer for the Midnight Commander
3 Callback function for some actions (hotkeys, menu)
5 Copyright (C) 1994-2015
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, 2013
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 mcview_t 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 <errno.h>
48 #include <stdlib.h>
50 #include "lib/global.h"
52 #include "lib/tty/tty.h"
53 #include "lib/tty/key.h" /* is_idle() */
54 #include "lib/lock.h" /* lock_file() */
55 #include "lib/util.h"
56 #include "lib/widget.h"
57 #ifdef HAVE_CHARSET
58 #include "lib/charsets.h"
59 #endif
60 #include "lib/event.h" /* mc_event_raise() */
62 #include "src/filemanager/layout.h"
63 #include "src/filemanager/cmd.h"
64 #include "src/filemanager/midnight.h" /* current_panel */
65 #include "src/filemanager/ext.h" /* regex_command_for() */
67 #include "src/history.h"
68 #include "src/execute.h"
69 #include "src/keybind-defaults.h"
71 #include "internal.h"
73 /*** global variables ****************************************************************************/
75 /*** file scope macro definitions ****************************************************************/
77 /*** file scope type declarations ****************************************************************/
79 /*** file scope variables ************************************************************************/
81 /* --------------------------------------------------------------------------------------------- */
82 /*** file scope functions ************************************************************************/
83 /* --------------------------------------------------------------------------------------------- */
85 static void
86 mcview_remove_ext_script (mcview_t * view)
88 if (view->ext_script != NULL)
90 mc_unlink (view->ext_script);
91 vfs_path_free (view->ext_script);
92 view->ext_script = NULL;
96 /* --------------------------------------------------------------------------------------------- */
98 /* Both views */
99 static void
100 mcview_search (mcview_t * view, gboolean start_search)
102 off_t want_search_start = view->search_start;
104 if (start_search)
106 if (mcview_dialog_search (view))
108 if (view->hex_mode)
109 want_search_start = view->hex_cursor;
111 mcview_do_search (view, want_search_start);
114 else
116 if (view->hex_mode)
118 if (!mcview_search_options.backwards)
119 want_search_start = view->hex_cursor + 1;
120 else if (view->hex_cursor > 0)
121 want_search_start = view->hex_cursor - 1;
122 else
123 want_search_start = 0;
126 mcview_do_search (view, want_search_start);
130 /* --------------------------------------------------------------------------------------------- */
132 static void
133 mcview_continue_search_cmd (mcview_t * view)
135 if (view->last_search_string != NULL)
136 mcview_search (view, FALSE);
137 else
139 /* find last search string in history */
140 GList *history;
142 history = history_get (MC_HISTORY_SHARED_SEARCH);
143 if (history != NULL && history->data != NULL)
145 view->last_search_string = (gchar *) g_strdup (history->data);
146 history = g_list_first (history);
147 g_list_free_full (history, g_free);
149 #ifdef HAVE_CHARSET
150 view->search = mc_search_new (view->last_search_string, -1, cp_source);
151 #else
152 view->search = mc_search_new (view->last_search_string, -1, NULL);
153 #endif
154 view->search_nroff_seq = mcview_nroff_seq_new (view);
156 if (view->search == NULL)
158 /* if not... then ask for an expression */
159 MC_PTR_FREE (view->last_search_string);
160 mcview_search (view, TRUE);
162 else
164 view->search->search_type = mcview_search_options.type;
165 #ifdef HAVE_CHARSET
166 view->search->is_all_charsets = mcview_search_options.all_codepages;
167 #endif
168 view->search->is_case_sensitive = mcview_search_options.case_sens;
169 view->search->whole_words = mcview_search_options.whole_words;
170 view->search->search_fn = mcview_search_cmd_callback;
171 view->search->update_fn = mcview_search_update_cmd_callback;
173 mcview_search (view, FALSE);
176 else
178 /* if not... then ask for an expression */
179 MC_PTR_FREE (view->last_search_string);
180 mcview_search (view, TRUE);
185 /* --------------------------------------------------------------------------------------------- */
187 static void
188 mcview_hook (void *v)
190 mcview_t *view = (mcview_t *) v;
191 WPanel *panel;
193 /* If the user is busy typing, wait until he finishes to update the
194 screen */
195 if (!is_idle ())
197 if (!hook_present (idle_hook, mcview_hook))
198 add_hook (&idle_hook, mcview_hook, v);
199 return;
202 delete_hook (&idle_hook, mcview_hook);
204 if (get_current_type () == view_listing)
205 panel = current_panel;
206 else if (get_other_type () == view_listing)
207 panel = other_panel;
208 else
209 return;
211 mcview_done (view);
212 mcview_init (view);
213 mcview_load (view, 0, panel->dir.list[panel->selected].fname, 0, 0, 0);
214 mcview_display (view);
217 /* --------------------------------------------------------------------------------------------- */
219 static cb_ret_t
220 mcview_handle_editkey (mcview_t * view, int key)
222 struct hexedit_change_node *node;
223 int byte_val;
225 /* Has there been a change at this position? */
226 node = view->change_list;
227 while ((node != NULL) && (node->offset != view->hex_cursor))
228 node = node->next;
230 if (!view->hexview_in_text)
232 /* Hex editing */
233 unsigned int hexvalue = 0;
235 if (key >= '0' && key <= '9')
236 hexvalue = 0 + (key - '0');
237 else if (key >= 'A' && key <= 'F')
238 hexvalue = 10 + (key - 'A');
239 else if (key >= 'a' && key <= 'f')
240 hexvalue = 10 + (key - 'a');
241 else
242 return MSG_NOT_HANDLED;
244 if (node != NULL)
245 byte_val = node->value;
246 else
247 mcview_get_byte (view, view->hex_cursor, &byte_val);
249 if (view->hexedit_lownibble)
250 byte_val = (byte_val & 0xf0) | (hexvalue);
251 else
252 byte_val = (byte_val & 0x0f) | (hexvalue << 4);
254 else
256 /* Text editing */
257 if (key < 256 && key != '\t')
258 byte_val = key;
259 else
260 return MSG_NOT_HANDLED;
263 if ((view->filename_vpath != NULL)
264 && (*(vfs_path_get_last_path_str (view->filename_vpath)) != '\0')
265 && (view->change_list == NULL))
266 view->locked = lock_file (view->filename_vpath);
268 if (node == NULL)
270 node = g_new (struct hexedit_change_node, 1);
271 node->offset = view->hex_cursor;
272 node->value = byte_val;
273 mcview_enqueue_change (&view->change_list, node);
275 else
276 node->value = byte_val;
278 view->dirty++;
279 mcview_move_right (view, 1);
281 return MSG_HANDLED;
284 /* --------------------------------------------------------------------------------------------- */
286 static void
287 mcview_load_next_prev_init (mcview_t * view)
289 if (mc_global.mc_run_mode != MC_RUN_VIEWER)
291 /* get file list from current panel. Update it each time */
292 view->dir = &current_panel->dir;
293 view->dir_idx = &current_panel->selected;
295 else if (view->dir == NULL)
297 /* Run from command line */
298 /* Run 1st time. Load/get directory */
300 /* TODO: check mtime of directory to reload it */
302 const char *fname;
303 size_t fname_len;
304 int i;
305 dir_sort_options_t sort_op = { FALSE, TRUE, FALSE };
307 /* load directory where requested file is */
308 view->dir = g_new0 (dir_list, 1);
309 view->dir_idx = g_new (int, 1);
311 dir_list_load (view->dir, view->workdir_vpath, (GCompareFunc) sort_name, &sort_op, NULL);
313 fname = x_basename (vfs_path_as_str (view->filename_vpath));
314 fname_len = strlen (fname);
316 /* search current file in the list */
317 for (i = 0; i != view->dir->len; i++)
319 const file_entry_t *fe = &view->dir->list[i];
321 if (fname_len == fe->fnamelen && strncmp (fname, fe->fname, fname_len) == 0)
322 break;
325 *view->dir_idx = i;
329 /* --------------------------------------------------------------------------------------------- */
331 static void
332 mcview_scan_for_file (mcview_t * view, int direction)
334 int i;
336 for (i = *view->dir_idx + direction; i != *view->dir_idx; i += direction)
338 if (i < 0)
339 i = view->dir->len - 1;
340 if (i == view->dir->len)
341 i = 0;
342 if (!S_ISDIR (view->dir->list[i].st.st_mode))
343 break;
346 *view->dir_idx = i;
349 /* --------------------------------------------------------------------------------------------- */
351 static void
352 mcview_load_next_prev (mcview_t * view, int direction)
354 dir_list *dir;
355 int *dir_idx;
356 vfs_path_t *vfile;
357 vfs_path_t *ext_script = NULL;
359 mcview_load_next_prev_init (view);
360 mcview_scan_for_file (view, direction);
362 /* reinit view */
363 dir = view->dir;
364 dir_idx = view->dir_idx;
365 view->dir = NULL;
366 view->dir_idx = NULL;
367 vfile = vfs_path_append_new (view->workdir_vpath, dir->list[*dir_idx].fname, (char *) NULL);
368 mcview_done (view);
369 mcview_remove_ext_script (view);
370 mcview_init (view);
371 if (regex_command_for (view, vfile, "View", &ext_script) == 0)
372 mcview_load (view, NULL, vfs_path_as_str (vfile), 0, 0, 0);
373 vfs_path_free (vfile);
374 view->dir = dir;
375 view->dir_idx = dir_idx;
376 view->ext_script = ext_script;
378 view->dpy_bbar_dirty = FALSE; /* FIXME */
379 view->dirty++;
382 /* --------------------------------------------------------------------------------------------- */
384 static cb_ret_t
385 mcview_execute_cmd (mcview_t * view, unsigned long command)
387 int res = MSG_HANDLED;
389 switch (command)
391 case CK_Help:
393 ev_help_t event_data = { NULL, "[Internal File Viewer]" };
394 mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
396 break;
397 case CK_WrapMode:
398 /* Toggle between wrapped and unwrapped view */
399 mcview_toggle_wrap_mode (view);
400 break;
401 case CK_HexEditMode:
402 /* Toggle between hexview and hexedit mode */
403 mcview_toggle_hexedit_mode (view);
404 break;
405 case CK_HexMode:
406 /* Toggle between hex view and text view */
407 mcview_toggle_hex_mode (view);
408 break;
409 case CK_Goto:
411 off_t addr;
413 if (mcview_dialog_goto (view, &addr))
415 if (addr >= 0)
416 mcview_moveto_offset (view, addr);
417 else
419 message (D_ERROR, _("Warning"), _("Invalid value"));
420 view->dirty++;
423 break;
425 case CK_Save:
426 mcview_hexedit_save_changes (view);
427 break;
428 case CK_Search:
429 mcview_search (view, TRUE);
430 break;
431 case CK_SearchForward:
432 mcview_search_options.backwards = FALSE;
433 mcview_search (view, TRUE);
434 break;
435 case CK_SearchBackward:
436 mcview_search_options.backwards = TRUE;
437 mcview_search (view, TRUE);
438 break;
439 case CK_MagicMode:
440 mcview_toggle_magic_mode (view);
441 break;
442 case CK_NroffMode:
443 mcview_toggle_nroff_mode (view);
444 break;
445 case CK_ToggleNavigation:
446 view->hexview_in_text = !view->hexview_in_text;
447 view->dirty++;
448 break;
449 case CK_Home:
450 mcview_moveto_bol (view);
451 break;
452 case CK_End:
453 mcview_moveto_eol (view);
454 break;
455 case CK_Left:
456 mcview_move_left (view, 1);
457 break;
458 case CK_Right:
459 mcview_move_right (view, 1);
460 break;
461 case CK_LeftQuick:
462 if (!view->hex_mode)
463 mcview_move_left (view, 10);
464 break;
465 case CK_RightQuick:
466 if (!view->hex_mode)
467 mcview_move_right (view, 10);
468 break;
469 case CK_SearchContinue:
470 mcview_continue_search_cmd (view);
471 break;
472 case CK_SearchForwardContinue:
473 mcview_search_options.backwards = FALSE;
474 mcview_continue_search_cmd (view);
475 break;
476 case CK_SearchBackwardContinue:
477 mcview_search_options.backwards = TRUE;
478 mcview_continue_search_cmd (view);
479 break;
480 case CK_Ruler:
481 mcview_display_toggle_ruler (view);
482 break;
483 case CK_Up:
484 mcview_move_up (view, 1);
485 break;
486 case CK_Down:
487 mcview_move_down (view, 1);
488 break;
489 case CK_HalfPageUp:
490 mcview_move_up (view, (view->data_area.height + 1) / 2);
491 break;
492 case CK_HalfPageDown:
493 mcview_move_down (view, (view->data_area.height + 1) / 2);
494 break;
495 case CK_PageUp:
496 mcview_move_up (view, view->data_area.height);
497 break;
498 case CK_PageDown:
499 mcview_move_down (view, view->data_area.height);
500 break;
501 case CK_Top:
502 mcview_moveto_top (view);
503 break;
504 case CK_Bottom:
505 mcview_moveto_bottom (view);
506 break;
507 case CK_Shell:
508 view_other_cmd ();
509 break;
510 case CK_BookmarkGoto:
511 view->marks[view->marker] = view->dpy_start;
512 break;
513 case CK_Bookmark:
514 view->dpy_start = view->marks[view->marker];
515 view->dpy_paragraph_skip_lines = 0; /* TODO: remember this value in the marker? */
516 view->dpy_wrap_dirty = TRUE;
517 view->dirty++;
518 break;
519 #ifdef HAVE_CHARSET
520 case CK_SelectCodepage:
521 mcview_select_encoding (view);
522 view->dirty++;
523 break;
524 #endif
525 case CK_FileNext:
526 case CK_FilePrev:
527 /* Does not work in panel mode */
528 if (!mcview_is_in_panel (view))
529 mcview_load_next_prev (view, command == CK_FileNext ? 1 : -1);
530 break;
531 case CK_Quit:
532 if (!mcview_is_in_panel (view))
533 dlg_stop (WIDGET (view)->owner);
534 break;
535 case CK_Cancel:
536 /* don't close viewer due to SIGINT */
537 break;
538 default:
539 res = MSG_NOT_HANDLED;
541 return res;
544 /* --------------------------------------------------------------------------------------------- */
545 /** Both views */
546 static cb_ret_t
547 mcview_handle_key (mcview_t * view, int key)
549 unsigned long command;
551 #ifdef HAVE_CHARSET
552 key = convert_from_input_c (key);
553 #endif
555 if (view->hex_mode)
557 if (view->hexedit_mode && (mcview_handle_editkey (view, key) == MSG_HANDLED))
558 return MSG_HANDLED;
560 command = keybind_lookup_keymap_command (viewer_hex_map, key);
561 if ((command != CK_IgnoreKey) && (mcview_execute_cmd (view, command) == MSG_HANDLED))
562 return MSG_HANDLED;
565 command = keybind_lookup_keymap_command (viewer_map, key);
566 if ((command != CK_IgnoreKey) && (mcview_execute_cmd (view, command) == MSG_HANDLED))
567 return MSG_HANDLED;
569 #ifdef MC_ENABLE_DEBUGGING_CODE
570 if (c == 't')
571 { /* mnemonic: "test" */
572 mcview_ccache_dump (view);
573 return MSG_HANDLED;
575 #endif
576 if (key >= '0' && key <= '9')
577 view->marker = key - '0';
579 /* Key not used */
580 return MSG_NOT_HANDLED;
584 /* --------------------------------------------------------------------------------------------- */
586 static inline void
587 mcview_adjust_size (WDialog * h)
589 mcview_t *view;
590 WButtonBar *b;
592 /* Look up the viewer and the buttonbar, we assume only two widgets here */
593 view = (mcview_t *) find_widget_type (h, mcview_callback);
594 b = find_buttonbar (h);
596 widget_set_size (WIDGET (view), 0, 0, LINES - 1, COLS);
597 widget_set_size (WIDGET (b), LINES - 1, 0, 1, COLS);
599 view->dpy_wrap_dirty = TRUE;
600 mcview_compute_areas (view);
601 mcview_update_bytes_per_line (view);
604 /* --------------------------------------------------------------------------------------------- */
606 static gboolean
607 mcview_ok_to_quit (mcview_t * view)
609 int r;
611 if (view->change_list == NULL)
612 return TRUE;
614 if (!mc_global.midnight_shutdown)
616 query_set_sel (2);
617 r = query_dialog (_("Quit"),
618 _("File was modified. Save with exit?"), D_NORMAL, 3,
619 _("&Yes"), _("&No"), _("&Cancel quit"));
621 else
623 r = query_dialog (_("Quit"),
624 _("Midnight Commander is being shut down.\nSave modified file?"),
625 D_NORMAL, 2, _("&Yes"), _("&No"));
626 /* Esc is No */
627 if (r == -1)
628 r = 1;
631 switch (r)
633 case 0: /* Yes */
634 return mcview_hexedit_save_changes (view) || mc_global.midnight_shutdown;
635 case 1: /* No */
636 mcview_hexedit_free_change_list (view);
637 return TRUE;
638 default:
639 return FALSE;
643 /* --------------------------------------------------------------------------------------------- */
644 /*** public functions ****************************************************************************/
645 /* --------------------------------------------------------------------------------------------- */
647 cb_ret_t
648 mcview_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
650 mcview_t *view = (mcview_t *) w;
651 cb_ret_t i;
653 mcview_compute_areas (view);
654 mcview_update_bytes_per_line (view);
656 switch (msg)
658 case MSG_INIT:
659 if (mcview_is_in_panel (view))
660 add_hook (&select_file_hook, mcview_hook, view);
661 else
662 view->dpy_bbar_dirty = TRUE;
663 return MSG_HANDLED;
665 case MSG_DRAW:
666 mcview_display (view);
667 return MSG_HANDLED;
669 case MSG_CURSOR:
670 if (view->hex_mode)
671 mcview_place_cursor (view);
672 return MSG_HANDLED;
674 case MSG_KEY:
675 i = mcview_handle_key (view, parm);
676 mcview_update (view);
677 /* don't pass any chars to command line in QuickView mode */
678 return mcview_is_in_panel (view) ? MSG_HANDLED : i;
680 case MSG_ACTION:
681 i = mcview_execute_cmd (view, parm);
682 mcview_update (view);
683 return i;
685 case MSG_FOCUS:
686 view->active = TRUE;
687 view->dpy_bbar_dirty = TRUE;
688 mcview_update (view);
689 return MSG_HANDLED;
691 case MSG_UNFOCUS:
692 view->active = FALSE;
693 return MSG_HANDLED;
695 case MSG_DESTROY:
696 if (mcview_is_in_panel (view))
698 delete_hook (&select_file_hook, mcview_hook);
700 if (mc_global.midnight_shutdown)
701 mcview_ok_to_quit (view);
703 mcview_done (view);
704 mcview_remove_ext_script (view);
705 return MSG_HANDLED;
707 default:
708 return widget_default_callback (w, sender, msg, parm, data);
712 /* --------------------------------------------------------------------------------------------- */
714 cb_ret_t
715 mcview_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
717 WDialog *h = DIALOG (w);
718 mcview_t *view;
720 switch (msg)
722 case MSG_RESIZE:
723 mcview_adjust_size (h);
724 return MSG_HANDLED;
726 case MSG_ACTION:
727 /* shortcut */
728 if (sender == NULL)
729 return mcview_execute_cmd (NULL, parm);
730 /* message from buttonbar */
731 if (sender == WIDGET (find_buttonbar (h)))
733 if (data != NULL)
734 return send_message (data, NULL, MSG_ACTION, parm, NULL);
736 view = (mcview_t *) find_widget_type (h, mcview_callback);
737 return mcview_execute_cmd (view, parm);
739 return MSG_NOT_HANDLED;
741 case MSG_VALIDATE:
742 view = (mcview_t *) find_widget_type (h, mcview_callback);
743 h->state = DLG_ACTIVE; /* don't stop the dialog before final decision */
744 if (mcview_ok_to_quit (view))
745 h->state = DLG_CLOSED;
746 else
747 mcview_update (view);
748 return MSG_HANDLED;
750 default:
751 return dlg_default_callback (w, sender, msg, parm, data);
755 /* --------------------------------------------------------------------------------------------- */