fix: hex mode show
[midnight-commander.git] / src / view.c
bloba226b2f140c9da280b71969b0e0f53c468b9622e
1 /*
2 Internal file viewer for the Midnight Commander
4 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
5 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc.
7 Written by: 1994, 1995, 1998 Miguel de Icaza
8 1994, 1995 Janne Kukonlehto
9 1995 Jakub Jelinek
10 1996 Joseph M. Hinkle
11 1997 Norbert Warmuth
12 1998 Pavel Machek
13 2004 Roland Illig <roland.illig@gmx.de>
14 2005 Roland Illig <roland.illig@gmx.de>
15 2009 Slava Zanko <slavazanko@google.com>
16 2009 Ilia Maslakov <il.smind@gmail.com>
18 This program is free software; you can redistribute it and/or modify
19 it under the terms of the GNU General Public License as published by
20 the Free Software Foundation; either version 2 of the License, or
21 (at your option) any later version.
23 This program is distributed in the hope that it will be useful,
24 but WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 GNU General Public License for more details.
28 You should have received a copy of the GNU General Public License
29 along with this program; if not, write to the Free Software
30 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
33 /** \file view.c
34 * \brief Source: internal file viewer
37 #ifdef HAVE_CONFIG_H
38 # include <config.h>
39 #endif
41 #include <assert.h>
42 #include <ctype.h>
43 #include <errno.h>
44 #include <limits.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <fcntl.h>
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 #include <unistd.h>
53 #include "global.h"
55 #include "../src/tty/tty.h"
56 #include "../src/tty/color.h"
57 #include "../src/tty/key.h"
58 #include "../src/tty/mouse.h"
60 #include "cmd.h" /* For view_other_cmd */
61 #include "dialog.h" /* Needed by widget.h */
62 #include "widget.h" /* Needed for buttonbar_new */
63 #include "help.h"
64 #include "layout.h"
65 #include "setup.h"
66 #include "wtools.h" /* For query_set_sel() */
67 #include "dir.h"
68 #include "panel.h" /* Needed for current_panel and other_panel */
69 #include "execute.h"
70 #include "main.h" /* source_codepage */
71 #include "view.h"
72 #include "history.h" /* MC_HISTORY_SHARED_SEARCH */
73 #include "charsets.h"
74 #include "selcodepage.h"
75 #include "strutil.h"
76 #include "../src/search/search.h"
78 /* Block size for reading files in parts */
79 #define VIEW_PAGE_SIZE ((size_t) 8192)
80 #define VIEW_COORD_CACHE_GRANUL 1024
82 typedef unsigned char byte;
84 /* Offset in bytes into a file */
85 typedef unsigned long offset_type;
86 #define INVALID_OFFSET ((offset_type) -1)
87 #define OFFSETTYPE_MAX (~((offset_type) 0))
88 #define OFFSETTYPE_PRIX "lX"
89 #define OFFSETTYPE_PRId "lu"
91 /* A width or height on the screen */
92 typedef unsigned int screen_dimen;
94 /* A cache entry for mapping offsets into line/column pairs and vice versa.
95 * cc_offset, cc_line, and cc_column are the 0-based values of the offset,
96 * line and column of that cache entry. cc_nroff_column is the column
97 * corresponding to cc_offset in nroff mode.
99 struct coord_cache_entry {
100 offset_type cc_offset;
101 offset_type cc_line;
102 offset_type cc_column;
103 offset_type cc_nroff_column;
106 /* A node for building a change list on change_list */
107 struct hexedit_change_node {
108 struct hexedit_change_node *next;
109 offset_type offset;
110 byte value;
113 /* data sources of the view */
114 enum view_ds {
115 DS_NONE, /* No data available */
116 DS_STDIO_PIPE, /* Data comes from a pipe using popen/pclose */
117 DS_VFS_PIPE, /* Data comes from a piped-in VFS file */
118 DS_FILE, /* Data comes from a VFS file */
119 DS_STRING /* Data comes from a string in memory */
122 struct area {
123 screen_dimen top, left;
124 screen_dimen height, width;
127 struct WView {
128 Widget widget;
130 char *filename; /* Name of the file */
131 char *command; /* Command used to pipe data in */
133 enum view_ds datasource; /* Where the displayed data comes from */
135 /* stdio pipe data source */
136 FILE *ds_stdio_pipe; /* Output of a shell command */
138 /* vfs pipe data source */
139 int ds_vfs_pipe; /* Non-seekable vfs file descriptor */
141 /* vfs file data source */
142 int ds_file_fd; /* File with random access */
143 off_t ds_file_filesize; /* Size of the file */
144 off_t ds_file_offset; /* Offset of the currently loaded data */
145 byte *ds_file_data; /* Currently loaded data */
146 size_t ds_file_datalen; /* Number of valid bytes in file_data */
147 size_t ds_file_datasize; /* Number of allocated bytes in file_data */
149 /* string data source */
150 byte *ds_string_data; /* The characters of the string */
151 size_t ds_string_len; /* The length of the string */
153 /* Growing buffers information */
154 gboolean growbuf_in_use; /* Use the growing buffers? */
155 byte **growbuf_blockptr; /* Pointer to the block pointers */
156 size_t growbuf_blocks; /* The number of blocks in *block_ptr */
157 size_t growbuf_lastindex; /* Number of bytes in the last page of the
158 growing buffer */
159 gboolean growbuf_finished; /* TRUE when all data has been read. */
162 /* Editor modes */
163 gboolean hex_mode; /* Hexview or Hexedit */
164 gboolean hexedit_mode; /* Hexedit */
165 gboolean hexview_in_text; /* Is the hexview cursor in the text area? */
166 gboolean text_nroff_mode; /* Nroff-style highlighting */
167 gboolean text_wrap_mode; /* Wrap text lines to fit them on the screen */
168 gboolean magic_mode; /* Preprocess the file using external programs */
169 gboolean utf8; /* It's multibyte file codeset */
171 /* Additional editor state */
172 gboolean hexedit_lownibble; /* Are we editing the last significant nibble? */
173 GArray *coord_cache; /* Cache for mapping offsets to cursor positions */
175 /* Display information */
176 screen_dimen dpy_frame_size;/* Size of the frame surrounding the real viewer */
177 offset_type dpy_start; /* Offset of the displayed data */
178 offset_type dpy_end; /* Offset after the displayed data */
179 offset_type dpy_text_column;/* Number of skipped columns in non-wrap
180 * text mode */
181 offset_type hex_cursor; /* Hexview cursor position in file */
182 screen_dimen cursor_col; /* Cursor column */
183 screen_dimen cursor_row; /* Cursor row */
184 struct hexedit_change_node *change_list; /* Linked list of changes */
185 struct area status_area; /* Where the status line is displayed */
186 struct area ruler_area; /* Where the ruler is displayed */
187 struct area data_area; /* Where the data is displayed */
189 int dirty; /* Number of skipped updates */
190 gboolean dpy_bbar_dirty; /* Does the button bar need to be updated? */
192 /* Mode variables */
193 int bytes_per_line; /* Number of bytes per line in hex mode */
195 /* Search variables */
196 offset_type search_start; /* First character to start searching from */
197 offset_type search_end; /* Length of found string or 0 if none was found */
199 /* Pointer to the last search command */
200 gboolean want_to_quit; /* Prepare for cleanup ... */
202 /* Markers */
203 int marker; /* mark to use */
204 offset_type marks [10]; /* 10 marks: 0..9 */
206 int move_dir; /* return value from widget:
207 * 0 do nothing
208 * -1 view previous file
209 * 1 view next file
212 offset_type update_steps; /* The number of bytes between percent
213 * increments */
214 offset_type update_activate;/* Last point where we updated the status */
216 /* converter for translation of text */
217 GIConv converter;
219 /* handle of search engine */
220 mc_search_t *search;
221 gchar *last_search_string;
222 mc_search_type_t search_type;
223 gboolean search_all_codepages;
224 gboolean search_case;
225 gboolean search_backwards;
227 int search_numNeedSkipChar;
231 /* {{{ Global Variables }}} */
233 /* Maxlimit for skipping updates */
234 int max_dirt_limit = 10;
236 /* If set, show a ruler */
237 static enum ruler_type {
238 RULER_NONE,
239 RULER_TOP,
240 RULER_BOTTOM
241 } ruler = RULER_NONE;
243 /* Scrolling is done in pages or line increments */
244 int mouse_move_pages_viewer = 1;
246 /* wrap mode default */
247 int global_wrap_mode = 1;
249 int default_hex_mode = 0;
250 int default_magic_flag = 1;
251 int default_nroff_flag = 1;
252 int altered_hex_mode = 0;
253 int altered_magic_flag = 0;
254 int altered_nroff_flag = 0;
256 static const char hex_char[] = "0123456789ABCDEF";
258 int mcview_remember_file_position = FALSE;
260 /* {{{ Function Prototypes }}} */
262 /* Our widget callback */
263 static cb_ret_t view_callback (Widget *, widget_msg_t, int);
265 static void view_labels (WView * view);
267 static void view_init_growbuf (WView *);
268 static void view_place_cursor (WView *view);
269 static void display (WView *);
270 static void view_done (WView *);
272 /* {{{ Helper Functions }}} */
274 /* difference or zero */
275 static inline screen_dimen
276 dimen_doz (screen_dimen a, screen_dimen b)
278 return (a >= b) ? a - b : 0;
281 static inline screen_dimen
282 dimen_min (screen_dimen a, screen_dimen b)
284 return (a < b) ? a : b;
287 static inline offset_type
288 offset_doz (offset_type a, offset_type b)
290 return (a >= b) ? a - b : 0;
293 static inline offset_type
294 offset_rounddown (offset_type a, offset_type b)
296 assert (b != 0);
297 return a - a % b;
300 /* {{{ Simple Primitive Functions for WView }}} */
302 static inline gboolean
303 view_is_in_panel (WView *view)
305 return (view->dpy_frame_size != 0);
308 static void
309 view_compute_areas (WView *view)
311 struct area view_area;
312 screen_dimen height, rest, y;
314 /* The viewer is surrounded by a frame of size view->dpy_frame_size.
315 * Inside that frame, there are: The status line (at the top),
316 * the data area and an optional ruler, which is shown above or
317 * below the data area. */
319 view_area.top = view->dpy_frame_size;
320 view_area.left = view->dpy_frame_size;
321 view_area.height = dimen_doz(view->widget.lines, 2 * view->dpy_frame_size);
322 view_area.width = dimen_doz(view->widget.cols, 2 * view->dpy_frame_size);
324 /* Most coordinates of the areas equal those of the whole viewer */
325 view->status_area = view_area;
326 view->ruler_area = view_area;
327 view->data_area = view_area;
329 /* Compute the heights of the areas */
330 rest = view_area.height;
332 height = dimen_min(rest, 1);
333 view->status_area.height = height;
334 rest -= height;
336 height = dimen_min(rest, (ruler == RULER_NONE || view->hex_mode) ? 0 : 2);
337 view->ruler_area.height = height;
338 rest -= height;
340 view->data_area.height = rest;
342 /* Compute the position of the areas */
343 y = view_area.top;
345 view->status_area.top = y;
346 y += view->status_area.height;
348 if (ruler == RULER_TOP) {
349 view->ruler_area.top = y;
350 y += view->ruler_area.height;
353 view->data_area.top = y;
354 y += view->data_area.height;
356 if (ruler == RULER_BOTTOM) {
357 view->ruler_area.top = y;
358 y += view->ruler_area.height;
362 static void
363 view_hexedit_free_change_list (WView *view)
365 struct hexedit_change_node *curr, *next;
367 for (curr = view->change_list; curr != NULL; curr = next) {
368 next = curr->next;
369 g_free (curr);
371 view->change_list = NULL;
372 view->dirty++;
375 /* {{{ Growing buffer }}} */
377 static void
378 view_init_growbuf (WView *view)
380 view->growbuf_in_use = TRUE;
381 view->growbuf_blockptr = NULL;
382 view->growbuf_blocks = 0;
383 view->growbuf_lastindex = VIEW_PAGE_SIZE;
384 view->growbuf_finished = FALSE;
387 static void
388 view_growbuf_free (WView *view)
390 size_t i;
392 assert (view->growbuf_in_use);
394 for (i = 0; i < view->growbuf_blocks; i++)
395 g_free (view->growbuf_blockptr[i]);
396 g_free (view->growbuf_blockptr);
397 view->growbuf_blockptr = NULL;
398 view->growbuf_in_use = FALSE;
401 static offset_type
402 view_growbuf_filesize (WView *view)
404 assert(view->growbuf_in_use);
406 if (view->growbuf_blocks == 0)
407 return 0;
408 else
409 return ((offset_type) view->growbuf_blocks - 1) * VIEW_PAGE_SIZE
410 + view->growbuf_lastindex;
413 /* Copies the output from the pipe to the growing buffer, until either
414 * the end-of-pipe is reached or the interval [0..ofs) of the growing
415 * buffer is completely filled. */
416 static void
417 view_growbuf_read_until (WView *view, offset_type ofs)
419 ssize_t nread;
420 byte *p;
421 size_t bytesfree;
422 gboolean short_read;
424 assert (view->growbuf_in_use);
426 if (view->growbuf_finished)
427 return;
429 short_read = FALSE;
430 while (view_growbuf_filesize (view) < ofs || short_read) {
431 if (view->growbuf_lastindex == VIEW_PAGE_SIZE) {
432 /* Append a new block to the growing buffer */
433 byte *newblock = g_try_malloc (VIEW_PAGE_SIZE);
434 byte **newblocks = g_try_malloc (sizeof (*newblocks) * (view->growbuf_blocks + 1));
435 if (!newblock || !newblocks) {
436 g_free (newblock);
437 g_free (newblocks);
438 return;
440 memcpy (newblocks, view->growbuf_blockptr, sizeof (*newblocks) * view->growbuf_blocks);
441 g_free (view->growbuf_blockptr);
442 view->growbuf_blockptr = newblocks;
443 view->growbuf_blockptr[view->growbuf_blocks++] = newblock;
444 view->growbuf_lastindex = 0;
446 p = view->growbuf_blockptr[view->growbuf_blocks - 1] + view->growbuf_lastindex;
447 bytesfree = VIEW_PAGE_SIZE - view->growbuf_lastindex;
449 if (view->datasource == DS_STDIO_PIPE) {
450 nread = fread (p, 1, bytesfree, view->ds_stdio_pipe);
451 if (nread == 0) {
452 view->growbuf_finished = TRUE;
453 (void) pclose (view->ds_stdio_pipe);
454 display (view);
455 close_error_pipe (D_NORMAL, NULL);
456 view->ds_stdio_pipe = NULL;
457 return;
459 } else {
460 assert (view->datasource == DS_VFS_PIPE);
461 do {
462 nread = mc_read (view->ds_vfs_pipe, p, bytesfree);
463 } while (nread == -1 && errno == EINTR);
464 if (nread == -1 || nread == 0) {
465 view->growbuf_finished = TRUE;
466 (void) mc_close (view->ds_vfs_pipe);
467 view->ds_vfs_pipe = -1;
468 return;
471 short_read = ((size_t)nread < bytesfree);
472 view->growbuf_lastindex += nread;
476 static int
477 get_byte_growing_buffer (WView *view, offset_type byte_index)
479 offset_type pageno = byte_index / VIEW_PAGE_SIZE;
480 offset_type pageindex = byte_index % VIEW_PAGE_SIZE;
482 assert (view->growbuf_in_use);
484 if ((size_t) pageno != pageno)
485 return -1;
487 view_growbuf_read_until (view, byte_index + 1);
488 if (view->growbuf_blocks == 0)
489 return -1;
490 if (pageno < view->growbuf_blocks - 1)
491 return view->growbuf_blockptr[pageno][pageindex];
492 if (pageno == view->growbuf_blocks - 1 && pageindex < view->growbuf_lastindex)
493 return view->growbuf_blockptr[pageno][pageindex];
494 return -1;
497 /* {{{ Data sources }}} */
500 The data source provides the viewer with data from either a file, a
501 string or the output of a command. The get_byte() function can be
502 used to get the value of a byte at a specific offset. If the offset
503 is out of range, -1 is returned. The function get_byte_indexed(a,b)
504 returns the byte at the offset a+b, or -1 if a+b is out of range.
506 The view_set_byte() function has the effect that later calls to
507 get_byte() will return the specified byte for this offset. This
508 function is designed only for use by the hexedit component after
509 saving its changes. Inspect the source before you want to use it for
510 other purposes.
512 The view_get_filesize() function returns the current size of the
513 data source. If the growing buffer is used, this size may increase
514 later on. Use the view_may_still_grow() function when you want to
515 know if the size can change later.
518 static offset_type
519 view_get_filesize (WView *view)
521 switch (view->datasource) {
522 case DS_NONE:
523 return 0;
524 case DS_STDIO_PIPE:
525 case DS_VFS_PIPE:
526 return view_growbuf_filesize (view);
527 case DS_FILE:
528 return view->ds_file_filesize;
529 case DS_STRING:
530 return view->ds_string_len;
531 default:
532 assert(!"Unknown datasource type");
533 return 0;
537 static inline gboolean
538 view_may_still_grow (WView *view)
540 return (view->growbuf_in_use && !view->growbuf_finished);
543 /* returns TRUE if the idx lies in the half-open interval
544 * [offset; offset + size), FALSE otherwise.
546 static inline gboolean
547 already_loaded (offset_type offset, offset_type idx, size_t size)
549 return (offset <= idx && idx - offset < size);
552 static inline void
553 view_file_load_data (WView *view, offset_type byte_index)
555 offset_type blockoffset;
556 ssize_t res;
557 size_t bytes_read;
559 assert (view->datasource == DS_FILE);
561 if (already_loaded (view->ds_file_offset, byte_index, view->ds_file_datalen))
562 return;
564 if (byte_index >= view->ds_file_filesize)
565 return;
567 blockoffset = offset_rounddown (byte_index, view->ds_file_datasize);
568 if (mc_lseek (view->ds_file_fd, blockoffset, SEEK_SET) == -1)
569 goto error;
571 bytes_read = 0;
572 while (bytes_read < view->ds_file_datasize) {
573 res = mc_read (view->ds_file_fd, view->ds_file_data + bytes_read, view->ds_file_datasize - bytes_read);
574 if (res == -1)
575 goto error;
576 if (res == 0)
577 break;
578 bytes_read += (size_t) res;
580 view->ds_file_offset = blockoffset;
581 if (bytes_read > view->ds_file_filesize - view->ds_file_offset) {
582 /* the file has grown in the meantime -- stick to the old size */
583 view->ds_file_datalen = view->ds_file_filesize - view->ds_file_offset;
584 } else {
585 view->ds_file_datalen = bytes_read;
587 return;
589 error:
590 view->ds_file_datalen = 0;
593 static char*
594 get_ptr_file (WView *view, offset_type byte_index)
596 assert (view->datasource == DS_FILE);
598 view_file_load_data (view, byte_index);
599 if (already_loaded(view->ds_file_offset, byte_index, view->ds_file_datalen))
600 return (char *) (view->ds_file_data + (byte_index - view->ds_file_offset));
601 return NULL;
604 static char*
605 get_ptr_string (WView *view, offset_type byte_index)
607 assert (view->datasource == DS_STRING);
608 if (byte_index < view->ds_string_len)
609 return (char *) (view->ds_string_data + byte_index);
610 return NULL;
613 static char*
614 get_ptr_growing_buffer (WView *view, offset_type byte_index)
616 offset_type pageno = byte_index / VIEW_PAGE_SIZE;
617 offset_type pageindex = byte_index % VIEW_PAGE_SIZE;
619 assert (view->growbuf_in_use);
621 if ((size_t) pageno != pageno)
622 return NULL;
624 view_growbuf_read_until (view, byte_index + 1);
625 if (view->growbuf_blocks == 0)
626 return NULL;
627 if (pageno < view->growbuf_blocks - 1)
628 return (char *) (view->growbuf_blockptr[pageno] + pageindex);
629 if (pageno == view->growbuf_blocks - 1 && pageindex < view->growbuf_lastindex)
630 return (char *) (view->growbuf_blockptr[pageno] + pageindex);
631 return NULL;
635 get_utf (WView *view, offset_type byte_index, int *char_width)
637 gchar *str = NULL;
638 int res = -1;
639 gunichar ch;
640 gchar *next_ch = NULL;
641 int width = 0;
643 switch (view->datasource) {
644 case DS_STDIO_PIPE:
645 case DS_VFS_PIPE:
646 str = get_ptr_growing_buffer (view, byte_index);
647 break;
648 case DS_FILE:
649 str = get_ptr_file (view, byte_index);
650 break;
651 case DS_STRING:
652 str = get_ptr_string (view, byte_index);
653 break;
654 case DS_NONE:
655 break;
658 if (str == NULL) {
659 width = 0;
660 return -1;
663 res = g_utf8_get_char_validated (str, -1);
665 if ( res < 0 ) {
666 ch = *str;
667 width = 0;
668 } else {
669 ch = res;
670 /* Calculate UTF-8 char width */
671 next_ch = g_utf8_next_char(str);
672 if ( next_ch ) {
673 if ( next_ch != str ) {
674 width = next_ch - str;
675 } else {
676 width = 0;
678 } else {
679 ch = 0;
680 width = 0;
683 *char_width = width;
684 return ch;
687 static int
688 get_byte_string (WView *view, offset_type byte_index)
690 assert (view->datasource == DS_STRING);
691 if (byte_index < view->ds_string_len)
692 return view->ds_string_data[byte_index];
693 return -1;
696 static int
697 get_byte_none (WView *view, offset_type byte_index)
699 assert (view->datasource == DS_NONE);
700 (void) &view;
701 (void) byte_index;
702 return -1;
705 static inline int
706 get_byte_file (WView *view, offset_type byte_index)
708 assert (view->datasource == DS_FILE);
710 view_file_load_data (view, byte_index);
711 if (already_loaded(view->ds_file_offset, byte_index, view->ds_file_datalen))
712 return view->ds_file_data[byte_index - view->ds_file_offset];
713 return -1;
716 static inline int
717 get_byte (WView *view, offset_type offset)
719 switch (view->datasource) {
720 case DS_STDIO_PIPE:
721 case DS_VFS_PIPE:
722 return get_byte_growing_buffer (view, offset);
723 case DS_FILE:
724 return get_byte_file (view, offset);
725 case DS_STRING:
726 return get_byte_string (view, offset);
727 case DS_NONE:
728 return get_byte_none (view, offset);
730 assert(!"Unknown datasource type");
731 return -1;
734 static inline int
735 get_byte_indexed (WView *view, offset_type base, offset_type ofs)
737 if (base <= OFFSETTYPE_MAX - ofs)
738 return get_byte (view, base + ofs);
739 return -1;
742 static void
743 view_set_byte (WView *view, offset_type offset, byte b)
745 (void) &b;
746 assert (offset < view_get_filesize (view));
747 assert (view->datasource == DS_FILE);
748 view->ds_file_datalen = 0; /* just force reloading */
751 static void
752 view_set_datasource_none (WView *view)
754 view->datasource = DS_NONE;
757 static void
758 view_set_datasource_vfs_pipe (WView *view, int fd)
760 assert (fd != -1);
761 view->datasource = DS_VFS_PIPE;
762 view->ds_vfs_pipe = fd;
764 view_init_growbuf (view);
767 static void
768 view_set_datasource_stdio_pipe (WView *view, FILE *fp)
770 assert (fp != NULL);
771 view->datasource = DS_STDIO_PIPE;
772 view->ds_stdio_pipe = fp;
774 view_init_growbuf (view);
777 static void
778 view_set_datasource_string (WView *view, const char *s)
780 view->datasource = DS_STRING;
781 view->ds_string_data = (byte *) g_strdup (s);
782 view->ds_string_len = strlen (s);
785 static void
786 view_set_datasource_file (WView *view, int fd, const struct stat *st)
788 view->datasource = DS_FILE;
789 view->ds_file_fd = fd;
790 view->ds_file_filesize = st->st_size;
791 view->ds_file_offset = 0;
792 view->ds_file_data = g_malloc (4096);
793 view->ds_file_datalen = 0;
794 view->ds_file_datasize = 4096;
797 static void
798 view_close_datasource (WView *view)
800 switch (view->datasource) {
801 case DS_NONE:
802 break;
803 case DS_STDIO_PIPE:
804 if (view->ds_stdio_pipe != NULL) {
805 (void) pclose (view->ds_stdio_pipe);
806 display (view);
807 close_error_pipe (D_NORMAL, NULL);
808 view->ds_stdio_pipe = NULL;
810 view_growbuf_free (view);
811 break;
812 case DS_VFS_PIPE:
813 if (view->ds_vfs_pipe != -1) {
814 (void) mc_close (view->ds_vfs_pipe);
815 view->ds_vfs_pipe = -1;
817 view_growbuf_free (view);
818 break;
819 case DS_FILE:
820 (void) mc_close (view->ds_file_fd);
821 view->ds_file_fd = -1;
822 g_free (view->ds_file_data);
823 view->ds_file_data = NULL;
824 break;
825 case DS_STRING:
826 g_free (view->ds_string_data);
827 view->ds_string_data = NULL;
828 break;
829 default:
830 assert (!"Unknown datasource type");
832 view->datasource = DS_NONE;
835 /* {{{ The Coordinate Cache }}} */
838 This cache provides you with a fast lookup to map file offsets into
839 line/column pairs and vice versa. The interface to the mapping is
840 provided by the functions view_coord_to_offset() and
841 view_offset_to_coord().
843 The cache is implemented as a simple sorted array holding entries
844 that map some of the offsets to their line/column pair. Entries that
845 are not cached themselves are interpolated (exactly) from their
846 neighbor entries. The algorithm used for determining the line/column
847 for a specific offset needs to be kept synchronized with the one used
848 in display().
851 enum ccache_type {
852 CCACHE_OFFSET,
853 CCACHE_LINECOL
856 static inline gboolean
857 coord_cache_entry_less (const struct coord_cache_entry *a,
858 const struct coord_cache_entry *b, enum ccache_type crit,
859 gboolean nroff_mode)
861 if (crit == CCACHE_OFFSET)
862 return (a->cc_offset < b->cc_offset);
864 if (a->cc_line < b->cc_line)
865 return TRUE;
867 if (a->cc_line == b->cc_line) {
868 if (nroff_mode) {
869 return (a->cc_nroff_column < b->cc_nroff_column);
870 } else {
871 return (a->cc_column < b->cc_column);
874 return FALSE;
877 #ifdef MC_ENABLE_DEBUGGING_CODE
878 static void view_coord_to_offset (WView *, offset_type *, offset_type, offset_type);
879 static void view_offset_to_coord (WView *, offset_type *, offset_type *, offset_type);
881 static void
882 view_ccache_dump (WView *view)
884 FILE *f;
885 offset_type offset, line, column, nextline_offset, filesize;
886 guint i;
887 const struct coord_cache_entry *cache;
889 assert (view->coord_cache != NULL);
891 filesize = view_get_filesize (view);
892 cache = &(g_array_index (view->coord_cache, struct coord_cache_entry, 0));
894 f = fopen("mcview-ccache.out", "w");
895 if (f == NULL)
896 return;
897 (void)setvbuf(f, NULL, _IONBF, 0);
899 /* cache entries */
900 for (i = 0; i < view->coord_cache->len; i++) {
901 (void) fprintf (f,
902 "entry %8u "
903 "offset %8"OFFSETTYPE_PRId" "
904 "line %8"OFFSETTYPE_PRId" "
905 "column %8"OFFSETTYPE_PRId" "
906 "nroff_column %8"OFFSETTYPE_PRId"\n",
907 (unsigned int) i, cache[i].cc_offset, cache[i].cc_line,
908 cache[i].cc_column, cache[i].cc_nroff_column);
910 (void)fprintf (f, "\n");
912 /* offset -> line/column translation */
913 for (offset = 0; offset < filesize; offset++) {
914 view_offset_to_coord (view, &line, &column, offset);
915 (void)fprintf (f,
916 "offset %8"OFFSETTYPE_PRId" "
917 "line %8"OFFSETTYPE_PRId" "
918 "column %8"OFFSETTYPE_PRId"\n",
919 offset, line, column);
922 /* line/column -> offset translation */
923 for (line = 0; TRUE; line++) {
924 view_coord_to_offset (view, &nextline_offset, line + 1, 0);
925 (void)fprintf (f, "nextline_offset %8"OFFSETTYPE_PRId"\n",
926 nextline_offset);
928 for (column = 0; TRUE; column++) {
929 view_coord_to_offset (view, &offset, line, column);
930 if (offset >= nextline_offset)
931 break;
933 (void)fprintf (f, "line %8"OFFSETTYPE_PRId" column %8"OFFSETTYPE_PRId" offset %8"OFFSETTYPE_PRId"\n",
934 line, column, offset);
937 if (nextline_offset >= filesize - 1)
938 break;
941 (void)fclose (f);
943 #endif
945 static inline gboolean
946 is_nroff_sequence (WView *view, offset_type offset)
948 int c0, c1, c2;
950 /* The following commands are ordered to speed up the calculation. */
952 c1 = get_byte_indexed (view, offset, 1);
953 if (c1 == -1 || c1 != '\b')
954 return FALSE;
956 c0 = get_byte_indexed (view, offset, 0);
957 if (c0 == -1 || !g_ascii_isprint(c0))
958 return FALSE;
960 c2 = get_byte_indexed (view, offset, 2);
961 if (c2 == -1 || !g_ascii_isprint(c2))
962 return FALSE;
964 return (c0 == c2 || c0 == '_' || (c0 == '+' && c2 == 'o'));
967 /* Find and return the index of the last cache entry that is
968 * smaller than ''coord'', according to the criterion ''sort_by''. */
969 static inline guint
970 view_ccache_find (WView *view, const struct coord_cache_entry *cache,
971 const struct coord_cache_entry *coord, enum ccache_type sort_by)
973 guint base, i, limit;
975 limit = view->coord_cache->len;
976 assert (limit != 0);
978 base = 0;
979 while (limit > 1) {
980 i = base + limit / 2;
981 if (coord_cache_entry_less (coord, &cache[i], sort_by, view->text_nroff_mode)) {
982 /* continue the search in the lower half of the cache */
983 } else {
984 /* continue the search in the upper half of the cache */
985 base = i;
987 limit = (limit + 1) / 2;
989 return base;
992 /* Look up the missing components of ''coord'', which are given by
993 * ''lookup_what''. The function returns the smallest value that
994 * matches the existing components of ''coord''.
996 static void
997 view_ccache_lookup (WView *view, struct coord_cache_entry *coord,
998 enum ccache_type lookup_what)
1000 guint i;
1001 struct coord_cache_entry *cache, current, next, entry;
1002 enum ccache_type sorter;
1003 offset_type limit;
1004 enum {
1005 NROFF_START,
1006 NROFF_BACKSPACE,
1007 NROFF_CONTINUATION
1008 } nroff_state;
1010 if (!view->coord_cache) {
1011 view->coord_cache = g_array_new (FALSE, FALSE, sizeof(struct coord_cache_entry));
1012 current.cc_offset = 0;
1013 current.cc_line = 0;
1014 current.cc_column = 0;
1015 current.cc_nroff_column = 0;
1016 g_array_append_val (view->coord_cache, current);
1019 sorter = (lookup_what == CCACHE_OFFSET) ? CCACHE_LINECOL : CCACHE_OFFSET;
1021 retry:
1022 /* find the two neighbor entries in the cache */
1023 cache = &(g_array_index (view->coord_cache, struct coord_cache_entry, 0));
1024 i = view_ccache_find (view, cache, coord, sorter);
1025 /* now i points to the lower neighbor in the cache */
1027 current = cache[i];
1028 if (i + 1 < view->coord_cache->len)
1029 limit = cache[i + 1].cc_offset;
1030 else
1031 limit = current.cc_offset + VIEW_COORD_CACHE_GRANUL;
1033 entry = current;
1034 nroff_state = NROFF_START;
1035 for (; current.cc_offset < limit; current = next) {
1036 int c, nextc;
1038 if ((c = get_byte (view, current.cc_offset)) == -1)
1039 break;
1041 if (!coord_cache_entry_less (&current, coord, sorter, view->text_nroff_mode)) {
1042 if (lookup_what == CCACHE_OFFSET
1043 && view->text_nroff_mode
1044 && nroff_state != NROFF_START) {
1045 /* don't break here */
1046 } else {
1047 break;
1051 /* Provide useful default values for ''next'' */
1052 next.cc_offset = current.cc_offset + 1;
1053 next.cc_line = current.cc_line;
1054 next.cc_column = current.cc_column + 1;
1055 next.cc_nroff_column = current.cc_nroff_column + 1;
1057 /* and override some of them as necessary. */
1058 if (c == '\r') {
1059 nextc = get_byte_indexed(view, current.cc_offset, 1);
1061 /* Ignore '\r' if it is followed by '\r' or '\n'. If it is
1062 * followed by anything else, it is a Mac line ending and
1063 * produces a line break.
1065 if (nextc == '\r' || nextc == '\n') {
1066 next.cc_column = current.cc_column;
1067 next.cc_nroff_column = current.cc_nroff_column;
1068 } else {
1069 next.cc_line = current.cc_line + 1;
1070 next.cc_column = 0;
1071 next.cc_nroff_column = 0;
1074 } else if (nroff_state == NROFF_BACKSPACE) {
1075 next.cc_nroff_column = current.cc_nroff_column - 1;
1077 } else if (c == '\t') {
1078 next.cc_column = offset_rounddown (current.cc_column, 8) + 8;
1079 next.cc_nroff_column =
1080 offset_rounddown (current.cc_nroff_column, 8) + 8;
1082 } else if (c == '\n') {
1083 next.cc_line = current.cc_line + 1;
1084 next.cc_column = 0;
1085 next.cc_nroff_column = 0;
1087 } else {
1088 /* Use all default values from above */
1091 switch (nroff_state) {
1092 case NROFF_START:
1093 case NROFF_CONTINUATION:
1094 if (is_nroff_sequence (view, current.cc_offset))
1095 nroff_state = NROFF_BACKSPACE;
1096 else
1097 nroff_state = NROFF_START;
1098 break;
1099 case NROFF_BACKSPACE:
1100 nroff_state = NROFF_CONTINUATION;
1101 break;
1104 /* Cache entries must guarantee that for each i < j,
1105 * line[i] <= line[j] and column[i] < column[j]. In the case of
1106 * nroff sequences and '\r' characters, this is not guaranteed,
1107 * so we cannot save them. */
1108 if (nroff_state == NROFF_START && c != '\r')
1109 entry = next;
1112 if (i + 1 == view->coord_cache->len && entry.cc_offset != cache[i].cc_offset) {
1113 g_array_append_val (view->coord_cache, entry);
1114 goto retry;
1117 if (lookup_what == CCACHE_OFFSET) {
1118 coord->cc_offset = current.cc_offset;
1119 } else {
1120 coord->cc_line = current.cc_line;
1121 coord->cc_column = current.cc_column;
1122 coord->cc_nroff_column = current.cc_nroff_column;
1126 static void
1127 view_coord_to_offset (WView *view, offset_type *ret_offset,
1128 offset_type line, offset_type column)
1130 struct coord_cache_entry coord;
1132 coord.cc_line = line;
1133 coord.cc_column = column;
1134 coord.cc_nroff_column = column;
1135 view_ccache_lookup (view, &coord, CCACHE_OFFSET);
1136 *ret_offset = coord.cc_offset;
1139 static void
1140 view_offset_to_coord (WView *view, offset_type *ret_line,
1141 offset_type *ret_column, offset_type offset)
1143 struct coord_cache_entry coord;
1145 coord.cc_offset = offset;
1146 view_ccache_lookup (view, &coord, CCACHE_LINECOL);
1147 *ret_line = coord.cc_line;
1148 *ret_column = (view->text_nroff_mode)
1149 ? coord.cc_nroff_column
1150 : coord.cc_column;
1153 /* {{{ Cursor Movement }}} */
1156 The following variables have to do with the current position and are
1157 updated by the cursor movement functions.
1159 In hex view and wrapped text view mode, dpy_start marks the offset of
1160 the top-left corner on the screen, in non-wrapping text mode it is
1161 the beginning of the current line. In hex mode, hex_cursor is the
1162 offset of the cursor. In non-wrapping text mode, dpy_text_column is
1163 the number of columns that are hidden on the left side on the screen.
1165 In hex mode, dpy_start is updated by the view_fix_cursor_position()
1166 function in order to keep the other functions simple. In
1167 non-wrapping text mode dpy_start and dpy_text_column are normalized
1168 such that dpy_text_column < view_get_datacolumns().
1171 /* prototypes for functions used by view_moveto_bottom() */
1172 static void view_move_up (WView *, offset_type);
1173 static void view_moveto_bol (WView *);
1175 static void
1176 view_scroll_to_cursor (WView *view)
1178 if (view->hex_mode) {
1179 const offset_type bytes = view->bytes_per_line;
1180 const offset_type displaysize = view->data_area.height * bytes;
1181 const offset_type cursor = view->hex_cursor;
1182 offset_type topleft = view->dpy_start;
1184 if (topleft + displaysize <= cursor)
1185 topleft = offset_rounddown (cursor, bytes)
1186 - (displaysize - bytes);
1187 if (cursor < topleft)
1188 topleft = offset_rounddown (cursor, bytes);
1189 view->dpy_start = topleft;
1190 } else if (view->text_wrap_mode) {
1191 offset_type line, col, columns;
1193 columns = view->data_area.width;
1194 view_offset_to_coord (view, &line, &col, view->dpy_start + view->dpy_text_column);
1195 if (columns != 0)
1196 col = offset_rounddown (col, columns);
1197 view_coord_to_offset (view, &(view->dpy_start), line, col);
1198 view->dpy_text_column = 0;
1199 } else {
1200 /* nothing to do */
1204 static void
1205 view_movement_fixups (WView *view, gboolean reset_search)
1207 view_scroll_to_cursor (view);
1208 if (reset_search) {
1209 view->search_start = view->dpy_start;
1210 view->search_end = view->dpy_start;
1212 view->dirty++;
1215 static void
1216 view_moveto_top (WView *view)
1218 view->dpy_start = 0;
1219 view->hex_cursor = 0;
1220 view->dpy_text_column = 0;
1221 view_movement_fixups (view, TRUE);
1224 static void
1225 view_moveto_bottom (WView *view)
1227 offset_type datalines, lines_up, filesize, last_offset;
1229 if (view->growbuf_in_use)
1230 view_growbuf_read_until (view, OFFSETTYPE_MAX);
1232 filesize = view_get_filesize (view);
1233 last_offset = offset_doz(filesize, 1);
1234 datalines = view->data_area.height;
1235 lines_up = offset_doz(datalines, 1);
1237 if (view->hex_mode) {
1238 view->hex_cursor = filesize;
1239 view_move_up (view, lines_up);
1240 view->hex_cursor = last_offset;
1241 } else {
1242 view->dpy_start = last_offset;
1243 view_moveto_bol (view);
1244 view_move_up (view, lines_up);
1246 view_movement_fixups (view, TRUE);
1249 static void
1250 view_moveto_bol (WView *view)
1252 if (view->hex_mode) {
1253 view->hex_cursor -= view->hex_cursor % view->bytes_per_line;
1254 } else if (view->text_wrap_mode) {
1255 /* do nothing */
1256 } else {
1257 offset_type line, column;
1258 view_offset_to_coord (view, &line, &column, view->dpy_start);
1259 view_coord_to_offset (view, &(view->dpy_start), line, 0);
1260 view->dpy_text_column = 0;
1262 view_movement_fixups (view, TRUE);
1265 static void
1266 view_moveto_eol (WView *view)
1268 if (view->hex_mode) {
1269 offset_type filesize, bol;
1271 bol = offset_rounddown (view->hex_cursor, view->bytes_per_line);
1272 if (get_byte_indexed (view, bol, view->bytes_per_line - 1) != -1) {
1273 view->hex_cursor = bol + view->bytes_per_line - 1;
1274 } else {
1275 filesize = view_get_filesize (view);
1276 view->hex_cursor = offset_doz(filesize, 1);
1278 } else if (view->text_wrap_mode) {
1279 /* nothing to do */
1280 } else {
1281 offset_type line, col;
1283 view_offset_to_coord (view, &line, &col, view->dpy_start);
1284 view_coord_to_offset (view, &(view->dpy_start), line, OFFSETTYPE_MAX);
1286 view_movement_fixups (view, FALSE);
1289 static void
1290 view_moveto_offset (WView *view, offset_type offset)
1292 if (view->hex_mode) {
1293 view->hex_cursor = offset;
1294 view->dpy_start = offset - offset % view->bytes_per_line;
1295 } else {
1296 view->dpy_start = offset;
1298 view_movement_fixups (view, TRUE);
1301 static void
1302 view_moveto (WView *view, offset_type line, offset_type col)
1304 offset_type offset;
1306 view_coord_to_offset (view, &offset, line, col);
1307 view_moveto_offset (view, offset);
1310 static void
1311 view_move_up (WView *view, offset_type lines)
1313 if (view->hex_mode) {
1314 offset_type bytes = lines * view->bytes_per_line;
1315 if (view->hex_cursor >= bytes) {
1316 view->hex_cursor -= bytes;
1317 if (view->hex_cursor < view->dpy_start)
1318 view->dpy_start = offset_doz (view->dpy_start, bytes);
1319 } else {
1320 view->hex_cursor %= view->bytes_per_line;
1322 } else if (view->text_wrap_mode) {
1323 const screen_dimen width = view->data_area.width;
1324 offset_type i, col, line, linestart;
1326 for (i = 0; i < lines; i++) {
1327 view_offset_to_coord (view, &line, &col, view->dpy_start);
1328 if (col >= width) {
1329 col -= width;
1330 } else if (line >= 1) {
1331 view_coord_to_offset (view, &linestart, line, 0);
1332 view_offset_to_coord (view, &line, &col, linestart - 1);
1334 /* if the only thing that would be displayed were a
1335 * single newline character, advance to the previous
1336 * part of the line. */
1337 if (col > 0 && col % width == 0)
1338 col -= width;
1339 else
1340 col -= col % width;
1341 } else {
1342 /* nothing to do */
1344 view_coord_to_offset (view, &(view->dpy_start), line, col);
1346 } else {
1347 offset_type line, column;
1349 view_offset_to_coord (view, &line, &column, view->dpy_start);
1350 line = offset_doz(line, lines);
1351 view_coord_to_offset (view, &(view->dpy_start), line, column);
1353 view_movement_fixups (view, (lines != 1));
1356 static void
1357 view_move_down (WView *view, offset_type lines)
1359 if (view->hex_mode) {
1360 offset_type i, limit, last_byte;
1362 last_byte = view_get_filesize (view);
1363 if (last_byte >= (offset_type) view->bytes_per_line)
1364 limit = last_byte - view->bytes_per_line;
1365 else
1366 limit = 0;
1367 for (i = 0; i < lines && view->hex_cursor < limit; i++) {
1368 view->hex_cursor += view->bytes_per_line;
1369 if (lines != 1)
1370 view->dpy_start += view->bytes_per_line;
1373 } else if (view->dpy_end == view_get_filesize (view)) {
1374 /* don't move further down. There's nothing more to see. */
1376 } else if (view->text_wrap_mode) {
1377 offset_type line, col, i;
1379 for (i = 0; i < lines; i++) {
1380 offset_type new_offset, chk_line, chk_col;
1382 view_offset_to_coord (view, &line, &col, view->dpy_start);
1383 col += view->data_area.width;
1384 view_coord_to_offset (view, &new_offset, line, col);
1386 /* skip to the next line if the only thing that would be
1387 * displayed is the newline character. */
1388 view_offset_to_coord (view, &chk_line, &chk_col, new_offset);
1389 if (chk_line == line && chk_col == col
1390 && get_byte (view, new_offset) == '\n')
1391 new_offset++;
1393 view->dpy_start = new_offset;
1396 } else {
1397 offset_type line, col;
1399 view_offset_to_coord (view, &line, &col, view->dpy_start);
1400 line += lines;
1401 view_coord_to_offset (view, &(view->dpy_start), line, col);
1403 view_movement_fixups (view, (lines != 1));
1406 static void
1407 view_move_left (WView *view, offset_type columns)
1409 if (view->hex_mode) {
1410 assert (columns == 1);
1411 if (view->hexview_in_text || !view->hexedit_lownibble) {
1412 if (view->hex_cursor > 0)
1413 view->hex_cursor--;
1415 if (!view->hexview_in_text)
1416 view->hexedit_lownibble = !view->hexedit_lownibble;
1417 } else if (view->text_wrap_mode) {
1418 /* nothing to do */
1419 } else {
1420 if (view->dpy_text_column >= columns)
1421 view->dpy_text_column -= columns;
1422 else
1423 view->dpy_text_column = 0;
1425 view_movement_fixups (view, FALSE);
1428 static void
1429 view_move_right (WView *view, offset_type columns)
1431 if (view->hex_mode) {
1432 assert (columns == 1);
1433 if (view->hexview_in_text || view->hexedit_lownibble) {
1434 if (get_byte_indexed (view, view->hex_cursor, 1) != -1)
1435 view->hex_cursor++;
1437 if (!view->hexview_in_text)
1438 view->hexedit_lownibble = !view->hexedit_lownibble;
1439 } else if (view->text_wrap_mode) {
1440 /* nothing to do */
1441 } else {
1442 view->dpy_text_column += columns;
1444 view_movement_fixups (view, FALSE);
1447 /* {{{ Toggling of viewer modes }}} */
1449 static void
1450 view_toggle_hex_mode (WView *view)
1452 view->hex_mode = !view->hex_mode;
1454 if (view->hex_mode) {
1455 view->hex_cursor = view->dpy_start;
1456 view->dpy_start =
1457 offset_rounddown (view->dpy_start, view->bytes_per_line);
1458 view->widget.options |= W_WANT_CURSOR;
1459 } else {
1460 view->dpy_start = view->hex_cursor;
1461 view_moveto_bol (view);
1462 view->widget.options &= ~W_WANT_CURSOR;
1464 altered_hex_mode = 1;
1465 view->dpy_bbar_dirty = TRUE;
1466 view->dirty++;
1469 static void
1470 view_toggle_hexedit_mode (WView *view)
1472 view->hexedit_mode = !view->hexedit_mode;
1473 view->dpy_bbar_dirty = TRUE;
1474 view->dirty++;
1477 static void
1478 view_toggle_wrap_mode (WView *view)
1480 view->text_wrap_mode = !view->text_wrap_mode;
1481 if (view->text_wrap_mode) {
1482 view_scroll_to_cursor (view);
1483 } else {
1484 offset_type line;
1486 view_offset_to_coord (view, &line, &(view->dpy_text_column), view->dpy_start);
1487 view_coord_to_offset (view, &(view->dpy_start), line, 0);
1489 view->dpy_bbar_dirty = TRUE;
1490 view->dirty++;
1493 static void
1494 view_toggle_nroff_mode (WView *view)
1496 view->text_nroff_mode = !view->text_nroff_mode;
1497 altered_nroff_flag = 1;
1498 view->dpy_bbar_dirty = TRUE;
1499 view->dirty++;
1502 static void
1503 view_toggle_magic_mode (WView *view)
1505 char *filename, *command;
1507 altered_magic_flag = 1;
1508 view->magic_mode = !view->magic_mode;
1509 filename = g_strdup (view->filename);
1510 command = g_strdup (view->command);
1512 view_done (view);
1513 view_load (view, command, filename, 0);
1514 g_free (filename);
1515 g_free (command);
1516 view->dpy_bbar_dirty = TRUE;
1517 view->dirty++;
1520 /* {{{ Miscellaneous functions }}} */
1522 static void
1523 view_done (WView *view)
1525 /* Save current file position */
1526 if (mcview_remember_file_position && view->filename != NULL) {
1527 char *canon_fname;
1528 offset_type line, col;
1530 canon_fname = vfs_canon (view->filename);
1531 view_offset_to_coord (view, &line, &col, view->dpy_start);
1532 save_file_position (canon_fname, line + 1, col);
1533 g_free (canon_fname);
1536 /* Write back the global viewer mode */
1537 default_hex_mode = view->hex_mode;
1538 default_nroff_flag = view->text_nroff_mode;
1539 default_magic_flag = view->magic_mode;
1540 global_wrap_mode = view->text_wrap_mode;
1542 /* Free memory used by the viewer */
1544 /* view->widget needs no destructor */
1546 g_free (view->filename), view->filename = NULL;
1547 g_free (view->command), view->command = NULL;
1549 view_close_datasource (view);
1550 /* the growing buffer is freed with the datasource */
1552 if (view->coord_cache) {
1553 g_array_free (view->coord_cache, TRUE), view->coord_cache = NULL;
1556 view_hexedit_free_change_list (view);
1557 /* FIXME: what about view->search_exp? */
1559 if (view->converter != str_cnv_from_term)
1560 str_close_conv (view->converter);
1563 static void
1564 view_show_error (WView *view, const char *msg)
1566 view_close_datasource (view);
1567 if (view_is_in_panel (view)) {
1568 view_set_datasource_string (view, msg);
1569 } else {
1570 message (D_ERROR, MSG_ERROR, "%s", msg);
1574 static gboolean
1575 view_load_command_output (WView *view, const char *command)
1577 FILE *fp;
1579 view_close_datasource (view);
1581 open_error_pipe ();
1582 if ((fp = popen (command, "r")) == NULL) {
1583 /* Avoid two messages. Message from stderr has priority. */
1584 display (view);
1585 if (!close_error_pipe (view_is_in_panel (view) ? -1 : D_ERROR, NULL))
1586 view_show_error (view, _(" Cannot spawn child process "));
1587 return FALSE;
1590 /* First, check if filter produced any output */
1591 view_set_datasource_stdio_pipe (view, fp);
1592 if (get_byte (view, 0) == -1) {
1593 view_close_datasource (view);
1595 /* Avoid two messages. Message from stderr has priority. */
1596 display (view);
1597 if (!close_error_pipe (view_is_in_panel (view) ? -1 : D_ERROR, NULL))
1598 view_show_error (view, _("Empty output from child filter"));
1599 return FALSE;
1600 } else {
1602 * At least something was read correctly. Close stderr and let
1603 * program die if it will try to write something there.
1605 * Ideally stderr should be read asynchronously to prevent programs
1606 * from blocking (poll/select multiplexor).
1608 close_error_pipe (D_NORMAL, NULL);
1610 return TRUE;
1613 gboolean
1614 view_load (WView *view, const char *command, const char *file,
1615 int start_line)
1617 int i, type;
1618 int fd = -1;
1619 char tmp[BUF_MEDIUM];
1620 const char *enc;
1621 char *canon_fname;
1622 struct stat st;
1623 #ifdef HAVE_CHARSET
1624 const char *cp_id;
1625 #endif
1626 gboolean retval = FALSE;
1627 #ifdef HAVE_CHARSET
1628 cp_id = get_codepage_id (source_codepage);
1630 if (cp_id != NULL && str_isutf8 (cp_id) != 0)
1631 view->utf8 = TRUE;
1632 else
1633 view->utf8 = FALSE;
1634 #endif
1636 assert (view->bytes_per_line != 0);
1637 view_done (view);
1639 /* Set up the state */
1640 view_set_datasource_none (view);
1641 view->filename = g_strdup (file);
1642 view->command = 0;
1644 /* Clear the markers */
1645 view->marker = 0;
1646 for (i = 0; i < 10; i++)
1647 view->marks[i] = 0;
1649 if (!view_is_in_panel (view)) {
1650 view->dpy_text_column = 0;
1653 if (command && (view->magic_mode || file == NULL || file[0] == '\0')) {
1654 retval = view_load_command_output (view, command);
1655 } else if (file != NULL && file[0] != '\0') {
1656 /* Open the file */
1657 if ((fd = mc_open (file, O_RDONLY | O_NONBLOCK)) == -1) {
1658 g_snprintf (tmp, sizeof (tmp), _(" Cannot open \"%s\"\n %s "),
1659 file, unix_error_string (errno));
1660 view_show_error (view, tmp);
1661 g_free (view->filename);
1662 view->filename = NULL;
1663 goto finish;
1666 /* Make sure we are working with a regular file */
1667 if (mc_fstat (fd, &st) == -1) {
1668 mc_close (fd);
1669 g_snprintf (tmp, sizeof (tmp), _(" Cannot stat \"%s\"\n %s "),
1670 file, unix_error_string (errno));
1671 view_show_error (view, tmp);
1672 g_free (view->filename);
1673 view->filename = NULL;
1674 goto finish;
1677 if (!S_ISREG (st.st_mode)) {
1678 mc_close (fd);
1679 view_show_error (view, _(" Cannot view: not a regular file "));
1680 g_free (view->filename);
1681 view->filename = NULL;
1682 goto finish;
1685 if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1) {
1686 /* Must be one of those nice files that grow (/proc) */
1687 view_set_datasource_vfs_pipe (view, fd);
1688 } else {
1689 type = get_compression_type (fd);
1691 if (view->magic_mode && (type != COMPRESSION_NONE)) {
1692 g_free (view->filename);
1693 view->filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
1695 view_set_datasource_file (view, fd, &st);
1697 retval = TRUE;
1700 finish:
1701 view->command = g_strdup (command);
1702 view->dpy_start = 0;
1703 view->search_start = 0;
1704 view->search_end = 0;
1705 view->dpy_text_column = 0;
1707 view->converter = str_cnv_from_term;
1708 /* try detect encoding from path */
1709 if (view->filename != NULL) {
1710 canon_fname = vfs_canon (view->filename);
1711 enc = vfs_get_encoding (canon_fname);
1712 if (enc != NULL) {
1713 view->converter = str_crt_conv_from (enc);
1714 if (view->converter == INVALID_CONV)
1715 view->converter = str_cnv_from_term;
1717 g_free (canon_fname);
1720 view_compute_areas (view);
1721 assert (view->bytes_per_line != 0);
1722 if (mcview_remember_file_position && view->filename != NULL && start_line == 0) {
1723 long line, col;
1725 canon_fname = vfs_canon (view->filename);
1726 load_file_position (canon_fname, &line, &col);
1727 g_free (canon_fname);
1728 view_moveto (view, offset_doz(line, 1), col);
1729 } else if (start_line > 0) {
1730 view_moveto (view, start_line - 1, 0);
1733 view->hexedit_lownibble = FALSE;
1734 view->hexview_in_text = FALSE;
1735 view->change_list = NULL;
1737 return retval;
1740 /* {{{ Display management }}} */
1742 static void
1743 view_update_bytes_per_line (WView *view)
1745 const screen_dimen cols = view->data_area.width;
1746 int bytes;
1748 if (cols < 8 + 17)
1749 bytes = 4;
1750 else
1751 bytes = 4 * ((cols - 8) / ((cols < 80) ? 17 : 18));
1752 assert(bytes != 0);
1754 view->bytes_per_line = bytes;
1755 view->dirty = max_dirt_limit + 1; /* To force refresh */
1758 static void
1759 view_percent (WView *view, offset_type p)
1761 const screen_dimen top = view->status_area.top;
1762 const screen_dimen right = view->status_area.left + view->status_area.width;
1763 const screen_dimen height = view->status_area.height;
1764 int percent;
1765 offset_type filesize;
1767 if (height < 1 || right < 4)
1768 return;
1769 if (view_may_still_grow (view))
1770 return;
1771 filesize = view_get_filesize (view);
1773 if (filesize == 0 || view->dpy_end == filesize)
1774 percent = 100;
1775 else if (p > (INT_MAX / 100))
1776 percent = p / (filesize / 100);
1777 else
1778 percent = p * 100 / filesize;
1780 widget_move (view, top, right - 4);
1781 tty_printf ("%3d%%", percent);
1784 static void
1785 view_display_status (WView *view)
1787 const screen_dimen top = view->status_area.top;
1788 const screen_dimen left = view->status_area.left;
1789 const screen_dimen width = view->status_area.width;
1790 const screen_dimen height = view->status_area.height;
1791 const char *file_label, *file_name;
1792 screen_dimen file_label_width;
1793 int i;
1795 if (height < 1)
1796 return;
1798 tty_setcolor (SELECTED_COLOR);
1799 tty_draw_hline (view->widget.y + top, view->widget.x + left, ' ', width);
1801 file_label = _("File: %s");
1802 file_label_width = str_term_width1 (file_label) - 2;
1803 file_name = view->filename ? view->filename
1804 : view->command ? view->command
1805 : "";
1807 if (width < file_label_width + 6)
1808 tty_print_string (str_fit_to_term (file_name, width, J_LEFT_FIT));
1809 else {
1810 i = (width > 22 ? 22 : width) - file_label_width;
1811 tty_printf (file_label, str_fit_to_term (file_name, i, J_LEFT_FIT));
1813 if (width > 46) {
1814 widget_move (view, top, left + 24);
1815 /* FIXME: the format strings need to be changed when offset_type changes */
1816 if (view->hex_mode)
1817 tty_printf (_("Offset 0x%08lx"), (unsigned long) view->hex_cursor);
1818 else {
1819 offset_type line, col;
1820 view_offset_to_coord (view, &line, &col, view->dpy_start);
1821 tty_printf (_("Line %lu Col %lu"),
1822 (unsigned long) line + 1,
1823 (unsigned long) (view->text_wrap_mode ? col : view->dpy_text_column));
1826 if (width > 62) {
1827 offset_type filesize;
1828 filesize = view_get_filesize (view);
1829 widget_move (view, top, left + 43);
1830 if (!view_may_still_grow (view)) {
1831 tty_printf (_("%s bytes"), size_trunc (filesize));
1832 } else {
1833 tty_printf (_(">= %s bytes"), size_trunc (filesize));
1836 if (width > 26) {
1837 view_percent (view, view->hex_mode
1838 ? view->hex_cursor
1839 : view->dpy_end);
1842 tty_setcolor (SELECTED_COLOR);
1845 static inline void
1846 view_display_clean (WView *view)
1848 tty_setcolor (NORMAL_COLOR);
1849 widget_erase ((Widget *) view);
1850 if (view->dpy_frame_size != 0) {
1851 draw_box (view->widget.parent, view->widget.y,
1852 view->widget.x, view->widget.lines,
1853 view->widget.cols);
1857 typedef enum {
1858 MARK_NORMAL,
1859 MARK_SELECTED,
1860 MARK_CURSOR,
1861 MARK_CHANGED
1862 } mark_t;
1864 static inline int
1865 view_count_backspaces (WView *view, off_t offset)
1867 int backspaces = 0;
1868 while (offset >= 2 * backspaces
1869 && get_byte (view, offset - 2 * backspaces) == '\b')
1870 backspaces++;
1871 return backspaces;
1874 static void
1875 view_display_ruler (WView *view)
1877 static const char ruler_chars[] = "|----*----";
1878 const screen_dimen top = view->ruler_area.top;
1879 const screen_dimen left = view->ruler_area.left;
1880 const screen_dimen width = view->ruler_area.width;
1881 const screen_dimen height = view->ruler_area.height;
1882 const screen_dimen line_row = (ruler == RULER_TOP) ? 0 : 1;
1883 const screen_dimen nums_row = (ruler == RULER_TOP) ? 1 : 0;
1885 char r_buff[10];
1886 offset_type cl;
1887 screen_dimen c;
1889 if (ruler == RULER_NONE || height < 1)
1890 return;
1892 tty_setcolor (MARKED_COLOR);
1893 for (c = 0; c < width; c++) {
1894 cl = view->dpy_text_column + c;
1895 if (line_row < height) {
1896 widget_move (view, top + line_row, left + c);
1897 tty_print_char (ruler_chars[cl % 10]);
1900 if ((cl != 0) && (cl % 10) == 0) {
1901 g_snprintf (r_buff, sizeof (r_buff), "%"OFFSETTYPE_PRId, cl);
1902 if (nums_row < height) {
1903 widget_move (view, top + nums_row, left + c - 1);
1904 tty_print_string (r_buff);
1908 tty_setcolor (NORMAL_COLOR);
1911 static void
1912 view_display_hex (WView *view)
1914 const screen_dimen top = view->data_area.top;
1915 const screen_dimen left = view->data_area.left;
1916 const screen_dimen height = view->data_area.height;
1917 const screen_dimen width = view->data_area.width;
1918 const int ngroups = view->bytes_per_line / 4;
1919 const screen_dimen text_start =
1920 8 + 13 * ngroups + ((width < 80) ? 0 : (ngroups - 1 + 1));
1921 /* 8 characters are used for the file offset, and every hex group
1922 * takes 13 characters. On ``big'' screens, the groups are separated
1923 * by an extra vertical line, and there is an extra space before the
1924 * text column.
1927 screen_dimen row, col;
1928 offset_type from;
1929 int c;
1930 mark_t boldflag = MARK_NORMAL;
1931 struct hexedit_change_node *curr = view->change_list;
1932 size_t i;
1933 int cw = 1;
1934 int ch = 0;
1936 char hex_buff[10]; /* A temporary buffer for sprintf */
1937 int bytes; /* Number of bytes already printed on the line */
1939 view_display_clean (view);
1941 /* Find the first displayable changed byte */
1942 from = view->dpy_start;
1943 while (curr && (curr->offset < from)) {
1944 curr = curr->next;
1947 for (row = 0; get_byte (view, from) != -1 && row < height; row++) {
1948 col = 0;
1950 /* Print the hex offset */
1951 g_snprintf (hex_buff, sizeof (hex_buff), "%08"OFFSETTYPE_PRIX" ", from);
1952 widget_move (view, top + row, left);
1953 tty_setcolor (MARKED_COLOR);
1954 for (i = 0; col < width && hex_buff[i] != '\0'; i++) {
1955 tty_print_char (hex_buff[i]);
1956 col++;
1958 tty_setcolor (NORMAL_COLOR);
1960 for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++) {
1962 #ifdef HAVE_CHARSET
1963 if ( view->utf8 ) {
1964 if ((ch = get_utf (view, from, &cw)) == -1)
1965 break;
1967 #endif
1968 if ((c = get_byte (view, from)) == -1)
1969 break;
1971 /* Save the cursor position for view_place_cursor() */
1972 if (from == view->hex_cursor && !view->hexview_in_text) {
1973 view->cursor_row = row;
1974 view->cursor_col = col;
1977 /* Determine the state of the current byte */
1978 boldflag =
1979 (from == view->hex_cursor) ? MARK_CURSOR
1980 : (curr != NULL && from == curr->offset) ? MARK_CHANGED
1981 : (view->search_start <= from &&
1982 from < view->search_end) ? MARK_SELECTED
1983 : MARK_NORMAL;
1985 /* Determine the value of the current byte */
1986 if (curr != NULL && from == curr->offset) {
1987 c = curr->value;
1988 curr = curr->next;
1991 /* Select the color for the hex number */
1992 tty_setcolor (
1993 boldflag == MARK_NORMAL ? NORMAL_COLOR :
1994 boldflag == MARK_SELECTED ? MARKED_COLOR :
1995 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
1996 /* boldflag == MARK_CURSOR */
1997 view->hexview_in_text ? MARKED_SELECTED_COLOR :
1998 VIEW_UNDERLINED_COLOR);
2000 /* Print the hex number */
2001 widget_move (view, top + row, left + col);
2002 if (col < width) {
2003 tty_print_char (hex_char[c / 16]);
2004 col += 1;
2006 if (col < width) {
2007 tty_print_char (hex_char[c % 16]);
2008 col += 1;
2011 /* Print the separator */
2012 tty_setcolor (NORMAL_COLOR);
2013 if (bytes != view->bytes_per_line - 1) {
2014 if (col < width) {
2015 tty_print_char (' ');
2016 col += 1;
2019 /* After every four bytes, print a group separator */
2020 if (bytes % 4 == 3) {
2021 if (view->data_area.width >= 80 && col < width) {
2022 tty_print_one_vline ();
2023 col += 1;
2025 if (col < width) {
2026 tty_print_char (' ');
2027 col += 1;
2032 /* Select the color for the character; this differs from the
2033 * hex color when boldflag == MARK_CURSOR */
2034 tty_setcolor (
2035 boldflag == MARK_NORMAL ? NORMAL_COLOR :
2036 boldflag == MARK_SELECTED ? MARKED_COLOR :
2037 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
2038 /* boldflag == MARK_CURSOR */
2039 view->hexview_in_text ? VIEW_UNDERLINED_COLOR :
2040 MARKED_SELECTED_COLOR);
2042 #ifdef HAVE_CHARSET
2043 if ( utf8_display ) {
2044 if ( !view->utf8 ) {
2045 ch = convert_from_8bit_to_utf_c ((unsigned char) ch, view->converter);
2047 } else {
2048 if ( view->utf8 ) {
2049 ch = convert_from_utf_to_current_c (ch, view->converter);
2050 } else {
2051 #endif
2052 ch = convert_to_display_c (ch);
2053 #ifdef HAVE_CHARSET
2056 #endif
2057 c = convert_to_display_c (c);
2058 if (!g_ascii_isprint (c))
2059 c = '.';
2061 /* Print corresponding character on the text side */
2062 if (text_start + bytes < width) {
2063 widget_move (view, top + row, left + text_start + bytes);
2064 if ( !view->utf8 ) {
2065 tty_print_char (c);
2066 } else {
2067 tty_print_anychar (ch);
2071 /* Save the cursor position for view_place_cursor() */
2072 if (from == view->hex_cursor && view->hexview_in_text) {
2073 view->cursor_row = row;
2074 view->cursor_col = text_start + bytes;
2079 /* Be polite to the other functions */
2080 tty_setcolor (NORMAL_COLOR);
2082 view_place_cursor (view);
2083 view->dpy_end = from;
2086 static void
2087 view_display_text (WView * view)
2089 const screen_dimen left = view->data_area.left;
2090 const screen_dimen top = view->data_area.top;
2091 const screen_dimen width = view->data_area.width;
2092 const screen_dimen height = view->data_area.height;
2093 screen_dimen row, col;
2094 offset_type from;
2095 int cw = 1;
2096 int c;
2097 struct hexedit_change_node *curr = view->change_list;
2099 view_display_clean (view);
2100 view_display_ruler (view);
2102 /* Find the first displayable changed byte */
2103 from = view->dpy_start;
2104 while (curr && (curr->offset < from)) {
2105 curr = curr->next;
2108 tty_setcolor (NORMAL_COLOR);
2109 for (row = 0, col = 0; row < height ; ) {
2110 #ifdef HAVE_CHARSET
2111 if ( view->utf8 ) {
2112 if ((c = get_utf (view, from, &cw)) == -1)
2113 break;
2114 } else
2115 #endif
2117 if ((c = get_byte (view, from)) == -1)
2118 break;
2120 from++;
2121 mc_log("cw=%i\n", cw);
2122 if ( cw > 1 )
2123 from += cw - 1;
2125 if (view->text_nroff_mode && c == '\b') {
2126 int c_prev;
2127 int c_next;
2129 if ((c_next = get_byte_indexed (view, from, 1)) != -1
2130 && g_ascii_isprint (c_next)
2131 && from >= 1
2132 && (c_prev = get_byte (view, from - 1)) != -1
2133 && g_ascii_isprint (c_prev)
2134 && (c_prev == c_next || c_prev == '_'
2135 || (c_prev == '+' && c_next == 'o'))) {
2136 if (col == 0) {
2137 if (row == 0) {
2138 /* We're inside an nroff character sequence at the
2139 * beginning of the screen -- just skip the
2140 * backspace and continue with the next character. */
2141 continue;
2143 row--;
2144 col = width;
2146 col--;
2147 if (c_prev == '_' && (c_next != '_' || view_count_backspaces (view, from) == 1))
2148 tty_setcolor (VIEW_UNDERLINED_COLOR);
2149 else
2150 tty_setcolor (MARKED_COLOR);
2151 continue;
2155 if ((c == '\n') || (col >= width && view->text_wrap_mode)) {
2156 col = 0;
2157 row++;
2158 if (c == '\n' || row >= height)
2159 continue;
2162 if (c == '\r') {
2163 c = get_byte_indexed(view, from, 1);
2164 if (c == '\r' || c == '\n')
2165 continue;
2166 col = 0;
2167 row++;
2168 continue;
2171 if (c == '\t') {
2172 offset_type line, column;
2173 view_offset_to_coord (view, &line, &column, from);
2174 col += (8 - column % 8);
2175 if (view->text_wrap_mode && col >= width && width != 0) {
2176 row += col / width;
2177 col %= width;
2179 continue;
2182 if (view->search_start <= from && from < view->search_end) {
2183 tty_setcolor (SELECTED_COLOR);
2186 if (col >= view->dpy_text_column
2187 && col - view->dpy_text_column < width) {
2188 widget_move (view, top + row, left + (col - view->dpy_text_column));
2189 #ifdef HAVE_CHARSET
2190 if ( utf8_display ) {
2191 if ( !view->utf8 ) {
2192 c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
2194 } else {
2195 if ( view->utf8 ) {
2196 c = convert_from_utf_to_current_c (c, view->converter);
2197 } else {
2198 #endif
2199 c = convert_to_display_c (c);
2200 #ifdef HAVE_CHARSET
2203 #endif
2204 tty_print_anychar (c);
2206 col++;
2207 tty_setcolor (NORMAL_COLOR);
2209 view->dpy_end = from;
2212 /* Displays as much data from view->dpy_start as fits on the screen */
2213 static void
2214 display (WView *view)
2216 view_compute_areas (view);
2217 if (view->hex_mode) {
2218 view_display_hex (view);
2219 } else {
2220 view_display_text (view);
2222 view_display_status (view);
2225 static void
2226 view_place_cursor (WView *view)
2228 const screen_dimen top = view->data_area.top;
2229 const screen_dimen left = view->data_area.left;
2230 screen_dimen col;
2232 col = view->cursor_col;
2233 if (!view->hexview_in_text && view->hexedit_lownibble)
2234 col++;
2235 widget_move (&view->widget, top + view->cursor_row, left + col);
2238 static void
2239 view_update (WView *view)
2241 static int dirt_limit = 1;
2243 if (view->dpy_bbar_dirty) {
2244 view->dpy_bbar_dirty = FALSE;
2245 view_labels (view);
2246 buttonbar_redraw (view->widget.parent);
2249 if (view->dirty > dirt_limit) {
2250 /* Too many updates skipped -> force a update */
2251 display (view);
2252 view->dirty = 0;
2253 /* Raise the update skipping limit */
2254 dirt_limit++;
2255 if (dirt_limit > max_dirt_limit)
2256 dirt_limit = max_dirt_limit;
2258 if (view->dirty) {
2259 if (is_idle ()) {
2260 /* We have time to update the screen properly */
2261 display (view);
2262 view->dirty = 0;
2263 if (dirt_limit > 1)
2264 dirt_limit--;
2265 } else {
2266 /* We are busy -> skipping full update,
2267 only the status line is updated */
2268 view_display_status (view);
2270 /* Here we had a refresh, if fast scrolling does not work
2271 restore the refresh, although this should not happen */
2275 /* {{{ Hex editor }}} */
2277 static void
2278 enqueue_change (struct hexedit_change_node **head,
2279 struct hexedit_change_node *node)
2281 /* chnode always either points to the head of the list or
2282 * to one of the ->next fields in the list. The value at
2283 * this location will be overwritten with the new node. */
2284 struct hexedit_change_node **chnode = head;
2286 while (*chnode != NULL && (*chnode)->offset < node->offset)
2287 chnode = &((*chnode)->next);
2289 node->next = *chnode;
2290 *chnode = node;
2293 static cb_ret_t
2294 view_handle_editkey (WView *view, int key)
2296 struct hexedit_change_node *node;
2297 byte byte_val;
2299 /* Has there been a change at this position? */
2300 node = view->change_list;
2301 while (node && (node->offset != view->hex_cursor))
2302 node = node->next;
2304 if (!view->hexview_in_text) {
2305 /* Hex editing */
2306 unsigned int hexvalue = 0;
2308 if (key >= '0' && key <= '9')
2309 hexvalue = 0 + (key - '0');
2310 else if (key >= 'A' && key <= 'F')
2311 hexvalue = 10 + (key - 'A');
2312 else if (key >= 'a' && key <= 'f')
2313 hexvalue = 10 + (key - 'a');
2314 else
2315 return MSG_NOT_HANDLED;
2317 if (node)
2318 byte_val = node->value;
2319 else
2320 byte_val = get_byte (view, view->hex_cursor);
2322 if (view->hexedit_lownibble) {
2323 byte_val = (byte_val & 0xf0) | (hexvalue);
2324 } else {
2325 byte_val = (byte_val & 0x0f) | (hexvalue << 4);
2327 } else {
2328 /* Text editing */
2329 if (key < 256 && ((key == '\n') || is_printable (key)))
2330 byte_val = key;
2331 else
2332 return MSG_NOT_HANDLED;
2334 if (!node) {
2335 node = g_new (struct hexedit_change_node, 1);
2336 node->offset = view->hex_cursor;
2337 node->value = byte_val;
2338 enqueue_change (&view->change_list, node);
2339 } else {
2340 node->value = byte_val;
2342 view->dirty++;
2343 view_update (view);
2344 view_move_right (view, 1);
2345 return MSG_HANDLED;
2348 static gboolean
2349 view_hexedit_save_changes (WView *view)
2351 struct hexedit_change_node *curr, *next;
2352 int fp, answer;
2353 char *text, *error;
2355 if (view->change_list == NULL)
2356 return TRUE;
2358 retry_save:
2359 assert (view->filename != NULL);
2360 fp = mc_open (view->filename, O_WRONLY);
2361 if (fp == -1)
2362 goto save_error;
2364 for (curr = view->change_list; curr != NULL; curr = next) {
2365 next = curr->next;
2367 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
2368 || mc_write (fp, &(curr->value), 1) != 1)
2369 goto save_error;
2371 /* delete the saved item from the change list */
2372 view->change_list = next;
2373 view->dirty++;
2374 view_set_byte (view, curr->offset, curr->value);
2375 g_free (curr);
2378 if (mc_close (fp) == -1) {
2379 error = g_strdup (strerror (errno));
2380 message (D_ERROR, _(" Save file "),
2381 _(" Error while closing the file: \n %s \n"
2382 " Data may have been written or not. "), error);
2383 g_free (error);
2385 view_update (view);
2386 return TRUE;
2388 save_error:
2389 error = g_strdup (strerror (errno));
2390 text = g_strdup_printf (_(" Cannot save file: \n %s "), error);
2391 g_free (error);
2392 (void) mc_close (fp);
2394 answer = query_dialog (_(" Save file "), text, D_ERROR,
2395 2, _("&Retry"), _("&Cancel"));
2396 g_free (text);
2398 if (answer == 0)
2399 goto retry_save;
2400 return FALSE;
2403 /* {{{ Miscellaneous functions }}} */
2405 static gboolean
2406 view_ok_to_quit (WView *view)
2408 int r;
2410 if (view->change_list == NULL)
2411 return TRUE;
2413 r = query_dialog (_("Quit"),
2414 _(" File was modified, Save with exit? "), D_NORMAL, 3,
2415 _("&Cancel quit"), _("&Yes"), _("&No"));
2417 switch (r) {
2418 case 1:
2419 return view_hexedit_save_changes (view);
2420 case 2:
2421 view_hexedit_free_change_list (view);
2422 return TRUE;
2423 default:
2424 return FALSE;
2428 static inline void
2429 my_define (Dlg_head *h, int idx, const char *text, void (*fn) (WView *),
2430 WView *view)
2432 buttonbar_set_label_data (h, idx, text, (buttonbarfn) fn, view);
2435 /* {{{ Searching }}} */
2438 /* we have set view->search_start and view->search_end and must set
2439 * view->dpy_text_column, view->first_showed_line and view->dpy_start
2440 * try to displaye maximum of match */
2441 void
2442 view_moveto_match (WView *view)
2444 /* AB:FIXME */
2447 static char *
2448 grow_string_buffer (char *text, gulong *size)
2450 char *new;
2452 /* The grow steps */
2453 *size += 160;
2454 new = g_realloc (text, *size);
2455 if (text == NULL) {
2456 *new = '\0';
2458 return new;
2461 static char *
2462 get_line_at (WView *view, offset_type *p, offset_type *skipped)
2464 char *buffer = NULL;
2465 gulong buffer_size = 0;
2466 offset_type usable_size = 0;
2467 int ch;
2468 const gboolean direction = !view->search_backwards;
2469 offset_type pos = *p;
2470 offset_type i = 0;
2471 int prev = '\0';
2473 *skipped = 0;
2475 if (pos == 0 && !direction)
2476 return 0;
2478 /* skip over all the possible zeros in the file */
2479 while ((ch = get_byte (view, pos)) == 0) {
2480 if (pos == 0 && !direction)
2481 return 0;
2482 pos += (direction ? 1 : -1);
2483 i++;
2485 *skipped = i;
2487 if (i == 0 && (pos != 0 || !direction)) {
2488 prev = get_byte (view, pos - (direction ? 1 : -1));
2489 if ((prev == -1) || (prev == '\n'))
2490 prev = '\0';
2493 for (i = 1; ch != -1; ch = get_byte (view, pos)) {
2494 if (i >= usable_size) {
2495 buffer = grow_string_buffer (buffer, &buffer_size);
2496 usable_size = buffer_size - 2; /* prev & null terminator */
2499 buffer[i++] = ch;
2501 if (pos == 0 && !direction)
2502 break;
2504 pos += (direction ? 1 : -1);
2506 if (ch == '\n' || ch == '\0') {
2507 i--; /* Strip newline/zero */
2508 break;
2512 if (buffer) {
2513 buffer[0] = prev;
2514 buffer[i] = '\0';
2516 /* If we are searching backwards, reverse the string */
2517 if (!direction) {
2518 g_strreverse (buffer + 1);
2522 *p = pos;
2523 return buffer;
2526 static void
2527 search_update_steps (WView *view)
2529 offset_type filesize = view_get_filesize (view);
2530 if (filesize != 0)
2531 view->update_steps = 40000;
2532 else /* viewing a data stream, not a file */
2533 view->update_steps = filesize / 100;
2535 /* Do not update the percent display but every 20 ks */
2536 if (view->update_steps < 20000)
2537 view->update_steps = 20000;
2540 /* {{{ User-definable commands }}} */
2543 The functions in this section can be bound to hotkeys. They are all
2544 of the same type (taking a pointer to WView as parameter and
2545 returning void). TODO: In the not-too-distant future, these commands
2546 will become fully configurable, like they already are in the
2547 internal editor. By convention, all the function names end in
2548 "_cmd".
2551 static void
2552 view_help_cmd (void)
2554 interactive_display (NULL, "[Internal File Viewer]");
2557 /* Toggle between hexview and hexedit mode */
2558 static void
2559 view_toggle_hexedit_mode_cmd (WView *view)
2561 view_toggle_hexedit_mode (view);
2562 view_update (view);
2565 /* Toggle between wrapped and unwrapped view */
2566 static void
2567 view_toggle_wrap_mode_cmd (WView *view)
2569 view_toggle_wrap_mode (view);
2570 view_update (view);
2573 /* Toggle between hex view and text view */
2574 static void
2575 view_toggle_hex_mode_cmd (WView *view)
2577 view_toggle_hex_mode (view);
2578 view_update (view);
2581 static void
2582 view_moveto_line_cmd (WView *view)
2584 char *answer, *answer_end, prompt[BUF_SMALL];
2585 offset_type line, col;
2587 view_offset_to_coord (view, &line, &col, view->dpy_start);
2589 g_snprintf (prompt, sizeof (prompt),
2590 _(" The current line number is %d.\n"
2591 " Enter the new line number:"), (int) (line + 1));
2592 answer = input_dialog (_(" Goto line "), prompt, MC_HISTORY_VIEW_GOTO_LINE, "");
2593 if (answer != NULL && answer[0] != '\0') {
2594 errno = 0;
2595 line = strtoul (answer, &answer_end, 10);
2596 if (*answer_end == '\0' && errno == 0 && line >= 1)
2597 view_moveto (view, line - 1, 0);
2599 g_free (answer);
2600 view->dirty++;
2601 view_update (view);
2604 static void
2605 view_moveto_addr_cmd (WView *view)
2607 char *line, *error, prompt[BUF_SMALL];
2608 offset_type addr;
2610 g_snprintf (prompt, sizeof (prompt),
2611 _(" The current address is 0x%lx.\n"
2612 " Enter the new address:"), view->hex_cursor);
2613 line = input_dialog (_(" Goto Address "), prompt, MC_HISTORY_VIEW_GOTO_ADDR, "");
2614 if (line != NULL) {
2615 if (*line != '\0') {
2616 addr = strtoul (line, &error, 0);
2617 if ((*error == '\0') && get_byte (view, addr) != -1) {
2618 view_moveto_offset (view, addr);
2619 } else {
2620 message (D_ERROR, _("Warning"), _(" Invalid address "));
2623 g_free (line);
2625 view->dirty++;
2626 view_update (view);
2629 static void
2630 view_hexedit_save_changes_cmd (WView *view)
2632 (void) view_hexedit_save_changes (view);
2635 static int
2636 view__get_nroff_real_len(WView *view, offset_type start, offset_type length)
2638 return 0; /* AB:FIXME */
2641 /* {{{ Searching }}} */
2643 static int
2644 view_search_update_cmd_callback(const void *user_data, gsize char_offset)
2646 WView *view = (WView *) user_data;
2648 if (char_offset >= view->update_activate) {
2649 view->update_activate += view->update_steps;
2650 if (verbose) {
2651 view_percent (view, char_offset);
2652 mc_refresh ();
2654 if (tty_got_interrupt ())
2655 return MC_SEARCH_CB_ABORT;
2657 /* may be in future return from this callback will change current position
2658 * in searching block. Now this just constant return value.
2660 return 1;
2663 static int
2664 view_search_cmd_callback(const void *user_data, gsize char_offset)
2666 int byte;
2667 WView *view = (WView *) user_data;
2669 byte = get_byte (view, char_offset);
2670 if (byte == -1)
2671 return MC_SEARCH_CB_ABORT;
2672 /* view_read_continue (view, &view->search_onechar_info); */ /* AB:FIXME */
2674 if (view->search_numNeedSkipChar) {
2675 view->search_numNeedSkipChar--;
2676 if (view->search_numNeedSkipChar){
2677 return byte;
2679 return MC_SEARCH_CB_SKIP;
2682 #if 0 /* AB:FIXME */
2683 if (view_read_test_nroff_back (view, &view->search_onechar_info)) {
2684 if (
2685 cmp (view->search_onechar_info.chi1, "_") &&
2686 (!cmp (view->search_onechar_info.cnxt, "_") || !cmp (view->search_onechar_info.chi2, "\b"))
2688 view->search_numNeedSkipChar = 2;
2689 else
2690 view->search_numNeedSkipChar = 1;
2692 return MC_SEARCH_CB_SKIP;
2694 if (byte == '_' && *view->search_onechar_info.cnxt == 0x8)
2696 view->search_numNeedSkipChar = 1;
2697 return MC_SEARCH_CB_SKIP;
2699 #endif
2701 return byte;
2704 static gboolean
2705 view_find (WView *view, gsize search_start, gsize *len)
2707 #if 0 /* AB:FIXME */
2708 gsize search_end;
2710 view->search_numNeedSkipChar = 0;
2712 if (view->search_backwards) {
2713 search_end = view_get_filesize (view);
2714 while ((int) search_start >= 0) {
2715 if (search_end > search_start + view->search->original_len && mc_search_is_fixed_search_str(view->search))
2716 search_end = search_start + view->search->original_len;
2718 view_read_start (view, &view->search_onechar_info, search_start);
2720 if ( mc_search_run(view->search, (void *) view, search_start, search_end, len)
2721 && view->search->normal_offset == search_start )
2722 return TRUE;
2724 search_start--;
2726 view->search->error_str = g_strdup(_(" Search string not found "));
2727 return FALSE;
2729 view_read_start (view, &view->search_onechar_info, search_start);
2730 return mc_search_run(view->search, (void *) view, search_start, view_get_filesize (view), len);
2731 #endif
2734 /* {{{ User-definable commands }}} */
2736 static void
2737 do_search (WView *view)
2739 offset_type search_start;
2740 gboolean isFound = FALSE;
2742 Dlg_head *d = NULL;
2744 size_t match_len;
2746 if (verbose) {
2747 d = create_message (D_NORMAL, _("Search"), _("Searching %s"), view->last_search_string);
2748 mc_refresh ();
2751 /*for avoid infinite search loop we need to increase or decrease start offset of search */
2753 if (view->search_start)
2755 search_start = (view->search_backwards) ? -2 : 2;
2756 search_start = view->search_start + search_start +
2757 view__get_nroff_real_len(view, view->search_start, 2) * search_start;
2759 else
2761 search_start = view->search_start;
2764 if (view->search_backwards && (int) search_start < 0 )
2765 search_start = 0;
2767 /* Compute the percent steps */
2768 search_update_steps (view);
2769 view->update_activate = 0;
2771 tty_enable_interrupt_key ();
2775 if (view_find(view, search_start, &match_len))
2777 view->search_start = view->search->normal_offset +
2778 view__get_nroff_real_len(view,
2779 view->search->start_buffer,
2780 view->search->normal_offset - view->search->start_buffer);
2782 view->search_end = view->search_start + match_len +
2783 view__get_nroff_real_len(view, view->search_start, match_len + 1);
2785 if (view->hex_mode){
2786 view->hex_cursor = view->search_start;
2787 view->hexedit_lownibble = FALSE;
2788 view->dpy_start = view->search_start - view->search_start % view->bytes_per_line;
2789 view->dpy_end = view->search_end - view->search_end % view->bytes_per_line;
2792 if (verbose) {
2793 dlg_run_done (d);
2794 destroy_dlg (d);
2795 d = create_message (D_NORMAL, _("Search"), _("Seeking to search result"));
2796 mc_refresh ();
2799 view_moveto_match (view);
2800 isFound = TRUE;
2801 break;
2803 } while (view_may_still_grow(view));
2805 if (!isFound){
2806 if (view->search->error_str)
2807 message (D_NORMAL, _("Search"), "%s", view->search->error_str);
2810 view->dirty++;
2811 view_update (view);
2813 tty_disable_interrupt_key ();
2815 if (verbose) {
2816 dlg_run_done (d);
2817 destroy_dlg (d);
2822 /* Both views */
2823 static void
2824 view_search_cmd (WView *view)
2826 enum {
2827 SEARCH_DLG_MIN_HEIGHT = 10,
2828 SEARCH_DLG_HEIGHT_SUPPLY = 3,
2829 SEARCH_DLG_WIDTH = 58
2832 char *defval = g_strdup (view->last_search_string != NULL ? view->last_search_string : "");
2833 char *exp = NULL;
2834 #ifdef HAVE_CHARSET
2835 GString *tmp;
2836 #endif
2838 int ttype_of_search = (int) view->search_type;
2839 int tall_codepages = (int) view->search_all_codepages;
2840 int tsearch_case = (int) view->search_case;
2841 int tsearch_backwards = (int) view->search_backwards;
2843 gchar **list_of_types = mc_search_get_types_strings_array();
2844 int SEARCH_DLG_HEIGHT = SEARCH_DLG_MIN_HEIGHT + g_strv_length (list_of_types) - SEARCH_DLG_HEIGHT_SUPPLY;
2846 QuickWidget quick_widgets[] = {
2848 {quick_button, 6, 10, SEARCH_DLG_HEIGHT - 3, SEARCH_DLG_HEIGHT, N_("&Cancel"), 0,
2849 B_CANCEL, 0, 0, NULL, NULL, NULL},
2851 {quick_button, 2, 10, SEARCH_DLG_HEIGHT - 3, SEARCH_DLG_HEIGHT , N_("&OK"), 0, B_ENTER,
2852 0, 0, NULL, NULL, NULL},
2854 #ifdef HAVE_CHARSET
2855 {quick_checkbox, SEARCH_DLG_WIDTH/2 + 3, SEARCH_DLG_WIDTH, 6, SEARCH_DLG_HEIGHT, N_("All charsets"), 0, 0,
2856 &tall_codepages, 0, NULL, NULL, NULL},
2857 #endif
2859 {quick_checkbox, SEARCH_DLG_WIDTH/2 + 3, SEARCH_DLG_WIDTH, 5, SEARCH_DLG_HEIGHT,
2860 N_("&Backwards"), 0, 0, &tsearch_backwards, 0, NULL, NULL, NULL},
2862 {quick_checkbox, SEARCH_DLG_WIDTH/2 + 3, SEARCH_DLG_WIDTH, 4, SEARCH_DLG_HEIGHT, N_("case &Sensitive"), 0, 0,
2863 &tsearch_case, 0, NULL, NULL, NULL},
2865 {quick_radio, 3, SEARCH_DLG_WIDTH, 4, SEARCH_DLG_HEIGHT, 0, g_strv_length (list_of_types), ttype_of_search,
2866 (void *) &ttype_of_search, const_cast (char **, list_of_types), NULL, NULL, NULL},
2869 {quick_input, 3, SEARCH_DLG_WIDTH, 3, SEARCH_DLG_HEIGHT, defval, 52, 0,
2870 0, &exp, MC_HISTORY_SHARED_SEARCH, NULL, NULL},
2872 {quick_label, 2, SEARCH_DLG_WIDTH, 2, SEARCH_DLG_HEIGHT,
2873 N_(" Enter search string:"), 0, 0, 0, 0, 0, NULL, NULL},
2875 NULL_QuickWidget
2878 QuickDialog Quick_input = {
2879 SEARCH_DLG_WIDTH, SEARCH_DLG_HEIGHT, -1, 0, N_("Search"),
2880 "[Input Line Keys]", quick_widgets, 0
2883 convert_to_display (defval);
2886 if (quick_dialog (&Quick_input) == B_CANCEL)
2887 goto cleanup;
2889 view->search_backwards = tsearch_backwards;
2890 view->search_type = (mc_search_type_t) ttype_of_search;
2892 view->search_all_codepages = (gboolean) tall_codepages;
2893 view->search_case = (gboolean) tsearch_case;
2895 if (exp == NULL || exp[0] == '\0')
2896 goto cleanup;
2898 #ifdef HAVE_CHARSET
2899 tmp = str_convert_to_input (exp);
2901 if (tmp)
2903 g_free(exp);
2904 exp = g_string_free (tmp, FALSE);
2906 #endif
2908 g_free (view->last_search_string);
2909 view->last_search_string = exp;
2910 exp = NULL;
2912 if (view->search)
2913 mc_search_free(view->search);
2915 view->search = mc_search_new(view->last_search_string, -1);
2916 if (! view->search)
2917 goto cleanup;
2919 view->search->search_type = view->search_type;
2920 view->search->is_all_charsets = view->search_all_codepages;
2921 view->search->is_case_sentitive = view->search_case;
2922 view->search->search_fn = view_search_cmd_callback;
2923 view->search->update_fn = view_search_update_cmd_callback;
2925 do_search (view);
2927 cleanup:
2928 g_free (exp);
2929 g_free (defval);
2932 static void
2933 view_toggle_magic_mode_cmd (WView *view)
2935 view_toggle_magic_mode (view);
2936 view_update (view);
2939 static void
2940 view_toggle_nroff_mode_cmd (WView *view)
2942 view_toggle_nroff_mode (view);
2943 view_update (view);
2946 static void
2947 view_quit_cmd (WView *view)
2949 if (view_ok_to_quit (view))
2950 dlg_stop (view->widget.parent);
2953 /* {{{ Miscellaneous functions }}} */
2955 /* Define labels and handlers for functional keys */
2956 static void
2957 view_labels (WView *view)
2959 const char *text;
2960 Dlg_head *h = view->widget.parent;
2962 buttonbar_set_label (h, 1, Q_("ButtonBar|Help"), view_help_cmd);
2964 my_define (h, 10, Q_("ButtonBar|Quit"), view_quit_cmd, view);
2965 text = view->hex_mode ? "ButtonBar|Ascii" : "ButtonBar|Hex";
2966 my_define (h, 4, Q_(text), view_toggle_hex_mode_cmd, view);
2967 text = view->hex_mode ?"ButtonBar|Goto": "ButtonBar|Line";
2968 my_define (h, 5, Q_(text),
2969 view->hex_mode ? view_moveto_addr_cmd : view_moveto_line_cmd, view);
2971 if (view->hex_mode) {
2972 if (view->hexedit_mode) {
2973 my_define (h, 2, Q_("ButtonBar|View"),
2974 view_toggle_hexedit_mode_cmd, view);
2975 } else if (view->datasource == DS_FILE) {
2976 my_define (h, 2, Q_("ButtonBar|Edit"),
2977 view_toggle_hexedit_mode_cmd, view);
2978 } else {
2979 buttonbar_clear_label (h, 2);
2981 my_define (h, 6, Q_("ButtonBar|Save"),
2982 view_hexedit_save_changes_cmd, view);
2983 } else {
2984 text = view->text_wrap_mode ? "ButtonBar|UnWrap" : "ButtonBar|Wrap";
2985 my_define (h, 2, Q_(text), view_toggle_wrap_mode_cmd, view);
2988 text = view->hex_mode ? "ButtonBar|HxSrch" : "ButtonBar|Search";
2989 my_define (h, 7, Q_(text), view_search_cmd, view);
2990 text = view->magic_mode ? "ButtonBar|Raw" : "ButtonBar|Parse";
2991 my_define (h, 8, Q_(text), view_toggle_magic_mode_cmd, view);
2993 /* don't override the key to access the main menu */
2994 if (!view_is_in_panel (view)) {
2995 text = view->text_nroff_mode ? "ButtonBar|Unform" : "ButtonBar|Format";
2996 my_define (h, 9, Q_(text), view_toggle_nroff_mode_cmd, view);
2997 my_define (h, 3, Q_("ButtonBar|Quit"), view_quit_cmd, view);
3001 /* {{{ Event handling }}} */
3003 /* Check for left and right arrows, possibly with modifiers */
3004 static cb_ret_t
3005 check_left_right_keys (WView *view, int c)
3007 if (c == KEY_LEFT) {
3008 view_move_left (view, 1);
3009 return MSG_HANDLED;
3012 if (c == KEY_RIGHT) {
3013 view_move_right (view, 1);
3014 return MSG_HANDLED;
3017 /* Ctrl with arrows moves by 10 postions in the unwrap mode */
3018 if (view->hex_mode || view->text_wrap_mode)
3019 return MSG_NOT_HANDLED;
3021 if (c == (KEY_M_CTRL | KEY_LEFT)) {
3022 if (view->dpy_text_column >= 10)
3023 view->dpy_text_column -= 10;
3024 else
3025 view->dpy_text_column = 0;
3026 view->dirty++;
3027 return MSG_HANDLED;
3030 if (c == (KEY_M_CTRL | KEY_RIGHT)) {
3031 if (view->dpy_text_column <= OFFSETTYPE_MAX - 10)
3032 view->dpy_text_column += 10;
3033 else
3034 view->dpy_text_column = OFFSETTYPE_MAX;
3035 view->dirty++;
3036 return MSG_HANDLED;
3039 return MSG_NOT_HANDLED;
3042 /* {{{ User-definable commands }}} */
3044 static void
3045 view_continue_search_cmd (WView *view)
3047 if (view->last_search_string != NULL) {
3048 do_search (view);
3049 } else {
3050 /* if not... then ask for an expression */
3051 view_search_cmd (view);
3055 static void
3056 view_toggle_ruler_cmd (WView *view)
3058 static const enum ruler_type next[3] = {
3059 RULER_TOP,
3060 RULER_BOTTOM,
3061 RULER_NONE
3064 assert ((size_t) ruler < 3);
3065 ruler = next[(size_t) ruler];
3066 view->dirty++;
3069 /* {{{ Event handling }}} */
3071 static void view_cmk_move_up (void *w, int n) {
3072 view_move_up ((WView *) w, n);
3074 static void view_cmk_move_down (void *w, int n) {
3075 view_move_down ((WView *) w, n);
3077 static void view_cmk_moveto_top (void *w, int n) {
3078 (void) &n;
3079 view_moveto_top ((WView *) w);
3081 static void view_cmk_moveto_bottom (void *w, int n) {
3082 (void) &n;
3083 view_moveto_bottom ((WView *) w);
3086 static void
3087 view_select_encoding (WView *view)
3089 #ifdef HAVE_CHARSET
3090 const char *enc = NULL;
3092 if (!do_select_codepage ())
3093 return;
3095 enc = get_codepage_id (source_codepage);
3096 if (enc != NULL) {
3097 GIConv conv;
3099 conv = str_crt_conv_from (enc);
3100 if (conv != INVALID_CONV) {
3101 if (view->converter != str_cnv_from_term)
3102 str_close_conv (view->converter);
3103 view->converter = conv;
3106 if (enc != NULL && str_isutf8 (enc) != 0)
3107 view->utf8 = TRUE;
3108 else
3109 view->utf8 = FALSE;
3111 #endif
3114 /* Both views */
3115 static cb_ret_t
3116 view_handle_key (WView *view, int c)
3118 c = convert_from_input_c (c);
3120 if (view->hex_mode) {
3121 switch (c) {
3122 case '\t':
3123 view->hexview_in_text = !view->hexview_in_text;
3124 view->dirty++;
3125 return MSG_HANDLED;
3127 case XCTRL ('a'):
3128 view_moveto_bol (view);
3129 view->dirty++;
3130 return MSG_HANDLED;
3132 case XCTRL ('b'):
3133 view_move_left (view, 1);
3134 return MSG_HANDLED;
3136 case XCTRL ('e'):
3137 view_moveto_eol (view);
3138 return MSG_HANDLED;
3140 case XCTRL ('f'):
3141 view_move_right (view, 1);
3142 return MSG_HANDLED;
3145 if (view->hexedit_mode
3146 && view_handle_editkey (view, c) == MSG_HANDLED)
3147 return MSG_HANDLED;
3150 if (check_left_right_keys (view, c))
3151 return MSG_HANDLED;
3153 if (check_movement_keys (c, view->data_area.height + 1, view,
3154 view_cmk_move_up, view_cmk_move_down,
3155 view_cmk_moveto_top, view_cmk_moveto_bottom))
3156 return MSG_HANDLED;
3158 switch (c) {
3160 case '?':
3161 case '/':
3162 view->search_type = MC_SEARCH_T_REGEX;
3163 view_search_cmd(view);
3164 return MSG_HANDLED;
3165 break;
3166 /* Continue search */
3167 case XCTRL ('r'):
3168 case XCTRL ('s'):
3169 case 'n':
3170 case KEY_F (17):
3171 view_continue_search_cmd (view);
3172 return MSG_HANDLED;
3174 /* toggle ruler */
3175 case ALT ('r'):
3176 view_toggle_ruler_cmd (view);
3177 return MSG_HANDLED;
3179 case 'h':
3180 view_move_left (view, 1);
3181 return MSG_HANDLED;
3183 case 'j':
3184 case '\n':
3185 case 'e':
3186 view_move_down (view, 1);
3187 return MSG_HANDLED;
3189 case 'd':
3190 view_move_down (view, (view->data_area.height + 1) / 2);
3191 return MSG_HANDLED;
3193 case 'u':
3194 view_move_up (view, (view->data_area.height + 1) / 2);
3195 return MSG_HANDLED;
3197 case 'k':
3198 case 'y':
3199 view_move_up (view, 1);
3200 return MSG_HANDLED;
3202 case 'l':
3203 view_move_right (view, 1);
3204 return MSG_HANDLED;
3206 case ' ':
3207 case 'f':
3208 view_move_down (view, view->data_area.height);
3209 return MSG_HANDLED;
3211 case XCTRL ('o'):
3212 view_other_cmd ();
3213 return MSG_HANDLED;
3215 /* Unlike Ctrl-O, run a new shell if the subshell is not running. */
3216 case '!':
3217 exec_shell ();
3218 return MSG_HANDLED;
3220 case 'b':
3221 view_move_up (view, view->data_area.height);
3222 return MSG_HANDLED;
3224 case KEY_IC:
3225 view_move_up (view, 2);
3226 return MSG_HANDLED;
3228 case KEY_DC:
3229 view_move_down (view, 2);
3230 return MSG_HANDLED;
3232 case 'm':
3233 view->marks[view->marker] = view->dpy_start;
3234 return MSG_HANDLED;
3236 case 'r':
3237 view->dpy_start = view->marks[view->marker];
3238 view->dirty++;
3239 return MSG_HANDLED;
3241 /* Use to indicate parent that we want to see the next/previous file */
3242 /* Does not work in panel mode */
3243 case XCTRL ('f'):
3244 case XCTRL ('b'):
3245 if (!view_is_in_panel (view))
3246 view->move_dir = c == XCTRL ('f') ? 1 : -1;
3247 /* FALLTHROUGH */
3248 case 'q':
3249 case XCTRL ('g'):
3250 case ESC_CHAR:
3251 if (view_ok_to_quit (view))
3252 view->want_to_quit = TRUE;
3253 return MSG_HANDLED;
3255 case XCTRL ('t'):
3256 view_select_encoding (view);
3257 view->dirty++;
3258 view_update (view);
3259 return MSG_HANDLED;
3261 #ifdef MC_ENABLE_DEBUGGING_CODE
3262 case 't': /* mnemonic: "test" */
3263 view_ccache_dump (view);
3264 return MSG_HANDLED;
3265 #endif
3267 if (c >= '0' && c <= '9')
3268 view->marker = c - '0';
3270 /* Key not used */
3271 return MSG_NOT_HANDLED;
3274 /* Both views */
3275 static int
3276 view_event (WView *view, Gpm_Event *event, int *result)
3278 screen_dimen y, x;
3280 *result = MOU_NORMAL;
3282 /* We are not interested in the release events */
3283 if (!(event->type & (GPM_DOWN | GPM_DRAG)))
3284 return 0;
3286 /* Wheel events */
3287 if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) {
3288 view_move_up (view, 2);
3289 return 1;
3291 if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
3292 view_move_down (view, 2);
3293 return 1;
3296 x = event->x;
3297 y = event->y;
3299 /* Scrolling left and right */
3300 if (!view->text_wrap_mode) {
3301 if (x < view->data_area.width * 1/4) {
3302 view_move_left (view, 1);
3303 goto processed;
3304 } else if (x < view->data_area.width * 3/4) {
3305 /* ignore the click */
3306 } else {
3307 view_move_right (view, 1);
3308 goto processed;
3312 /* Scrolling up and down */
3313 if (y < view->data_area.top + view->data_area.height * 1/3) {
3314 if (mouse_move_pages_viewer)
3315 view_move_up (view, view->data_area.height / 2);
3316 else
3317 view_move_up (view, 1);
3318 goto processed;
3319 } else if (y < view->data_area.top + view->data_area.height * 2/3) {
3320 /* ignore the click */
3321 } else {
3322 if (mouse_move_pages_viewer)
3323 view_move_down (view, view->data_area.height / 2);
3324 else
3325 view_move_down (view, 1);
3326 goto processed;
3329 return 0;
3331 processed:
3332 *result = MOU_REPEAT;
3333 return 1;
3336 /* Real view only */
3337 static int
3338 real_view_event (Gpm_Event *event, void *x)
3340 WView *view = (WView *) x;
3341 int result;
3343 if (view_event (view, event, &result))
3344 view_update (view);
3345 return result;
3348 static void
3349 view_adjust_size (Dlg_head *h)
3351 WView *view;
3352 WButtonBar *bar;
3354 /* Look up the viewer and the buttonbar, we assume only two widgets here */
3355 view = (WView *) find_widget_type (h, view_callback);
3356 bar = find_buttonbar (h);
3357 widget_set_size (&view->widget, 0, 0, LINES - 1, COLS);
3358 widget_set_size ((Widget *) bar, LINES - 1, 0, 1, COLS);
3360 view_compute_areas (view);
3361 view_update_bytes_per_line (view);
3364 /* Callback for the view dialog */
3365 static cb_ret_t
3366 view_dialog_callback (Dlg_head *h, dlg_msg_t msg, int parm)
3368 switch (msg) {
3369 case DLG_RESIZE:
3370 view_adjust_size (h);
3371 return MSG_HANDLED;
3373 default:
3374 return default_dlg_callback (h, msg, parm);
3378 /* {{{ External interface }}} */
3380 /* Real view only */
3382 mc_internal_viewer (const char *command, const char *file,
3383 int *move_dir_p, int start_line)
3385 gboolean succeeded;
3386 WView *wview;
3387 WButtonBar *bar;
3388 Dlg_head *view_dlg;
3390 /* Create dialog and widgets, put them on the dialog */
3391 view_dlg =
3392 create_dlg (0, 0, LINES, COLS, NULL, view_dialog_callback,
3393 "[Internal File Viewer]", NULL, DLG_WANT_TAB);
3395 wview = view_new (0, 0, COLS, LINES - 1, 0);
3397 bar = buttonbar_new (1);
3399 add_widget (view_dlg, bar);
3400 add_widget (view_dlg, wview);
3402 succeeded = view_load (wview, command, file, start_line);
3403 if (succeeded) {
3404 run_dlg (view_dlg);
3405 if (move_dir_p)
3406 *move_dir_p = wview->move_dir;
3407 } else {
3408 if (move_dir_p)
3409 *move_dir_p = 0;
3411 destroy_dlg (view_dlg);
3413 return succeeded;
3416 /* {{{ Miscellaneous functions }}} */
3418 static void
3419 view_hook (void *v)
3421 WView *view = (WView *) v;
3422 WPanel *panel;
3424 /* If the user is busy typing, wait until he finishes to update the
3425 screen */
3426 if (!is_idle ()) {
3427 if (!hook_present (idle_hook, view_hook))
3428 add_hook (&idle_hook, view_hook, v);
3429 return;
3432 delete_hook (&idle_hook, view_hook);
3434 if (get_current_type () == view_listing)
3435 panel = current_panel;
3436 else if (get_other_type () == view_listing)
3437 panel = other_panel;
3438 else
3439 return;
3441 view_load (view, 0, panel->dir.list[panel->selected].fname, 0);
3442 display (view);
3445 /* {{{ Event handling }}} */
3447 static cb_ret_t
3448 view_callback (Widget *w, widget_msg_t msg, int parm)
3450 WView *view = (WView *) w;
3451 cb_ret_t i;
3452 Dlg_head *h = view->widget.parent;
3454 view_compute_areas (view);
3455 view_update_bytes_per_line (view);
3457 switch (msg) {
3458 case WIDGET_INIT:
3459 if (view_is_in_panel (view))
3460 add_hook (&select_file_hook, view_hook, view);
3461 else
3462 view->dpy_bbar_dirty = TRUE;
3463 return MSG_HANDLED;
3465 case WIDGET_DRAW:
3466 display (view);
3467 return MSG_HANDLED;
3469 case WIDGET_CURSOR:
3470 if (view->hex_mode)
3471 view_place_cursor (view);
3472 return MSG_HANDLED;
3474 case WIDGET_KEY:
3475 i = view_handle_key ((WView *) view, parm);
3476 if (view->want_to_quit && !view_is_in_panel (view))
3477 dlg_stop (h);
3478 else {
3479 view_update (view);
3481 return i;
3483 case WIDGET_FOCUS:
3484 view->dpy_bbar_dirty = TRUE;
3485 view_update (view);
3486 return MSG_HANDLED;
3488 case WIDGET_DESTROY:
3489 view_done (view);
3490 if (view_is_in_panel (view))
3491 delete_hook (&select_file_hook, view_hook);
3492 return MSG_HANDLED;
3494 default:
3495 return default_proc (msg, parm);
3499 /* {{{ External interface }}} */
3501 WView *
3502 view_new (int y, int x, int cols, int lines, int is_panel)
3504 WView *view = g_new0 (WView, 1);
3505 size_t i;
3507 init_widget (&view->widget, y, x, lines, cols,
3508 view_callback,
3509 real_view_event);
3511 view->filename = NULL;
3512 view->command = NULL;
3514 view_set_datasource_none (view);
3516 view->growbuf_in_use = FALSE;
3517 /* leave the other growbuf fields uninitialized */
3519 view->hex_mode = FALSE;
3520 view->hexedit_mode = FALSE;
3521 view->hexview_in_text = FALSE;
3522 view->text_nroff_mode = FALSE;
3523 view->text_wrap_mode = FALSE;
3524 view->magic_mode = FALSE;
3525 view->utf8 = FALSE;
3527 view->hexedit_lownibble = FALSE;
3528 view->coord_cache = NULL;
3530 view->dpy_frame_size = is_panel ? 1 : 0;
3531 view->dpy_start = 0;
3532 view->dpy_text_column = 0;
3533 view->dpy_end = 0;
3534 view->hex_cursor = 0;
3535 view->cursor_col = 0;
3536 view->cursor_row = 0;
3537 view->change_list = NULL;
3538 view->converter = str_cnv_from_term;
3540 /* {status,ruler,data}_area are left uninitialized */
3542 view->dirty = 0;
3543 view->dpy_bbar_dirty = TRUE;
3544 view->bytes_per_line = 1;
3546 view->search_start = 0;
3547 view->search_end = 0;
3549 view->want_to_quit = FALSE;
3550 view->marker = 0;
3551 for (i = 0; i < sizeof(view->marks) / sizeof(view->marks[0]); i++)
3552 view->marks[i] = 0;
3554 view->move_dir = 0;
3555 view->update_steps = 0;
3556 view->update_activate = 0;
3558 if (default_hex_mode)
3559 view_toggle_hex_mode (view);
3560 if (default_nroff_flag)
3561 view_toggle_nroff_mode (view);
3562 if (global_wrap_mode)
3563 view_toggle_wrap_mode (view);
3564 if (default_magic_flag)
3565 view_toggle_magic_mode (view);
3567 return view;