Merge branch '4609_rust_crate'
[midnight-commander.git] / src / viewer / mcviewer.c
blob121b0f5affe3ed7ad366c05a7bfef60a896854b2
1 /*
2 Internal file viewer for the Midnight Commander
3 Interface functions
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/>.
36 #include <config.h>
37 #include <errno.h>
39 #include "lib/global.h"
40 #include "lib/tty/tty.h"
41 #include "lib/vfs/vfs.h"
42 #include "lib/strutil.h"
43 #include "lib/util.h" /* load_file_position() */
44 #include "lib/widget.h"
46 #include "src/filemanager/layout.h"
47 #include "src/filemanager/filemanager.h" /* the_menubar */
49 #include "internal.h"
51 /*** global variables ****************************************************************************/
53 mcview_mode_flags_t mcview_global_flags = {
54 .wrap = TRUE,
55 .hex = FALSE,
56 .magic = TRUE,
57 .nroff = FALSE
60 mcview_mode_flags_t mcview_altered_flags = {
61 .wrap = FALSE,
62 .hex = FALSE,
63 .magic = FALSE,
64 .nroff = FALSE
67 gboolean mcview_remember_file_position = FALSE;
69 /* Maxlimit for skipping updates */
70 int mcview_max_dirt_limit = 10;
72 /* Scrolling is done in pages or line increments */
73 gboolean mcview_mouse_move_pages = TRUE;
75 /* end of file will be showen from mcview_show_eof */
76 char *mcview_show_eof = NULL;
78 /*** file scope macro definitions ****************************************************************/
80 /*** file scope type declarations ****************************************************************/
82 /*** forward declarations (file scope functions) *************************************************/
84 /*** file scope variables ************************************************************************/
86 /* --------------------------------------------------------------------------------------------- */
87 /*** file scope functions ************************************************************************/
88 /* --------------------------------------------------------------------------------------------- */
90 static void
91 mcview_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
93 WView *view = (WView *) w;
94 const WRect *r = &view->data_area;
95 gboolean ok = TRUE;
97 switch (msg)
99 case MSG_MOUSE_DOWN:
100 if (mcview_is_in_panel (view))
102 if (event->y == WIDGET (w->owner)->rect.y)
104 /* return MOU_UNHANDLED */
105 event->result.abort = TRUE;
106 /* don't draw viewer over menu */
107 ok = FALSE;
108 break;
111 if (!widget_get_state (w, WST_FOCUSED))
113 /* Grab focus */
114 (void) change_panel ();
117 MC_FALLTHROUGH;
119 case MSG_MOUSE_CLICK:
120 if (!view->mode_flags.wrap)
122 /* Scrolling left and right */
123 int x;
125 x = event->x + 1; /* FIXME */
127 if (x < r->cols * 1 / 4)
129 mcview_move_left (view, 1);
130 event->result.repeat = msg == MSG_MOUSE_DOWN;
132 else if (x < r->cols * 3 / 4)
134 /* ignore the click */
135 ok = FALSE;
137 else
139 mcview_move_right (view, 1);
140 event->result.repeat = msg == MSG_MOUSE_DOWN;
143 else
145 /* Scrolling up and down */
146 int y;
148 y = event->y + 1; /* FIXME */
150 if (y < r->y + r->lines * 1 / 3)
152 if (mcview_mouse_move_pages)
153 mcview_move_up (view, r->lines / 2);
154 else
155 mcview_move_up (view, 1);
157 event->result.repeat = msg == MSG_MOUSE_DOWN;
159 else if (y < r->y + r->lines * 2 / 3)
161 /* ignore the click */
162 ok = FALSE;
164 else
166 if (mcview_mouse_move_pages)
167 mcview_move_down (view, r->lines / 2);
168 else
169 mcview_move_down (view, 1);
171 event->result.repeat = msg == MSG_MOUSE_DOWN;
174 break;
176 case MSG_MOUSE_SCROLL_UP:
177 mcview_move_up (view, 2);
178 break;
180 case MSG_MOUSE_SCROLL_DOWN:
181 mcview_move_down (view, 2);
182 break;
184 default:
185 ok = FALSE;
186 break;
189 if (ok)
190 mcview_update (view);
193 /* --------------------------------------------------------------------------------------------- */
194 /*** public functions ****************************************************************************/
195 /* --------------------------------------------------------------------------------------------- */
197 WView *
198 mcview_new (const WRect *r, gboolean is_panel)
200 WView *view;
201 Widget *w;
203 view = g_new0 (WView, 1);
204 w = WIDGET (view);
206 widget_init (w, r, mcview_callback, mcview_mouse_callback);
207 w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
208 w->keymap = viewer_map;
210 mcview_clear_mode_flags (&view->mode_flags);
211 view->hexedit_mode = FALSE;
212 view->hex_keymap = viewer_hex_map;
213 view->hexview_in_text = FALSE;
214 view->locked = FALSE;
216 view->dpy_frame_size = is_panel ? 1 : 0;
217 view->converter = str_cnv_from_term;
219 mcview_init (view);
221 if (mcview_global_flags.hex)
222 mcview_toggle_hex_mode (view);
223 if (mcview_global_flags.nroff)
224 mcview_toggle_nroff_mode (view);
225 if (mcview_global_flags.wrap)
226 mcview_toggle_wrap_mode (view);
227 if (mcview_global_flags.magic)
228 mcview_toggle_magic_mode (view);
230 return view;
233 /* --------------------------------------------------------------------------------------------- */
234 /** Real view only */
236 gboolean
237 mcview_viewer (const char *command, const vfs_path_t *file_vpath, int start_line,
238 off_t search_start, off_t search_end)
240 gboolean succeeded;
241 WView *lc_mcview;
242 WDialog *view_dlg;
243 Widget *vw, *b;
244 WGroup *g;
245 WRect r;
247 /* Create dialog and widgets, put them on the dialog */
248 view_dlg = dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, mcview_dialog_callback,
249 NULL, "[Internal File Viewer]", NULL);
250 vw = WIDGET (view_dlg);
251 widget_want_tab (vw, TRUE);
253 g = GROUP (view_dlg);
255 r = vw->rect;
256 r.lines--;
257 lc_mcview = mcview_new (&r, FALSE);
258 group_add_widget_autopos (g, lc_mcview, WPOS_KEEP_ALL, NULL);
260 b = WIDGET (buttonbar_new ());
261 group_add_widget_autopos (g, b, b->pos_flags, NULL);
263 view_dlg->get_title = mcview_get_title;
265 succeeded =
266 mcview_load (lc_mcview, command, vfs_path_as_str (file_vpath), start_line, search_start,
267 search_end);
269 if (succeeded)
270 dlg_run (view_dlg);
271 else
272 dlg_close (view_dlg);
274 if (widget_get_state (vw, WST_CLOSED))
275 widget_destroy (vw);
277 return succeeded;
280 /* {{{ Miscellaneous functions }}} */
282 /* --------------------------------------------------------------------------------------------- */
284 gboolean
285 mcview_load (WView *view, const char *command, const char *file, int start_line,
286 off_t search_start, off_t search_end)
288 gboolean retval = FALSE;
289 vfs_path_t *vpath = NULL;
291 g_assert (view->bytes_per_line != 0);
293 view->filename_vpath = vfs_path_from_str (file);
295 /* get working dir */
296 if (file != NULL && file[0] != '\0')
298 vfs_path_free (view->workdir_vpath, TRUE);
300 if (!g_path_is_absolute (file))
302 vfs_path_t *p;
304 p = vfs_path_clone (vfs_get_raw_current_dir ());
305 view->workdir_vpath = vfs_path_append_new (p, file, (char *) NULL);
306 vfs_path_free (p, TRUE);
308 else
310 /* try extract path from filename */
311 const char *fname;
312 char *dir;
314 fname = x_basename (file);
315 dir = g_strndup (file, (size_t) (fname - file));
316 view->workdir_vpath = vfs_path_from_str (dir);
317 g_free (dir);
321 if (!mcview_is_in_panel (view))
322 view->dpy_text_column = 0;
324 #ifdef HAVE_CHARSET
325 mcview_set_codeset (view);
326 #endif
328 if (command != NULL && (view->mode_flags.magic || file == NULL || file[0] == '\0'))
329 retval = mcview_load_command_output (view, command);
330 else if (file != NULL && file[0] != '\0')
332 int fd;
333 char tmp[BUF_MEDIUM];
334 struct stat st;
336 /* Open the file */
337 vpath = vfs_path_from_str (file);
338 fd = mc_open (vpath, O_RDONLY | O_NONBLOCK);
339 if (fd == -1)
341 g_snprintf (tmp, sizeof (tmp), _("Cannot open \"%s\"\n%s"),
342 file, unix_error_string (errno));
343 mcview_close_datasource (view);
344 mcview_show_error (view, tmp);
345 vfs_path_free (view->filename_vpath, TRUE);
346 view->filename_vpath = NULL;
347 vfs_path_free (view->workdir_vpath, TRUE);
348 view->workdir_vpath = NULL;
349 goto finish;
352 /* Make sure we are working with a regular file */
353 if (mc_fstat (fd, &st) == -1)
355 mc_close (fd);
356 g_snprintf (tmp, sizeof (tmp), _("Cannot stat \"%s\"\n%s"),
357 file, unix_error_string (errno));
358 mcview_close_datasource (view);
359 mcview_show_error (view, tmp);
360 vfs_path_free (view->filename_vpath, TRUE);
361 view->filename_vpath = NULL;
362 vfs_path_free (view->workdir_vpath, TRUE);
363 view->workdir_vpath = NULL;
364 goto finish;
367 if (!S_ISREG (st.st_mode))
369 mc_close (fd);
370 mcview_close_datasource (view);
371 mcview_show_error (view, _("Cannot view: not a regular file"));
372 vfs_path_free (view->filename_vpath, TRUE);
373 view->filename_vpath = NULL;
374 vfs_path_free (view->workdir_vpath, TRUE);
375 view->workdir_vpath = NULL;
376 goto finish;
379 if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1)
381 /* Must be one of those nice files that grow (/proc) */
382 mcview_set_datasource_vfs_pipe (view, fd);
384 else
386 if (view->mode_flags.magic)
388 int type;
390 type = get_compression_type (fd, file);
392 if (type != COMPRESSION_NONE)
394 char *tmp_filename;
395 vfs_path_t *vpath1;
396 int fd1;
398 tmp_filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
399 vpath1 = vfs_path_from_str (tmp_filename);
400 g_free (tmp_filename);
401 fd1 = mc_open (vpath1, O_RDONLY | O_NONBLOCK);
402 vfs_path_free (vpath1, TRUE);
404 if (fd1 == -1)
406 g_snprintf (tmp, sizeof (tmp), _("Cannot open \"%s\" in parse mode\n%s"),
407 file, unix_error_string (errno));
408 mcview_close_datasource (view);
409 mcview_show_error (view, tmp);
411 else
413 mc_close (fd);
414 fd = fd1;
415 mc_fstat (fd, &st);
420 mcview_set_datasource_file (view, fd, &st);
422 retval = TRUE;
425 finish:
426 view->command = g_strdup (command);
427 view->dpy_start = 0;
428 view->dpy_paragraph_skip_lines = 0;
429 mcview_state_machine_init (&view->dpy_state_top, 0);
430 view->dpy_wrap_dirty = FALSE;
431 view->force_max = -1;
432 view->dpy_text_column = 0;
434 mcview_compute_areas (view);
435 mcview_update_bytes_per_line (view);
437 if (mcview_remember_file_position && view->filename_vpath != NULL && start_line == 0)
439 long line, col;
440 off_t new_offset, max_offset;
442 load_file_position (view->filename_vpath, &line, &col, &new_offset, &view->saved_bookmarks);
443 max_offset = mcview_get_filesize (view) - 1;
444 if (max_offset < 0)
445 new_offset = 0;
446 else
447 new_offset = MIN (new_offset, max_offset);
448 if (!view->mode_flags.hex)
450 view->dpy_start = mcview_bol (view, new_offset, 0);
451 view->dpy_wrap_dirty = TRUE;
453 else
455 view->dpy_start = new_offset - new_offset % view->bytes_per_line;
456 view->hex_cursor = new_offset;
459 else if (start_line > 0)
460 mcview_moveto (view, start_line - 1, 0);
462 view->search_start = search_start;
463 view->search_end = search_end;
464 view->hexedit_lownibble = FALSE;
465 view->hexview_in_text = FALSE;
466 view->change_list = NULL;
467 vfs_path_free (vpath, TRUE);
468 return retval;
471 /* --------------------------------------------------------------------------------------------- */