2 Internal file viewer for the Midnight Commander
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/>.
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 */
51 /*** global variables ****************************************************************************/
53 mcview_mode_flags_t mcview_global_flags
= {
60 mcview_mode_flags_t mcview_altered_flags
= {
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 /* --------------------------------------------------------------------------------------------- */
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
;
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 */
111 if (!widget_get_state (w
, WST_FOCUSED
))
114 (void) change_panel ();
119 case MSG_MOUSE_CLICK
:
120 if (!view
->mode_flags
.wrap
)
122 /* Scrolling left and right */
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 */
139 mcview_move_right (view
, 1);
140 event
->result
.repeat
= msg
== MSG_MOUSE_DOWN
;
145 /* Scrolling up and down */
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);
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 */
166 if (mcview_mouse_move_pages
)
167 mcview_move_down (view
, r
->lines
/ 2);
169 mcview_move_down (view
, 1);
171 event
->result
.repeat
= msg
== MSG_MOUSE_DOWN
;
176 case MSG_MOUSE_SCROLL_UP
:
177 mcview_move_up (view
, 2);
180 case MSG_MOUSE_SCROLL_DOWN
:
181 mcview_move_down (view
, 2);
190 mcview_update (view
);
193 /* --------------------------------------------------------------------------------------------- */
194 /*** public functions ****************************************************************************/
195 /* --------------------------------------------------------------------------------------------- */
198 mcview_new (const WRect
*r
, gboolean is_panel
)
203 view
= g_new0 (WView
, 1);
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
;
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
);
233 /* --------------------------------------------------------------------------------------------- */
234 /** Real view only */
237 mcview_viewer (const char *command
, const vfs_path_t
*file_vpath
, int start_line
,
238 off_t search_start
, off_t search_end
)
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
);
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
;
266 mcview_load (lc_mcview
, command
, vfs_path_as_str (file_vpath
), start_line
, search_start
,
272 dlg_close (view_dlg
);
274 if (widget_get_state (vw
, WST_CLOSED
))
280 /* {{{ Miscellaneous functions }}} */
282 /* --------------------------------------------------------------------------------------------- */
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
))
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
);
310 /* try extract path from filename */
314 fname
= x_basename (file
);
315 dir
= g_strndup (file
, (size_t) (fname
- file
));
316 view
->workdir_vpath
= vfs_path_from_str (dir
);
321 if (!mcview_is_in_panel (view
))
322 view
->dpy_text_column
= 0;
325 mcview_set_codeset (view
);
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')
333 char tmp
[BUF_MEDIUM
];
337 vpath
= vfs_path_from_str (file
);
338 fd
= mc_open (vpath
, O_RDONLY
| O_NONBLOCK
);
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
;
352 /* Make sure we are working with a regular file */
353 if (mc_fstat (fd
, &st
) == -1)
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
;
367 if (!S_ISREG (st
.st_mode
))
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
;
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
);
386 if (view
->mode_flags
.magic
)
390 type
= get_compression_type (fd
, file
);
392 if (type
!= COMPRESSION_NONE
)
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
);
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
);
420 mcview_set_datasource_file (view
, fd
, &st
);
426 view
->command
= g_strdup (command
);
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)
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;
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
;
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
);
471 /* --------------------------------------------------------------------------------------------- */