Updated italian translation
[midnight-commander.git] / src / view.c
bloba2109462bbce2a585ae1495895762ef29773b004
1 /*
2 Internal file viewer for the Midnight Commander
4 Copyright (C) 1994, 1995, 1996 The Free Software Foundation
6 Written by: 1994, 1995, 1998 Miguel de Icaza
7 1994, 1995 Janne Kukonlehto
8 1995 Jakub Jelinek
9 1996 Joseph M. Hinkle
10 1997 Norbert Warmuth
11 1998 Pavel Machek
12 2004 Roland Illig <roland.illig@gmx.de>
13 2005 Roland Illig <roland.illig@gmx.de>
15 This program is free software; you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation; either version 2 of the License, or
18 (at your option) any later version.
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
30 #ifdef HAVE_CONFIG_H
31 # include <config.h>
32 #endif
34 #include <assert.h>
35 #include <ctype.h>
36 #include <errno.h>
37 #include <limits.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <unistd.h>
46 #include "global.h"
47 #include "tty.h"
48 #include "cmd.h" /* For view_other_cmd */
49 #include "dialog.h" /* Needed by widget.h */
50 #include "widget.h" /* Needed for buttonbar_new */
51 #include "color.h"
52 #include "mouse.h"
53 #include "help.h"
54 #include "key.h" /* For mi_getch() */
55 #include "layout.h"
56 #include "setup.h"
57 #include "wtools.h" /* For query_set_sel() */
58 #include "dir.h"
59 #include "panel.h" /* Needed for current_panel and other_panel */
60 #include "win.h"
61 #include "execute.h"
62 #include "main.h" /* slow_terminal */
63 #include "view.h"
65 #include "charsets.h"
66 #include "selcodepage.h"
68 /* Block size for reading files in parts */
69 #define VIEW_PAGE_SIZE ((size_t) 8192)
70 #define VIEW_COORD_CACHE_GRANUL 1024
72 typedef unsigned char byte;
74 /* Offset in bytes into a file */
75 typedef unsigned long offset_type;
76 #define INVALID_OFFSET ((offset_type) -1)
77 #define OFFSETTYPE_MAX (~((offset_type) 0))
78 #define OFFSETTYPE_PRIX "lX"
79 #define OFFSETTYPE_PRId "lu"
81 /* A width or height on the screen */
82 typedef unsigned int screen_dimen;
84 /* A cache entry for mapping offsets into line/column pairs and vice versa.
85 * cc_offset, cc_line, and cc_column are the 0-based values of the offset,
86 * line and column of that cache entry. cc_nroff_column is the column
87 * corresponding to cc_offset in nroff mode.
89 struct coord_cache_entry {
90 offset_type cc_offset;
91 offset_type cc_line;
92 offset_type cc_column;
93 offset_type cc_nroff_column;
96 /* A node for building a change list on change_list */
97 struct hexedit_change_node {
98 struct hexedit_change_node *next;
99 offset_type offset;
100 byte value;
103 /* data sources of the view */
104 enum view_ds {
105 DS_NONE, /* No data available */
106 DS_STDIO_PIPE, /* Data comes from a pipe using popen/pclose */
107 DS_VFS_PIPE, /* Data comes from a piped-in VFS file */
108 DS_FILE, /* Data comes from a VFS file */
109 DS_STRING /* Data comes from a string in memory */
112 struct area {
113 screen_dimen top, left;
114 screen_dimen height, width;
117 struct WView {
118 Widget widget;
120 char *filename; /* Name of the file */
121 char *command; /* Command used to pipe data in */
123 enum view_ds datasource; /* Where the displayed data comes from */
125 /* stdio pipe data source */
126 FILE *ds_stdio_pipe; /* Output of a shell command */
128 /* vfs pipe data source */
129 int ds_vfs_pipe; /* Non-seekable vfs file descriptor */
131 /* vfs file data source */
132 int ds_file_fd; /* File with random access */
133 off_t ds_file_filesize; /* Size of the file */
134 off_t ds_file_offset; /* Offset of the currently loaded data */
135 byte *ds_file_data; /* Currently loaded data */
136 size_t ds_file_datalen; /* Number of valid bytes in file_data */
137 size_t ds_file_datasize; /* Number of allocated bytes in file_data */
139 /* string data source */
140 byte *ds_string_data; /* The characters of the string */
141 size_t ds_string_len; /* The length of the string */
143 /* Growing buffers information */
144 gboolean growbuf_in_use; /* Use the growing buffers? */
145 byte **growbuf_blockptr; /* Pointer to the block pointers */
146 size_t growbuf_blocks; /* The number of blocks in *block_ptr */
147 size_t growbuf_lastindex; /* Number of bytes in the last page of the
148 growing buffer */
149 gboolean growbuf_finished; /* TRUE when all data has been read. */
151 /* Editor modes */
152 gboolean hex_mode; /* Hexview or Hexedit */
153 gboolean hexedit_mode; /* Hexedit */
154 gboolean hexview_in_text; /* Is the hexview cursor in the text area? */
155 gboolean text_nroff_mode; /* Nroff-style highlighting */
156 gboolean text_wrap_mode; /* Wrap text lines to fit them on the screen */
157 gboolean magic_mode; /* Preprocess the file using external programs */
159 /* Additional editor state */
160 gboolean hexedit_lownibble; /* Are we editing the last significant nibble? */
161 GArray *coord_cache; /* Cache for mapping offsets to cursor positions */
163 /* Display information */
164 screen_dimen dpy_frame_size;/* Size of the frame surrounding the real viewer */
165 offset_type dpy_start; /* Offset of the displayed data */
166 offset_type dpy_end; /* Offset after the displayed data */
167 offset_type dpy_text_column;/* Number of skipped columns in non-wrap
168 * text mode */
169 offset_type hex_cursor; /* Hexview cursor position in file */
170 screen_dimen cursor_col; /* Cursor column */
171 screen_dimen cursor_row; /* Cursor row */
172 struct hexedit_change_node *change_list; /* Linked list of changes */
173 struct area status_area; /* Where the status line is displayed */
174 struct area ruler_area; /* Where the ruler is displayed */
175 struct area data_area; /* Where the data is displayed */
177 int dirty; /* Number of skipped updates */
178 gboolean dpy_bbar_dirty; /* Does the button bar need to be updated? */
180 /* Mode variables */
181 int bytes_per_line; /* Number of bytes per line in hex mode */
183 /* Search variables */
184 offset_type search_start; /* First character to start searching from */
185 offset_type search_length; /* Length of found string or 0 if none was found */
186 char *search_exp; /* The search expression */
187 int direction; /* 1= forward; -1 backward */
188 void (*last_search)(WView *);
189 /* Pointer to the last search command */
190 gboolean want_to_quit; /* Prepare for cleanup ... */
192 /* Markers */
193 int marker; /* mark to use */
194 offset_type marks [10]; /* 10 marks: 0..9 */
196 int move_dir; /* return value from widget:
197 * 0 do nothing
198 * -1 view previous file
199 * 1 view next file
202 offset_type update_steps; /* The number of bytes between percent
203 * increments */
204 offset_type update_activate;/* Last point where we updated the status */
208 /* {{{ Global Variables }}} */
210 /* Maxlimit for skipping updates */
211 int max_dirt_limit = 10;
213 /* If set, show a ruler */
214 static enum ruler_type {
215 RULER_NONE,
216 RULER_TOP,
217 RULER_BOTTOM
218 } ruler = RULER_NONE;
220 /* Scrolling is done in pages or line increments */
221 int mouse_move_pages_viewer = 1;
223 /* wrap mode default */
224 int global_wrap_mode = 1;
226 int default_hex_mode = 0;
227 int default_magic_flag = 1;
228 int default_nroff_flag = 1;
229 int altered_hex_mode = 0;
230 int altered_magic_flag = 0;
231 int altered_nroff_flag = 0;
233 static const char hex_char[] = "0123456789ABCDEF";
235 int mcview_remember_file_position = FALSE;
237 /* {{{ Function Prototypes }}} */
239 /* Our widget callback */
240 static cb_ret_t view_callback (Widget *, widget_msg_t, int);
242 static int regexp_view_search (WView * view, char *pattern, char *string,
243 int match_type);
244 static void view_labels (WView * view);
246 static void view_init_growbuf (WView *);
247 static void view_place_cursor (WView *view);
248 static void display (WView *);
249 static void view_done (WView *);
251 /* {{{ Helper Functions }}} */
253 /* difference or zero */
254 static inline screen_dimen
255 dimen_doz (screen_dimen a, screen_dimen b)
257 return (a >= b) ? a - b : 0;
260 static inline screen_dimen
261 dimen_min (screen_dimen a, screen_dimen b)
263 return (a < b) ? a : b;
266 static inline offset_type
267 offset_doz (offset_type a, offset_type b)
269 return (a >= b) ? a - b : 0;
272 static inline offset_type
273 offset_rounddown (offset_type a, offset_type b)
275 assert (b != 0);
276 return a - a % b;
279 /* {{{ Simple Primitive Functions for WView }}} */
281 static inline gboolean
282 view_is_in_panel (WView *view)
284 return (view->dpy_frame_size != 0);
287 static void
288 view_compute_areas (WView *view)
290 struct area view_area;
291 screen_dimen height, rest, y;
293 /* The viewer is surrounded by a frame of size view->dpy_frame_size.
294 * Inside that frame, there are: The status line (at the top),
295 * the data area and an optional ruler, which is shown above or
296 * below the data area. */
298 view_area.top = view->dpy_frame_size;
299 view_area.left = view->dpy_frame_size;
300 view_area.height = dimen_doz(view->widget.lines, 2 * view->dpy_frame_size);
301 view_area.width = dimen_doz(view->widget.cols, 2 * view->dpy_frame_size);
303 /* Most coordinates of the areas equal those of the whole viewer */
304 view->status_area = view_area;
305 view->ruler_area = view_area;
306 view->data_area = view_area;
308 /* Compute the heights of the areas */
309 rest = view_area.height;
311 height = dimen_min(rest, 1);
312 view->status_area.height = height;
313 rest -= height;
315 height = dimen_min(rest, (ruler == RULER_NONE || view->hex_mode) ? 0 : 2);
316 view->ruler_area.height = height;
317 rest -= height;
319 view->data_area.height = rest;
321 /* Compute the position of the areas */
322 y = view_area.top;
324 view->status_area.top = y;
325 y += view->status_area.height;
327 if (ruler == RULER_TOP) {
328 view->ruler_area.top = y;
329 y += view->ruler_area.height;
332 view->data_area.top = y;
333 y += view->data_area.height;
335 if (ruler == RULER_BOTTOM) {
336 view->ruler_area.top = y;
337 y += view->ruler_area.height;
341 static void
342 view_hexedit_free_change_list (WView *view)
344 struct hexedit_change_node *curr, *next;
346 for (curr = view->change_list; curr != NULL; curr = next) {
347 next = curr->next;
348 g_free (curr);
350 view->change_list = NULL;
351 view->dirty++;
354 /* {{{ Growing buffer }}} */
356 static void
357 view_init_growbuf (WView *view)
359 view->growbuf_in_use = TRUE;
360 view->growbuf_blockptr = NULL;
361 view->growbuf_blocks = 0;
362 view->growbuf_lastindex = VIEW_PAGE_SIZE;
363 view->growbuf_finished = FALSE;
366 static void
367 view_growbuf_free (WView *view)
369 size_t i;
371 assert (view->growbuf_in_use);
373 for (i = 0; i < view->growbuf_blocks; i++)
374 g_free (view->growbuf_blockptr[i]);
375 g_free (view->growbuf_blockptr);
376 view->growbuf_blockptr = NULL;
377 view->growbuf_in_use = FALSE;
380 static offset_type
381 view_growbuf_filesize (WView *view)
383 assert(view->growbuf_in_use);
385 if (view->growbuf_blocks == 0)
386 return 0;
387 else
388 return ((offset_type) view->growbuf_blocks - 1) * VIEW_PAGE_SIZE
389 + view->growbuf_lastindex;
392 /* Copies the output from the pipe to the growing buffer, until either
393 * the end-of-pipe is reached or the interval [0..ofs) of the growing
394 * buffer is completely filled. */
395 static void
396 view_growbuf_read_until (WView *view, offset_type ofs)
398 ssize_t nread;
399 byte *p;
400 size_t bytesfree;
401 gboolean short_read;
403 assert (view->growbuf_in_use);
405 if (view->growbuf_finished)
406 return;
408 short_read = FALSE;
409 while (view_growbuf_filesize (view) < ofs || short_read) {
410 if (view->growbuf_lastindex == VIEW_PAGE_SIZE) {
411 /* Append a new block to the growing buffer */
412 byte *newblock = g_try_malloc (VIEW_PAGE_SIZE);
413 byte **newblocks = g_try_malloc (sizeof (*newblocks) * (view->growbuf_blocks + 1));
414 if (!newblock || !newblocks) {
415 g_free (newblock);
416 g_free (newblocks);
417 return;
419 memcpy (newblocks, view->growbuf_blockptr, sizeof (*newblocks) * view->growbuf_blocks);
420 g_free (view->growbuf_blockptr);
421 view->growbuf_blockptr = newblocks;
422 view->growbuf_blockptr[view->growbuf_blocks++] = newblock;
423 view->growbuf_lastindex = 0;
425 p = view->growbuf_blockptr[view->growbuf_blocks - 1] + view->growbuf_lastindex;
426 bytesfree = VIEW_PAGE_SIZE - view->growbuf_lastindex;
428 if (view->datasource == DS_STDIO_PIPE) {
429 nread = fread (p, 1, bytesfree, view->ds_stdio_pipe);
430 if (nread == 0) {
431 view->growbuf_finished = TRUE;
432 (void) pclose (view->ds_stdio_pipe);
433 display (view);
434 close_error_pipe (0, NULL);
435 view->ds_stdio_pipe = NULL;
436 return;
438 } else {
439 assert (view->datasource == DS_VFS_PIPE);
440 do {
441 nread = mc_read (view->ds_vfs_pipe, p, bytesfree);
442 } while (nread == -1 && errno == EINTR);
443 if (nread == -1 || nread == 0) {
444 view->growbuf_finished = TRUE;
445 (void) mc_close (view->ds_vfs_pipe);
446 view->ds_vfs_pipe = -1;
447 return;
450 short_read = ((size_t)nread < bytesfree);
451 view->growbuf_lastindex += nread;
455 static int
456 get_byte_growing_buffer (WView *view, offset_type byte_index)
458 offset_type pageno = byte_index / VIEW_PAGE_SIZE;
459 offset_type pageindex = byte_index % VIEW_PAGE_SIZE;
461 assert (view->growbuf_in_use);
463 if ((size_t) pageno != pageno)
464 return -1;
466 view_growbuf_read_until (view, byte_index + 1);
467 if (view->growbuf_blocks == 0)
468 return -1;
469 if (pageno < view->growbuf_blocks - 1)
470 return view->growbuf_blockptr[pageno][pageindex];
471 if (pageno == view->growbuf_blocks - 1 && pageindex < view->growbuf_lastindex)
472 return view->growbuf_blockptr[pageno][pageindex];
473 return -1;
476 /* {{{ Data sources }}} */
479 The data source provides the viewer with data from either a file, a
480 string or the output of a command. The get_byte() function can be
481 used to get the value of a byte at a specific offset. If the offset
482 is out of range, -1 is returned. The function get_byte_indexed(a,b)
483 returns the byte at the offset a+b, or -1 if a+b is out of range.
485 The view_set_byte() function has the effect that later calls to
486 get_byte() will return the specified byte for this offset. This
487 function is designed only for use by the hexedit component after
488 saving its changes. Inspect the source before you want to use it for
489 other purposes.
491 The view_get_filesize() function returns the current size of the
492 data source. If the growing buffer is used, this size may increase
493 later on. Use the view_may_still_grow() function when you want to
494 know if the size can change later.
497 static offset_type
498 view_get_filesize (WView *view)
500 switch (view->datasource) {
501 case DS_NONE:
502 return 0;
503 case DS_STDIO_PIPE:
504 case DS_VFS_PIPE:
505 return view_growbuf_filesize (view);
506 case DS_FILE:
507 return view->ds_file_filesize;
508 case DS_STRING:
509 return view->ds_string_len;
510 default:
511 assert(!"Unknown datasource type");
512 return 0;
516 static inline gboolean
517 view_may_still_grow (WView *view)
519 return (view->growbuf_in_use && !view->growbuf_finished);
522 /* returns TRUE if the idx lies in the half-open interval
523 * [offset; offset + size), FALSE otherwise.
525 static inline gboolean
526 already_loaded (offset_type offset, offset_type idx, size_t size)
528 return (offset <= idx && idx - offset < size);
531 static inline void
532 view_file_load_data (WView *view, offset_type byte_index)
534 offset_type blockoffset;
535 ssize_t res;
536 size_t bytes_read;
538 assert (view->datasource == DS_FILE);
540 if (already_loaded (view->ds_file_offset, byte_index, view->ds_file_datalen))
541 return;
543 blockoffset = offset_rounddown (byte_index, view->ds_file_datasize);
544 if (mc_lseek (view->ds_file_fd, blockoffset, SEEK_SET) == -1)
545 goto error;
547 bytes_read = 0;
548 while (bytes_read < view->ds_file_datasize) {
549 res = mc_read (view->ds_file_fd, view->ds_file_data + bytes_read, view->ds_file_datasize - bytes_read);
550 if (res == -1)
551 goto error;
552 if (res == 0)
553 break;
554 bytes_read += (size_t) res;
556 view->ds_file_offset = blockoffset;
557 if (bytes_read > view->ds_file_filesize - view->ds_file_offset) {
558 /* the file has grown in the meantime -- stick to the old size */
559 view->ds_file_datalen = view->ds_file_filesize - view->ds_file_offset;
560 } else {
561 view->ds_file_datalen = bytes_read;
563 return;
565 error:
566 view->ds_file_datalen = 0;
569 static int
570 get_byte_none (WView *view, offset_type byte_index)
572 assert (view->datasource == DS_NONE);
573 (void) &view;
574 (void) byte_index;
575 return -1;
578 static inline int
579 get_byte_file (WView *view, offset_type byte_index)
581 assert (view->datasource == DS_FILE);
583 view_file_load_data (view, byte_index);
584 if (already_loaded(view->ds_file_offset, byte_index, view->ds_file_datalen))
585 return view->ds_file_data[byte_index - view->ds_file_offset];
586 return -1;
589 static int
590 get_byte_string (WView *view, offset_type byte_index)
592 assert (view->datasource == DS_STRING);
593 if (byte_index < view->ds_string_len)
594 return view->ds_string_data[byte_index];
595 return -1;
598 static inline int
599 get_byte (WView *view, offset_type offset)
601 switch (view->datasource) {
602 case DS_STDIO_PIPE:
603 case DS_VFS_PIPE:
604 return get_byte_growing_buffer (view, offset);
605 case DS_FILE:
606 return get_byte_file (view, offset);
607 case DS_STRING:
608 return get_byte_string (view, offset);
609 case DS_NONE:
610 return get_byte_none (view, offset);
612 assert(!"Unknown datasource type");
613 return -1;
616 static inline int
617 get_byte_indexed (WView *view, offset_type base, offset_type ofs)
619 if (base <= OFFSETTYPE_MAX - ofs)
620 return get_byte (view, base + ofs);
621 return -1;
624 static void
625 view_set_byte (WView *view, offset_type offset, byte b)
627 (void) &b;
628 assert (offset < view_get_filesize (view));
629 assert (view->datasource == DS_FILE);
630 view->ds_file_datalen = 0; /* just force reloading */
633 static void
634 view_set_datasource_none (WView *view)
636 view->datasource = DS_NONE;
639 static void
640 view_set_datasource_vfs_pipe (WView *view, int fd)
642 assert (fd != -1);
643 view->datasource = DS_VFS_PIPE;
644 view->ds_vfs_pipe = fd;
646 view_init_growbuf (view);
649 static void
650 view_set_datasource_stdio_pipe (WView *view, FILE *fp)
652 assert (fp != NULL);
653 view->datasource = DS_STDIO_PIPE;
654 view->ds_stdio_pipe = fp;
656 view_init_growbuf (view);
659 static void
660 view_set_datasource_string (WView *view, const char *s)
662 view->datasource = DS_STRING;
663 view->ds_string_data = (byte *) g_strdup (s);
664 view->ds_string_len = strlen (s);
667 static void
668 view_set_datasource_file (WView *view, int fd, const struct stat *st)
670 view->datasource = DS_FILE;
671 view->ds_file_fd = fd;
672 view->ds_file_filesize = st->st_size;
673 view->ds_file_offset = 0;
674 view->ds_file_data = g_malloc (4096);
675 view->ds_file_datalen = 0;
676 view->ds_file_datasize = 4096;
679 static void
680 view_close_datasource (WView *view)
682 switch (view->datasource) {
683 case DS_NONE:
684 break;
685 case DS_STDIO_PIPE:
686 if (view->ds_stdio_pipe != NULL) {
687 (void) pclose (view->ds_stdio_pipe);
688 display (view);
689 close_error_pipe (0, NULL);
690 view->ds_stdio_pipe = NULL;
692 view_growbuf_free (view);
693 break;
694 case DS_VFS_PIPE:
695 if (view->ds_vfs_pipe != -1) {
696 (void) mc_close (view->ds_vfs_pipe);
697 view->ds_vfs_pipe = -1;
699 view_growbuf_free (view);
700 break;
701 case DS_FILE:
702 (void) mc_close (view->ds_file_fd);
703 view->ds_file_fd = -1;
704 g_free (view->ds_file_data);
705 view->ds_file_data = NULL;
706 break;
707 case DS_STRING:
708 g_free (view->ds_string_data);
709 view->ds_string_data = NULL;
710 break;
711 default:
712 assert (!"Unknown datasource type");
714 view->datasource = DS_NONE;
717 /* {{{ The Coordinate Cache }}} */
720 This cache provides you with a fast lookup to map file offsets into
721 line/column pairs and vice versa. The interface to the mapping is
722 provided by the functions view_coord_to_offset() and
723 view_offset_to_coord().
725 The cache is implemented as a simple sorted array holding entries
726 that map some of the offsets to their line/column pair. Entries that
727 are not cached themselves are interpolated (exactly) from their
728 neighbor entries. The algorithm used for determining the line/column
729 for a specific offset needs to be kept synchronized with the one used
730 in display().
733 enum ccache_type {
734 CCACHE_OFFSET,
735 CCACHE_LINECOL
738 static inline gboolean
739 coord_cache_entry_less (const struct coord_cache_entry *a,
740 const struct coord_cache_entry *b, enum ccache_type crit,
741 gboolean nroff_mode)
743 if (crit == CCACHE_OFFSET)
744 return (a->cc_offset < b->cc_offset);
746 if (a->cc_line < b->cc_line)
747 return TRUE;
749 if (a->cc_line == b->cc_line) {
750 if (nroff_mode) {
751 return (a->cc_nroff_column < b->cc_nroff_column);
752 } else {
753 return (a->cc_column < b->cc_column);
756 return FALSE;
759 #ifdef MC_ENABLE_DEBUGGING_CODE
760 static void view_coord_to_offset (WView *, offset_type *, offset_type, offset_type);
761 static void view_offset_to_coord (WView *, offset_type *, offset_type *, offset_type);
763 static void
764 view_ccache_dump (WView *view)
766 FILE *f;
767 offset_type offset, line, column, nextline_offset, filesize;
768 guint i;
769 const struct coord_cache_entry *cache;
771 assert (view->coord_cache != NULL);
773 filesize = view_get_filesize (view);
774 cache = &(g_array_index (view->coord_cache, struct coord_cache_entry, 0));
776 f = fopen("mcview-ccache.out", "w");
777 if (f == NULL)
778 return;
779 (void)setvbuf(f, NULL, _IONBF, 0);
781 /* cache entries */
782 for (i = 0; i < view->coord_cache->len; i++) {
783 (void) fprintf (f,
784 "entry %8u "
785 "offset %8"OFFSETTYPE_PRId" "
786 "line %8"OFFSETTYPE_PRId" "
787 "column %8"OFFSETTYPE_PRId" "
788 "nroff_column %8"OFFSETTYPE_PRId"\n",
789 (unsigned int) i, cache[i].cc_offset, cache[i].cc_line,
790 cache[i].cc_column, cache[i].cc_nroff_column);
792 (void)fprintf (f, "\n");
794 /* offset -> line/column translation */
795 for (offset = 0; offset < filesize; offset++) {
796 view_offset_to_coord (view, &line, &column, offset);
797 (void)fprintf (f,
798 "offset %8"OFFSETTYPE_PRId" "
799 "line %8"OFFSETTYPE_PRId" "
800 "column %8"OFFSETTYPE_PRId"\n",
801 offset, line, column);
804 /* line/column -> offset translation */
805 for (line = 0; TRUE; line++) {
806 view_coord_to_offset (view, &nextline_offset, line + 1, 0);
807 (void)fprintf (f, "nextline_offset %8"OFFSETTYPE_PRId"\n",
808 nextline_offset);
810 for (column = 0; TRUE; column++) {
811 view_coord_to_offset (view, &offset, line, column);
812 if (offset >= nextline_offset)
813 break;
815 (void)fprintf (f, "line %8"OFFSETTYPE_PRId" column %8"OFFSETTYPE_PRId" offset %8"OFFSETTYPE_PRId"\n",
816 line, column, offset);
819 if (nextline_offset >= filesize - 1)
820 break;
823 (void)fclose (f);
825 #endif
827 static inline gboolean
828 is_nroff_sequence (WView *view, offset_type offset)
830 int c0, c1, c2;
832 /* The following commands are ordered to speed up the calculation. */
834 c1 = get_byte_indexed (view, offset, 1);
835 if (c1 == -1 || c1 != '\b')
836 return FALSE;
838 c0 = get_byte_indexed (view, offset, 0);
839 if (c0 == -1 || !is_printable(c0))
840 return FALSE;
842 c2 = get_byte_indexed (view, offset, 2);
843 if (c2 == -1 || !is_printable(c2))
844 return FALSE;
846 return (c0 == c2 || c0 == '_' || (c0 == '+' && c2 == 'o'));
849 /* Find and return the index of the last cache entry that is
850 * smaller than ''coord'', according to the criterion ''sort_by''. */
851 static inline guint
852 view_ccache_find (WView *view, const struct coord_cache_entry *cache,
853 const struct coord_cache_entry *coord, enum ccache_type sort_by)
855 guint base, i, limit;
857 limit = view->coord_cache->len;
858 assert (limit != 0);
860 base = 0;
861 while (limit > 1) {
862 i = base + limit / 2;
863 if (coord_cache_entry_less (coord, &cache[i], sort_by, view->text_nroff_mode)) {
864 /* continue the search in the lower half of the cache */
865 } else {
866 /* continue the search in the upper half of the cache */
867 base = i;
869 limit = (limit + 1) / 2;
871 return base;
874 /* Look up the missing components of ''coord'', which are given by
875 * ''lookup_what''. The function returns the smallest value that
876 * matches the existing components of ''coord''.
878 static void
879 view_ccache_lookup (WView *view, struct coord_cache_entry *coord,
880 enum ccache_type lookup_what)
882 guint i;
883 struct coord_cache_entry *cache, current, next, entry;
884 enum ccache_type sorter;
885 offset_type limit;
886 enum {
887 NROFF_START,
888 NROFF_BACKSPACE,
889 NROFF_CONTINUATION
890 } nroff_state;
892 if (!view->coord_cache) {
893 view->coord_cache = g_array_new (FALSE, FALSE, sizeof(struct coord_cache_entry));
894 current.cc_offset = 0;
895 current.cc_line = 0;
896 current.cc_column = 0;
897 current.cc_nroff_column = 0;
898 g_array_append_val (view->coord_cache, current);
901 sorter = (lookup_what == CCACHE_OFFSET) ? CCACHE_LINECOL : CCACHE_OFFSET;
903 retry:
904 /* find the two neighbor entries in the cache */
905 cache = &(g_array_index (view->coord_cache, struct coord_cache_entry, 0));
906 i = view_ccache_find (view, cache, coord, sorter);
907 /* now i points to the lower neighbor in the cache */
909 current = cache[i];
910 if (i + 1 < view->coord_cache->len)
911 limit = cache[i + 1].cc_offset;
912 else
913 limit = current.cc_offset + VIEW_COORD_CACHE_GRANUL;
915 entry = current;
916 nroff_state = NROFF_START;
917 for (; current.cc_offset < limit; current = next) {
918 int c;
920 if ((c = get_byte (view, current.cc_offset)) == -1)
921 break;
923 if (!coord_cache_entry_less (&current, coord, sorter, view->text_nroff_mode)) {
924 if (lookup_what == CCACHE_OFFSET
925 && view->text_nroff_mode
926 && nroff_state != NROFF_START) {
927 /* don't break here */
928 } else {
929 break;
933 /* Provide useful default values for ''next'' */
934 next.cc_offset = current.cc_offset + 1;
935 next.cc_line = current.cc_line;
936 next.cc_column = current.cc_column + 1;
937 next.cc_nroff_column = current.cc_nroff_column + 1;
939 /* and override some of them as necessary. */
940 if (c == '\r') {
941 next.cc_column = current.cc_column;
942 next.cc_nroff_column = current.cc_nroff_column;
944 } else if (nroff_state == NROFF_BACKSPACE) {
945 next.cc_nroff_column = current.cc_nroff_column - 1;
947 } else if (c == '\t') {
948 next.cc_column = offset_rounddown (current.cc_column, 8) + 8;
949 next.cc_nroff_column =
950 offset_rounddown (current.cc_nroff_column, 8) + 8;
952 } else if (c == '\n') {
953 next.cc_line = current.cc_line + 1;
954 next.cc_column = 0;
955 next.cc_nroff_column = 0;
957 } else {
958 /* Use all default values from above */
961 switch (nroff_state) {
962 case NROFF_START:
963 case NROFF_CONTINUATION:
964 if (is_nroff_sequence (view, current.cc_offset))
965 nroff_state = NROFF_BACKSPACE;
966 else
967 nroff_state = NROFF_START;
968 break;
969 case NROFF_BACKSPACE:
970 nroff_state = NROFF_CONTINUATION;
971 break;
974 /* Cache entries must guarantee that for each i < j,
975 * line[i] <= line[j] and column[i] < column[j]. In the case of
976 * nroff sequences and '\r' characters, this is not guaranteed,
977 * so we cannot save them. */
978 if (nroff_state == NROFF_START && c != '\r')
979 entry = next;
982 if (i + 1 == view->coord_cache->len && entry.cc_offset != cache[i].cc_offset) {
983 g_array_append_val (view->coord_cache, entry);
984 goto retry;
987 if (lookup_what == CCACHE_OFFSET) {
988 coord->cc_offset = current.cc_offset;
989 } else {
990 coord->cc_line = current.cc_line;
991 coord->cc_column = current.cc_column;
992 coord->cc_nroff_column = current.cc_nroff_column;
996 static void
997 view_coord_to_offset (WView *view, offset_type *ret_offset,
998 offset_type line, offset_type column)
1000 struct coord_cache_entry coord;
1002 coord.cc_line = line;
1003 coord.cc_column = column;
1004 coord.cc_nroff_column = column;
1005 view_ccache_lookup (view, &coord, CCACHE_OFFSET);
1006 *ret_offset = coord.cc_offset;
1009 static void
1010 view_offset_to_coord (WView *view, offset_type *ret_line,
1011 offset_type *ret_column, offset_type offset)
1013 struct coord_cache_entry coord;
1015 coord.cc_offset = offset;
1016 view_ccache_lookup (view, &coord, CCACHE_LINECOL);
1017 *ret_line = coord.cc_line;
1018 *ret_column = (view->text_nroff_mode)
1019 ? coord.cc_nroff_column
1020 : coord.cc_column;
1023 /* {{{ Cursor Movement }}} */
1026 The following variables have to do with the current position and are
1027 updated by the cursor movement functions.
1029 In hex view and wrapped text view mode, dpy_start marks the offset of
1030 the top-left corner on the screen, in non-wrapping text mode it is
1031 the beginning of the current line. In hex mode, hex_cursor is the
1032 offset of the cursor. In non-wrapping text mode, dpy_text_column is
1033 the number of columns that are hidden on the left side on the screen.
1035 In hex mode, dpy_start is updated by the view_fix_cursor_position()
1036 function in order to keep the other functions simple. In
1037 non-wrapping text mode dpy_start and dpy_text_column are normalized
1038 such that dpy_text_column < view_get_datacolumns().
1041 /* prototypes for functions used by view_moveto_bottom() */
1042 static void view_move_up (WView *, offset_type);
1043 static void view_moveto_bol (WView *);
1045 static void
1046 view_scroll_to_cursor (WView *view)
1048 if (view->hex_mode) {
1049 const offset_type bytes = view->bytes_per_line;
1050 const offset_type displaysize = view->data_area.height * bytes;
1051 const offset_type cursor = view->hex_cursor;
1052 offset_type topleft = view->dpy_start;
1054 if (topleft + displaysize <= cursor)
1055 topleft = offset_rounddown (cursor, bytes)
1056 - (displaysize - bytes);
1057 if (cursor < topleft)
1058 topleft = offset_rounddown (cursor, bytes);
1059 view->dpy_start = topleft;
1060 } else if (view->text_wrap_mode) {
1061 offset_type line, col, columns;
1063 columns = view->data_area.width;
1064 view_offset_to_coord (view, &line, &col, view->dpy_start + view->dpy_text_column);
1065 if (columns != 0)
1066 col = offset_rounddown (col, columns);
1067 view_coord_to_offset (view, &(view->dpy_start), line, col);
1068 view->dpy_text_column = 0;
1069 } else {
1070 /* nothing to do */
1074 static void
1075 view_movement_fixups (WView *view, gboolean reset_search)
1077 view_scroll_to_cursor (view);
1078 if (reset_search) {
1079 view->search_start = view->dpy_start;
1080 view->search_length = 0;
1082 view->dirty++;
1085 static void
1086 view_moveto_top (WView *view)
1088 view->dpy_start = 0;
1089 view->hex_cursor = 0;
1090 view->dpy_text_column = 0;
1091 view_movement_fixups (view, TRUE);
1094 static void
1095 view_moveto_bottom (WView *view)
1097 offset_type datalines, lines_up, filesize, last_offset;
1099 if (view->growbuf_in_use)
1100 view_growbuf_read_until (view, OFFSETTYPE_MAX);
1102 filesize = view_get_filesize (view);
1103 last_offset = offset_doz(filesize, 1);
1104 datalines = view->data_area.height;
1105 lines_up = offset_doz(datalines, 1);
1107 if (view->hex_mode) {
1108 view->hex_cursor = filesize;
1109 view_move_up (view, lines_up);
1110 view->hex_cursor = last_offset;
1111 } else {
1112 view->dpy_start = last_offset;
1113 view_moveto_bol (view);
1114 view_move_up (view, lines_up);
1116 view_movement_fixups (view, TRUE);
1119 static void
1120 view_moveto_bol (WView *view)
1122 if (view->hex_mode) {
1123 view->hex_cursor -= view->hex_cursor % view->bytes_per_line;
1124 } else if (view->text_wrap_mode) {
1125 /* do nothing */
1126 } else {
1127 offset_type line, column;
1128 view_offset_to_coord (view, &line, &column, view->dpy_start);
1129 view_coord_to_offset (view, &(view->dpy_start), line, 0);
1130 view->dpy_text_column = 0;
1132 view_movement_fixups (view, TRUE);
1135 static void
1136 view_moveto_eol (WView *view)
1138 if (view->hex_mode) {
1139 offset_type filesize, bol;
1141 bol = offset_rounddown (view->hex_cursor, view->bytes_per_line);
1142 if (get_byte_indexed (view, bol, view->bytes_per_line - 1) != -1) {
1143 view->hex_cursor = bol + view->bytes_per_line - 1;
1144 } else {
1145 filesize = view_get_filesize (view);
1146 view->hex_cursor = offset_doz(filesize, 1);
1148 } else if (view->text_wrap_mode) {
1149 /* nothing to do */
1150 } else {
1151 offset_type line, col;
1153 view_offset_to_coord (view, &line, &col, view->dpy_start);
1154 view_coord_to_offset (view, &(view->dpy_start), line, OFFSETTYPE_MAX);
1156 view_movement_fixups (view, FALSE);
1159 static void
1160 view_moveto_offset (WView *view, offset_type offset)
1162 if (view->hex_mode) {
1163 view->hex_cursor = offset;
1164 view->dpy_start = offset - offset % view->bytes_per_line;
1165 } else {
1166 view->dpy_start = offset;
1168 view_movement_fixups (view, TRUE);
1171 static void
1172 view_moveto (WView *view, offset_type line, offset_type col)
1174 offset_type offset;
1176 view_coord_to_offset (view, &offset, line, col);
1177 view_moveto_offset (view, offset);
1180 static void
1181 view_move_up (WView *view, offset_type lines)
1183 if (view->hex_mode) {
1184 offset_type bytes = lines * view->bytes_per_line;
1185 if (view->hex_cursor >= bytes) {
1186 view->hex_cursor -= bytes;
1187 if (view->hex_cursor < view->dpy_start)
1188 view->dpy_start = offset_doz (view->dpy_start, bytes);
1189 } else {
1190 view->hex_cursor %= view->bytes_per_line;
1192 } else if (view->text_wrap_mode) {
1193 const screen_dimen width = view->data_area.width;
1194 offset_type i, col, line, linestart;
1196 for (i = 0; i < lines; i++) {
1197 view_offset_to_coord (view, &line, &col, view->dpy_start);
1198 if (col >= width) {
1199 col -= width;
1200 } else if (line >= 1) {
1201 view_coord_to_offset (view, &linestart, line, 0);
1202 view_offset_to_coord (view, &line, &col, linestart - 1);
1204 /* if the only thing that would be displayed were a
1205 * single newline character, advance to the previous
1206 * part of the line. */
1207 if (col > 0 && col % width == 0)
1208 col -= width;
1209 else
1210 col -= col % width;
1211 } else {
1212 /* nothing to do */
1214 view_coord_to_offset (view, &(view->dpy_start), line, col);
1216 } else {
1217 offset_type line, column;
1219 view_offset_to_coord (view, &line, &column, view->dpy_start);
1220 line = offset_doz(line, lines);
1221 view_coord_to_offset (view, &(view->dpy_start), line, column);
1223 view_movement_fixups (view, (lines != 1));
1226 static void
1227 view_move_down (WView *view, offset_type lines)
1229 if (view->hex_mode) {
1230 offset_type i, limit, last_byte;
1232 last_byte = view_get_filesize (view);
1233 if (last_byte >= (offset_type) view->bytes_per_line)
1234 limit = last_byte - view->bytes_per_line;
1235 else
1236 limit = 0;
1237 for (i = 0; i < lines && view->hex_cursor < limit; i++) {
1238 view->hex_cursor += view->bytes_per_line;
1239 if (lines != 1)
1240 view->dpy_start += view->bytes_per_line;
1243 } else if (view->dpy_end == view_get_filesize (view)) {
1244 /* don't move further down. There's nothing more to see. */
1246 } else if (view->text_wrap_mode) {
1247 offset_type line, col, i;
1249 for (i = 0; i < lines; i++) {
1250 offset_type new_offset, chk_line, chk_col;
1252 view_offset_to_coord (view, &line, &col, view->dpy_start);
1253 col += view->data_area.width;
1254 view_coord_to_offset (view, &new_offset, line, col);
1256 /* skip to the next line if the only thing that would be
1257 * displayed is the newline character. */
1258 view_offset_to_coord (view, &chk_line, &chk_col, new_offset);
1259 if (chk_line == line && chk_col == col
1260 && get_byte (view, new_offset) == '\n')
1261 new_offset++;
1263 view->dpy_start = new_offset;
1266 } else {
1267 offset_type line, col;
1269 view_offset_to_coord (view, &line, &col, view->dpy_start);
1270 line += lines;
1271 view_coord_to_offset (view, &(view->dpy_start), line, col);
1273 view_movement_fixups (view, (lines != 1));
1276 static void
1277 view_move_left (WView *view, offset_type columns)
1279 if (view->hex_mode) {
1280 assert (columns == 1);
1281 if (view->hexview_in_text || !view->hexedit_lownibble) {
1282 if (view->hex_cursor > 0)
1283 view->hex_cursor--;
1285 if (!view->hexview_in_text)
1286 view->hexedit_lownibble = !view->hexedit_lownibble;
1287 } else if (view->text_wrap_mode) {
1288 /* nothing to do */
1289 } else {
1290 if (view->dpy_text_column >= columns)
1291 view->dpy_text_column -= columns;
1292 else
1293 view->dpy_text_column = 0;
1295 view_movement_fixups (view, FALSE);
1298 static void
1299 view_move_right (WView *view, offset_type columns)
1301 if (view->hex_mode) {
1302 assert (columns == 1);
1303 if (view->hexview_in_text || view->hexedit_lownibble) {
1304 if (get_byte_indexed (view, view->hex_cursor, 1) != -1)
1305 view->hex_cursor++;
1307 if (!view->hexview_in_text)
1308 view->hexedit_lownibble = !view->hexedit_lownibble;
1309 } else if (view->text_wrap_mode) {
1310 /* nothing to do */
1311 } else {
1312 view->dpy_text_column += columns;
1314 view_movement_fixups (view, FALSE);
1317 /* {{{ Toggling of viewer modes }}} */
1319 static void
1320 view_toggle_hex_mode (WView *view)
1322 view->hex_mode = !view->hex_mode;
1324 if (view->hex_mode) {
1325 view->hex_cursor = view->dpy_start;
1326 view->dpy_start =
1327 offset_rounddown (view->dpy_start, view->bytes_per_line);
1328 view->widget.options |= W_WANT_CURSOR;
1329 } else {
1330 view->dpy_start = view->hex_cursor;
1331 view_moveto_bol (view);
1332 view->widget.options &= ~W_WANT_CURSOR;
1334 altered_hex_mode = 1;
1335 view->dpy_bbar_dirty = TRUE;
1336 view->dirty++;
1339 static void
1340 view_toggle_hexedit_mode (WView *view)
1342 view->hexedit_mode = !view->hexedit_mode;
1343 view->dpy_bbar_dirty = TRUE;
1344 view->dirty++;
1347 static void
1348 view_toggle_wrap_mode (WView *view)
1350 view->text_wrap_mode = !view->text_wrap_mode;
1351 if (view->text_wrap_mode) {
1352 view_scroll_to_cursor (view);
1353 } else {
1354 offset_type line;
1356 view_offset_to_coord (view, &line, &(view->dpy_text_column), view->dpy_start);
1357 view_coord_to_offset (view, &(view->dpy_start), line, 0);
1359 view->dpy_bbar_dirty = TRUE;
1360 view->dirty++;
1363 static void
1364 view_toggle_nroff_mode (WView *view)
1366 view->text_nroff_mode = !view->text_nroff_mode;
1367 altered_nroff_flag = 1;
1368 view->dpy_bbar_dirty = TRUE;
1369 view->dirty++;
1372 static void
1373 view_toggle_magic_mode (WView *view)
1375 char *filename, *command;
1377 altered_magic_flag = 1;
1378 view->magic_mode = !view->magic_mode;
1379 filename = g_strdup (view->filename);
1380 command = g_strdup (view->command);
1382 view_done (view);
1383 view_load (view, command, filename, 0);
1384 g_free (filename);
1385 g_free (command);
1386 view->dpy_bbar_dirty = TRUE;
1387 view->dirty++;
1390 /* {{{ Miscellaneous functions }}} */
1392 static void
1393 view_done (WView *view)
1395 /* Save current file position */
1396 if (mcview_remember_file_position && view->filename != NULL) {
1397 char *canon_fname;
1398 offset_type line, col;
1400 canon_fname = vfs_canon (view->filename);
1401 view_offset_to_coord (view, &line, &col, view->dpy_start);
1402 save_file_position (canon_fname, line + 1, col);
1403 g_free (canon_fname);
1406 /* Write back the global viewer mode */
1407 default_hex_mode = view->hex_mode;
1408 default_nroff_flag = view->text_nroff_mode;
1409 default_magic_flag = view->magic_mode;
1410 global_wrap_mode = view->text_wrap_mode;
1412 /* Free memory used by the viewer */
1414 /* view->widget needs no destructor */
1416 g_free (view->filename), view->filename = NULL;
1417 g_free (view->command), view->command = NULL;
1419 view_close_datasource (view);
1420 /* the growing buffer is freed with the datasource */
1422 if (view->coord_cache) {
1423 g_array_free (view->coord_cache, TRUE), view->coord_cache = NULL;
1426 view_hexedit_free_change_list (view);
1427 /* FIXME: what about view->search_exp? */
1430 static void
1431 view_show_error (WView *view, const char *msg)
1433 view_close_datasource (view);
1434 if (view_is_in_panel (view)) {
1435 view_set_datasource_string (view, msg);
1436 } else {
1437 message (1, MSG_ERROR, "%s", msg);
1441 static gboolean
1442 view_load_command_output (WView *view, const char *command)
1444 FILE *fp;
1446 view_close_datasource (view);
1448 open_error_pipe ();
1449 if ((fp = popen (command, "r")) == NULL) {
1450 /* Avoid two messages. Message from stderr has priority. */
1451 display (view);
1452 if (!close_error_pipe (view_is_in_panel (view) ? -1 : 1, NULL))
1453 view_show_error (view, _(" Cannot spawn child process "));
1454 return FALSE;
1457 /* First, check if filter produced any output */
1458 view_set_datasource_stdio_pipe (view, fp);
1459 if (get_byte (view, 0) == -1) {
1460 view_close_datasource (view);
1462 /* Avoid two messages. Message from stderr has priority. */
1463 display (view);
1464 if (!close_error_pipe (view_is_in_panel (view) ? -1 : 1, NULL))
1465 view_show_error (view, _("Empty output from child filter"));
1466 return FALSE;
1468 return TRUE;
1471 gboolean
1472 view_load (WView *view, const char *command, const char *file,
1473 int start_line)
1475 int i, type;
1476 int fd = -1;
1477 char tmp[BUF_MEDIUM];
1478 struct stat st;
1479 gboolean retval = FALSE;
1481 assert (view->bytes_per_line != 0);
1482 view_done (view);
1484 /* Set up the state */
1485 view_set_datasource_none (view);
1486 view->filename = g_strdup (file);
1487 view->command = 0;
1489 /* Clear the markers */
1490 view->marker = 0;
1491 for (i = 0; i < 10; i++)
1492 view->marks[i] = 0;
1494 if (!view_is_in_panel (view)) {
1495 view->dpy_text_column = 0;
1498 if (command && (view->magic_mode || file == NULL || file[0] == '\0')) {
1499 retval = view_load_command_output (view, command);
1500 } else if (file != NULL && file[0] != '\0') {
1501 /* Open the file */
1502 if ((fd = mc_open (file, O_RDONLY | O_NONBLOCK)) == -1) {
1503 g_snprintf (tmp, sizeof (tmp), _(" Cannot open \"%s\"\n %s "),
1504 file, unix_error_string (errno));
1505 view_show_error (view, tmp);
1506 goto finish;
1509 /* Make sure we are working with a regular file */
1510 if (mc_fstat (fd, &st) == -1) {
1511 mc_close (fd);
1512 g_snprintf (tmp, sizeof (tmp), _(" Cannot stat \"%s\"\n %s "),
1513 file, unix_error_string (errno));
1514 view_show_error (view, tmp);
1515 goto finish;
1518 if (!S_ISREG (st.st_mode)) {
1519 mc_close (fd);
1520 view_show_error (view, _(" Cannot view: not a regular file "));
1521 goto finish;
1524 if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1) {
1525 /* Must be one of those nice files that grow (/proc) */
1526 view_set_datasource_vfs_pipe (view, fd);
1527 } else {
1528 type = get_compression_type (fd);
1530 if (view->magic_mode && (type != COMPRESSION_NONE)) {
1531 g_free (view->filename);
1532 view->filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
1534 view_set_datasource_file (view, fd, &st);
1536 retval = TRUE;
1539 finish:
1540 view->command = g_strdup (command);
1541 view->dpy_start = 0;
1542 view->search_start = 0;
1543 view->search_length = 0;
1544 view->dpy_text_column = 0;
1545 view->last_search = 0; /* Start a new search */
1547 assert (view->bytes_per_line != 0);
1548 if (mcview_remember_file_position && file != NULL && start_line == 0) {
1549 long line, col;
1550 char *canon_fname;
1552 canon_fname = vfs_canon (file);
1553 load_file_position (file, &line, &col);
1554 g_free (canon_fname);
1555 view_moveto (view, offset_doz(line, 1), col);
1556 } else if (start_line > 0) {
1557 view_moveto (view, start_line - 1, 0);
1560 view->hexedit_lownibble = FALSE;
1561 view->hexview_in_text = FALSE;
1562 view->change_list = NULL;
1564 return retval;
1567 /* {{{ Display management }}} */
1569 static void
1570 view_update_bytes_per_line (WView *view)
1572 const screen_dimen cols = view->data_area.width;
1573 int bytes;
1575 if (cols < 8)
1576 bytes = 1;
1577 else if (cols < 80)
1578 bytes = ((cols - 8) / 17) * 4;
1579 else
1580 bytes = ((cols - 8) / 18) * 4;
1582 if (bytes == 0)
1583 bytes = 1; /* To avoid division by 0 */
1585 view->bytes_per_line = bytes;
1586 view->dirty = max_dirt_limit + 1; /* To force refresh */
1589 static void
1590 view_percent (WView *view, offset_type p)
1592 const screen_dimen top = view->status_area.top;
1593 const screen_dimen right = view->status_area.left + view->status_area.width;
1594 const screen_dimen height = view->status_area.height;
1595 int percent;
1596 offset_type filesize;
1598 if (height < 1 || right < 4)
1599 return;
1600 if (view_may_still_grow (view))
1601 return;
1602 filesize = view_get_filesize (view);
1604 if (filesize == 0 || view->dpy_end == filesize)
1605 percent = 100;
1606 else if (p > (INT_MAX / 100))
1607 percent = p / (filesize / 100);
1608 else
1609 percent = p * 100 / filesize;
1611 widget_move (view, top, right - 4);
1612 printw (str_unconst ("%3d%%"), percent);
1615 static void
1616 view_display_status (WView *view)
1618 const screen_dimen top = view->status_area.top;
1619 const screen_dimen left = view->status_area.left;
1620 const screen_dimen width = view->status_area.width;
1621 const screen_dimen height = view->status_area.height;
1622 const char *file_label, *file_name;
1623 screen_dimen file_label_width;
1624 int i;
1626 if (height < 1)
1627 return;
1629 attrset (SELECTED_COLOR);
1630 widget_move (view, top, left);
1631 hline (' ', width);
1633 file_label = _("File: %s");
1634 file_label_width = strlen (file_label) - 2;
1635 file_name = view->filename ? view->filename
1636 : view->command ? view->command
1637 : "";
1639 if (width < file_label_width + 6)
1640 addstr ((char *) name_trunc (file_name, width));
1641 else {
1642 i = (width > 22 ? 22 : width) - file_label_width;
1643 printw (str_unconst (file_label), name_trunc (file_name, i));
1644 if (width > 46) {
1645 widget_move (view, top, left + 24);
1646 /* FIXME: the format strings need to be changed when offset_type changes */
1647 if (view->hex_mode)
1648 printw (str_unconst (_("Offset 0x%08lx")), view->hex_cursor);
1649 else {
1650 offset_type line, col;
1651 view_offset_to_coord (view, &line, &col, view->dpy_start);
1652 printw (str_unconst (_("Line %lu Col %lu")),
1653 (unsigned long) line + 1,
1654 (unsigned long) (view->text_wrap_mode ? col : view->dpy_text_column));
1657 if (width > 62) {
1658 offset_type filesize;
1659 filesize = view_get_filesize (view);
1660 widget_move (view, top, left + 43);
1661 if (!view_may_still_grow (view)) {
1662 printw (str_unconst (_("%s bytes")), size_trunc (filesize));
1663 } else {
1664 printw (str_unconst (_(">= %s bytes")), size_trunc (filesize));
1667 if (width > 26) {
1668 view_percent (view, view->hex_mode
1669 ? view->hex_cursor
1670 : view->dpy_end);
1673 attrset (SELECTED_COLOR);
1676 static inline void
1677 view_display_clean (WView *view)
1679 attrset (NORMAL_COLOR);
1680 widget_erase ((Widget *) view);
1681 if (view->dpy_frame_size != 0) {
1682 draw_double_box (view->widget.parent, view->widget.y,
1683 view->widget.x, view->widget.lines,
1684 view->widget.cols);
1688 typedef enum {
1689 MARK_NORMAL,
1690 MARK_SELECTED,
1691 MARK_CURSOR,
1692 MARK_CHANGED
1693 } mark_t;
1695 static inline int
1696 view_count_backspaces (WView *view, off_t offset)
1698 int backspaces = 0;
1699 while (offset >= 2 * backspaces
1700 && get_byte (view, offset - 2 * backspaces) == '\b')
1701 backspaces++;
1702 return backspaces;
1705 static void
1706 view_display_ruler (WView *view)
1708 static const char ruler_chars[] = "|----*----";
1709 const screen_dimen top = view->ruler_area.top;
1710 const screen_dimen left = view->ruler_area.left;
1711 const screen_dimen width = view->ruler_area.width;
1712 const screen_dimen height = view->ruler_area.height;
1713 const screen_dimen line_row = (ruler == RULER_TOP) ? 0 : 1;
1714 const screen_dimen nums_row = (ruler == RULER_TOP) ? 1 : 0;
1716 char r_buff[10];
1717 offset_type cl;
1718 screen_dimen c;
1720 if (ruler == RULER_NONE || height < 1)
1721 return;
1723 attrset (MARKED_COLOR);
1724 for (c = 0; c < width; c++) {
1725 cl = view->dpy_text_column + c;
1726 if (line_row < height) {
1727 widget_move (view, top + line_row, left + c);
1728 tty_print_char (ruler_chars[cl % 10]);
1731 if ((cl != 0) && (cl % 10) == 0) {
1732 g_snprintf (r_buff, sizeof (r_buff), "%"OFFSETTYPE_PRId, cl);
1733 if (nums_row < height) {
1734 widget_move (view, top + nums_row, left + c - 1);
1735 tty_print_string (r_buff);
1739 attrset (NORMAL_COLOR);
1742 static void
1743 view_display_hex (WView *view)
1744 /* FIXME: prevent any screen overflows */
1746 const screen_dimen top = view->data_area.top;
1747 const screen_dimen left = view->data_area.left;
1748 const screen_dimen height = view->data_area.height;
1749 const screen_dimen width = view->data_area.width;
1750 const screen_dimen text_start = width - view->bytes_per_line;
1751 const screen_dimen hex_start = 9;
1753 screen_dimen row, col;
1754 offset_type from;
1755 int c;
1756 mark_t boldflag = MARK_NORMAL;
1757 struct hexedit_change_node *curr = view->change_list;
1759 char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */
1760 int bytes; /* Number of bytes already printed on the line */
1762 view_display_clean (view);
1764 /* Find the first displayable changed byte */
1765 from = view->dpy_start;
1766 while (curr && (curr->offset < from)) {
1767 curr = curr->next;
1770 for (row = 0; get_byte (view, from) != -1 && row < height; row++) {
1771 /* Print the hex offset */
1772 attrset (MARKED_COLOR);
1773 g_snprintf (hex_buff, sizeof (hex_buff), "%08"OFFSETTYPE_PRIX, from);
1774 widget_move (view, top + row, left);
1775 tty_print_string (hex_buff);
1776 attrset (NORMAL_COLOR);
1778 col = hex_start;
1779 for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++) {
1781 if ((c = get_byte (view, from)) == -1)
1782 break;
1784 /* Save the cursor position for view_place_cursor() */
1785 if (from == view->hex_cursor && !view->hexview_in_text) {
1786 view->cursor_row = row;
1787 view->cursor_col = col;
1790 /* Determine the state of the current byte */
1792 /* The current cursor position */
1793 if (from == view->hex_cursor) {
1794 boldflag = MARK_CURSOR;
1796 /* Changed bytes from the hex editor */
1797 } else if (curr && from == curr->offset) {
1798 c = curr->value;
1799 curr = curr->next;
1800 boldflag = MARK_CHANGED;
1802 /* Marked bytes from the search functions */
1803 } else if (view->search_start <= from
1804 && from < view->search_start + view->search_length) {
1805 boldflag = MARK_SELECTED;
1807 } else {
1808 boldflag = MARK_NORMAL;
1811 /* Select the color for the hex number */
1812 attrset (
1813 boldflag == MARK_NORMAL ? NORMAL_COLOR :
1814 boldflag == MARK_SELECTED ? MARKED_COLOR :
1815 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
1816 /* boldflag == MARK_CURSOR */
1817 view->hexview_in_text ? MARKED_SELECTED_COLOR :
1818 VIEW_UNDERLINED_COLOR);
1820 /* Print the hex number */
1821 widget_move (view, top + row, left + col);
1822 tty_print_char (hex_char[c / 16]);
1823 tty_print_char (hex_char[c % 16]);
1824 col += 2;
1826 /* Print the separator */
1827 attrset (NORMAL_COLOR);
1828 if (bytes != view->bytes_per_line - 1) {
1829 tty_print_char (' ');
1830 col += 1;
1832 /* After every four bytes, print a group separator */
1833 if (bytes % 4 == 3) {
1834 if (view->data_area.width >= 80) {
1835 tty_print_one_vline ();
1836 col += 1;
1838 tty_print_char (' ');
1839 col += 1;
1843 /* Select the color for the character; this differs from the
1844 * hex color when boldflag == MARK_CURSOR */
1845 attrset (
1846 boldflag == MARK_NORMAL ? NORMAL_COLOR :
1847 boldflag == MARK_SELECTED ? MARKED_COLOR :
1848 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
1849 /* boldflag == MARK_CURSOR */
1850 view->hexview_in_text ? VIEW_UNDERLINED_COLOR :
1851 MARKED_SELECTED_COLOR);
1853 c = convert_to_display_c (c);
1854 if (!is_printable (c))
1855 c = '.';
1857 /* Print corresponding character on the text side */
1858 widget_move (view, top + row, left + text_start + bytes);
1859 tty_print_char (c);
1861 /* Save the cursor position for view_place_cursor() */
1862 if (from == view->hex_cursor && view->hexview_in_text) {
1863 view->cursor_row = row;
1864 view->cursor_col = text_start + bytes;
1869 /* Be polite to the other functions */
1870 attrset (NORMAL_COLOR);
1872 view_place_cursor (view);
1873 view->dpy_end = from;
1876 static void
1877 view_display_text (WView * view)
1879 const screen_dimen left = view->data_area.left;
1880 const screen_dimen top = view->data_area.top;
1881 const screen_dimen width = view->data_area.width;
1882 const screen_dimen height = view->data_area.height;
1883 screen_dimen row, col;
1884 offset_type from;
1885 int c;
1886 struct hexedit_change_node *curr = view->change_list;
1888 view_display_clean (view);
1889 view_display_ruler (view);
1891 /* Find the first displayable changed byte */
1892 from = view->dpy_start;
1893 while (curr && (curr->offset < from)) {
1894 curr = curr->next;
1897 attrset (NORMAL_COLOR);
1898 for (row = 0, col = 0; row < height && (c = get_byte (view, from)) != -1; from++) {
1900 if (view->text_nroff_mode && c == '\b') {
1901 int c_prev;
1902 int c_next;
1904 if ((c_next = get_byte_indexed (view, from, 1)) != -1
1905 && is_printable (c_next)
1906 && from >= 1
1907 && (c_prev = get_byte (view, from - 1)) != -1
1908 && is_printable (c_prev)
1909 && (c_prev == c_next || c_prev == '_'
1910 || (c_prev == '+' && c_next == 'o'))) {
1911 if (col == 0) {
1912 if (row == 0) {
1913 /* We're inside an nroff character sequence at the
1914 * beginning of the screen -- just skip the
1915 * backspace and continue with the next character. */
1916 continue;
1918 row--;
1919 col = width;
1921 col--;
1922 if (c_prev == '_' && (c_next != '_' || view_count_backspaces (view, from) == 1))
1923 attrset (VIEW_UNDERLINED_COLOR);
1924 else
1925 attrset (MARKED_COLOR);
1926 continue;
1930 if ((c == '\n') || (col >= width && view->text_wrap_mode)) {
1931 col = 0;
1932 row++;
1933 if (c == '\n' || row >= height)
1934 continue;
1937 if (c == '\r')
1938 continue;
1940 if (c == '\t') {
1941 offset_type line, column;
1942 view_offset_to_coord (view, &line, &column, from);
1943 col += (8 - column % 8);
1944 if (view->text_wrap_mode && col >= width && width != 0) {
1945 row += col / width;
1946 col %= width;
1948 continue;
1951 if (view->search_start <= from
1952 && from < view->search_start + view->search_length) {
1953 attrset (SELECTED_COLOR);
1956 if (col >= view->dpy_text_column
1957 && col - view->dpy_text_column < width) {
1958 widget_move (view, top + row, left + (col - view->dpy_text_column));
1959 c = convert_to_display_c (c);
1960 if (!is_printable (c))
1961 c = '.';
1962 tty_print_char (c);
1964 col++;
1965 attrset (NORMAL_COLOR);
1967 view->dpy_end = from;
1970 /* Displays as much data from view->dpy_start as fits on the screen */
1971 static void
1972 display (WView *view)
1974 view_compute_areas (view);
1975 if (view->hex_mode) {
1976 view_display_hex (view);
1977 } else {
1978 view_display_text (view);
1980 view_display_status (view);
1983 static void
1984 view_place_cursor (WView *view)
1986 const screen_dimen top = view->data_area.top;
1987 const screen_dimen left = view->data_area.left;
1988 screen_dimen col;
1990 col = view->cursor_col;
1991 if (!view->hexview_in_text && view->hexedit_lownibble)
1992 col++;
1993 widget_move (&view->widget, top + view->cursor_row, left + col);
1996 static void
1997 view_update (WView *view)
1999 static int dirt_limit = 1;
2001 if (view->dpy_bbar_dirty) {
2002 view->dpy_bbar_dirty = FALSE;
2003 view_labels (view);
2004 buttonbar_redraw (view->widget.parent);
2007 if (view->dirty > dirt_limit) {
2008 /* Too many updates skipped -> force a update */
2009 display (view);
2010 view->dirty = 0;
2011 /* Raise the update skipping limit */
2012 dirt_limit++;
2013 if (dirt_limit > max_dirt_limit)
2014 dirt_limit = max_dirt_limit;
2016 if (view->dirty) {
2017 if (is_idle ()) {
2018 /* We have time to update the screen properly */
2019 display (view);
2020 view->dirty = 0;
2021 if (dirt_limit > 1)
2022 dirt_limit--;
2023 } else {
2024 /* We are busy -> skipping full update,
2025 only the status line is updated */
2026 view_display_status (view);
2028 /* Here we had a refresh, if fast scrolling does not work
2029 restore the refresh, although this should not happen */
2033 /* {{{ Hex editor }}} */
2035 static void
2036 enqueue_change (struct hexedit_change_node **head,
2037 struct hexedit_change_node *node)
2039 /* chnode always either points to the head of the list or
2040 * to one of the ->next fields in the list. The value at
2041 * this location will be overwritten with the new node. */
2042 struct hexedit_change_node **chnode = head;
2044 while (*chnode != NULL && (*chnode)->offset < node->offset)
2045 chnode = &((*chnode)->next);
2047 node->next = *chnode;
2048 *chnode = node;
2051 static cb_ret_t
2052 view_handle_editkey (WView *view, int key)
2054 struct hexedit_change_node *node;
2055 byte byte_val;
2057 /* Has there been a change at this position? */
2058 node = view->change_list;
2059 while (node && (node->offset != view->hex_cursor))
2060 node = node->next;
2062 if (!view->hexview_in_text) {
2063 /* Hex editing */
2064 unsigned int hexvalue = 0;
2066 if (key >= '0' && key <= '9')
2067 hexvalue = 0 + (key - '0');
2068 else if (key >= 'A' && key <= 'F')
2069 hexvalue = 10 + (key - 'A');
2070 else if (key >= 'a' && key <= 'f')
2071 hexvalue = 10 + (key - 'a');
2072 else
2073 return MSG_NOT_HANDLED;
2075 if (node)
2076 byte_val = node->value;
2077 else
2078 byte_val = get_byte (view, view->hex_cursor);
2080 if (view->hexedit_lownibble) {
2081 byte_val = (byte_val & 0xf0) | (hexvalue);
2082 } else {
2083 byte_val = (byte_val & 0x0f) | (hexvalue << 4);
2085 } else {
2086 /* Text editing */
2087 if (key < 256 && (is_printable (key) || (key == '\n')))
2088 byte_val = key;
2089 else
2090 return MSG_NOT_HANDLED;
2092 if (!node) {
2093 node = g_new (struct hexedit_change_node, 1);
2094 node->offset = view->hex_cursor;
2095 node->value = byte_val;
2096 enqueue_change (&view->change_list, node);
2097 } else {
2098 node->value = byte_val;
2100 view->dirty++;
2101 view_update (view);
2102 view_move_right (view, 1);
2103 return MSG_HANDLED;
2106 static gboolean
2107 view_hexedit_save_changes (WView *view)
2109 struct hexedit_change_node *curr, *next;
2110 int fp, answer;
2111 char *text, *error;
2113 if (view->change_list == NULL)
2114 return TRUE;
2116 retry_save:
2117 assert (view->filename != NULL);
2118 fp = mc_open (view->filename, O_WRONLY);
2119 if (fp == -1)
2120 goto save_error;
2122 for (curr = view->change_list; curr != NULL; curr = next) {
2123 next = curr->next;
2125 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
2126 || mc_write (fp, &(curr->value), 1) != 1)
2127 goto save_error;
2129 /* delete the saved item from the change list */
2130 view->change_list = next;
2131 view->dirty++;
2132 view_set_byte (view, curr->offset, curr->value);
2133 g_free (curr);
2136 if (mc_close (fp) == -1) {
2137 error = g_strdup (strerror (errno));
2138 message (D_ERROR, _(" Save file "),
2139 _(" Error while closing the file: \n %s \n"
2140 " Data may have been written or not. "), error);
2141 g_free (error);
2143 view_update (view);
2144 return TRUE;
2146 save_error:
2147 error = g_strdup (strerror (errno));
2148 text = g_strdup_printf (_(" Cannot save file: \n %s "), error);
2149 g_free (error);
2150 (void) mc_close (fp);
2152 answer = query_dialog (_(" Save file "), text, D_ERROR,
2153 2, _("&Retry"), _("&Cancel"));
2154 g_free (text);
2156 if (answer == 0)
2157 goto retry_save;
2158 return FALSE;
2161 /* {{{ Miscellaneous functions }}} */
2163 static gboolean
2164 view_ok_to_quit (WView *view)
2166 int r;
2168 if (view->change_list == NULL)
2169 return TRUE;
2171 r = query_dialog (_("Quit"),
2172 _(" File was modified, Save with exit? "), D_NORMAL, 3,
2173 _("&Cancel quit"), _("&Yes"), _("&No"));
2175 switch (r) {
2176 case 1:
2177 return view_hexedit_save_changes (view);
2178 case 2:
2179 view_hexedit_free_change_list (view);
2180 return TRUE;
2181 default:
2182 return FALSE;
2186 static inline void
2187 my_define (Dlg_head *h, int idx, const char *text, void (*fn) (WView *),
2188 WView *view)
2190 buttonbar_set_label_data (h, idx, text, (buttonbarfn) fn, view);
2193 /* {{{ Searching }}} */
2195 /* Case insensitive search of text in data */
2196 static int
2197 icase_search_p (WView *view, char *text, char *data, int nothing)
2199 const char *q;
2200 int lng;
2201 const int direction = view->direction;
2203 (void) nothing;
2205 /* If we are searching backwards, reverse the string */
2206 if (direction == -1) {
2207 g_strreverse (text);
2208 g_strreverse (data);
2211 q = _icase_search (text, data, &lng);
2213 if (direction == -1) {
2214 g_strreverse (text);
2215 g_strreverse (data);
2218 if (q != 0) {
2219 if (direction > 0)
2220 view->search_start = q - data - lng;
2221 else
2222 view->search_start = strlen (data) - (q - data);
2223 view->search_length = lng;
2224 return 1;
2226 return 0;
2229 static char *
2230 grow_string_buffer (char *text, gulong *size)
2232 char *new;
2234 /* The grow steps */
2235 *size += 160;
2236 new = g_realloc (text, *size);
2237 if (text == NULL) {
2238 *new = '\0';
2240 return new;
2243 static char *
2244 get_line_at (WView *view, offset_type *p, offset_type *skipped)
2246 char *buffer = NULL;
2247 gulong buffer_size = 0;
2248 offset_type usable_size = 0;
2249 int ch;
2250 const int direction = view->direction;
2251 offset_type pos = *p;
2252 offset_type i = 0;
2253 int prev = '\0';
2255 *skipped = 0;
2257 if (pos == 0 && direction == -1)
2258 return 0;
2260 /* skip over all the possible zeros in the file */
2261 while ((ch = get_byte (view, pos)) == 0) {
2262 if (pos == 0 && direction == -1)
2263 return 0;
2264 pos += direction;
2265 i++;
2267 *skipped = i;
2269 if (i == 0 && (pos != 0 || direction == -1)) {
2270 prev = get_byte (view, pos - direction);
2271 if ((prev == -1) || (prev == '\n'))
2272 prev = '\0';
2275 for (i = 1; ch != -1; ch = get_byte (view, pos)) {
2276 if (i >= usable_size) {
2277 buffer = grow_string_buffer (buffer, &buffer_size);
2278 usable_size = buffer_size - 2; /* prev & null terminator */
2281 buffer[i++] = ch;
2283 if (pos == 0 && direction == -1)
2284 break;
2286 pos += direction;
2288 if (ch == '\n' || ch == '\0') {
2289 i--; /* Strip newline/zero */
2290 break;
2294 if (buffer) {
2295 buffer[0] = prev;
2296 buffer[i] = '\0';
2298 /* If we are searching backwards, reverse the string */
2299 if (direction == -1) {
2300 g_strreverse (buffer + 1);
2304 *p = pos;
2305 return buffer;
2308 static void
2309 search_update_steps (WView *view)
2311 offset_type filesize = view_get_filesize (view);
2312 if (filesize != 0)
2313 view->update_steps = 40000;
2314 else /* viewing a data stream, not a file */
2315 view->update_steps = filesize / 100;
2317 /* Do not update the percent display but every 20 ks */
2318 if (view->update_steps < 20000)
2319 view->update_steps = 20000;
2322 static void
2323 search (WView *view, char *text,
2324 int (*search) (WView *, char *, char *, int))
2326 char *s = NULL; /* The line we read from the view buffer */
2327 offset_type p, beginning, search_start;
2328 int found_len;
2329 int search_status;
2330 Dlg_head *d = 0;
2332 /* Used to keep track of where the line starts, when looking forward
2333 * is the index before transfering the line; the reverse case uses
2334 * the position returned after the line has been read */
2335 offset_type forward_line_start;
2336 offset_type reverse_line_start;
2337 offset_type t;
2339 if (verbose) {
2340 d = create_message (D_NORMAL, _("Search"), _("Searching %s"), text);
2341 mc_refresh ();
2344 found_len = view->search_length;
2345 search_start = view->search_start;
2347 if (view->direction == 1) {
2348 p = search_start + ((found_len) ? 1 : 0);
2349 } else {
2350 p = search_start - ((found_len && search_start >= 1) ? 1 : 0);
2352 beginning = p;
2354 /* Compute the percent steps */
2355 search_update_steps (view);
2356 view->update_activate = 0;
2358 enable_interrupt_key ();
2359 for (;; g_free (s)) {
2360 if (p >= view->update_activate) {
2361 view->update_activate += view->update_steps;
2362 if (verbose) {
2363 view_percent (view, p);
2364 mc_refresh ();
2366 if (got_interrupt ())
2367 break;
2369 forward_line_start = p;
2370 s = get_line_at (view, &p, &t);
2371 reverse_line_start = p;
2373 if (!s)
2374 break;
2376 search_status = (*search) (view, text, s + 1, match_normal);
2377 if (search_status < 0) {
2378 g_free (s);
2379 break;
2382 if (search_status == 0)
2383 continue;
2385 /* We found the string */
2387 /* Handle ^ and $ when regexp search starts at the middle of the line */
2388 if (*s && !view->search_start && (search == regexp_view_search)) {
2389 if ((*text == '^' && view->direction == 1)
2390 || (view->direction == -1 && text[strlen (text) - 1] == '$')
2392 continue;
2395 /* Record the position used to continue the search */
2396 if (view->direction == 1)
2397 t += forward_line_start;
2398 else
2399 t = reverse_line_start ? reverse_line_start + 3 : 0;
2400 view->search_start += t;
2402 if (t != beginning) {
2403 view->dpy_start = t;
2406 g_free (s);
2407 break;
2409 disable_interrupt_key ();
2410 if (verbose) {
2411 dlg_run_done (d);
2412 destroy_dlg (d);
2414 if (!s) {
2415 message (0, _("Search"), _(" Search string not found "));
2416 view->search_length = 0;
2420 /* Search buffer (its size is len) in the complete buffer
2421 * returns the position where the block was found or INVALID_OFFSET
2422 * if not found */
2423 static offset_type
2424 block_search (WView *view, const char *buffer, int len)
2426 int direction = view->direction;
2427 const char *d = buffer;
2428 char b;
2429 offset_type e;
2431 enable_interrupt_key ();
2432 if (direction == 1)
2433 e = view->search_start + ((view->search_length) ? 1 : 0);
2434 else
2435 e = view->search_start
2436 - ((view->search_length && view->search_start >= 1) ? 1 : 0);
2438 search_update_steps (view);
2439 view->update_activate = 0;
2441 if (direction == -1) {
2442 for (d += len - 1;; e--) {
2443 if (e <= view->update_activate) {
2444 view->update_activate -= view->update_steps;
2445 if (verbose) {
2446 view_percent (view, e);
2447 mc_refresh ();
2449 if (got_interrupt ())
2450 break;
2452 b = get_byte (view, e);
2454 if (*d == b) {
2455 if (d == buffer) {
2456 disable_interrupt_key ();
2457 return e;
2459 d--;
2460 } else {
2461 e += buffer + len - 1 - d;
2462 d = buffer + len - 1;
2464 if (e == 0)
2465 break;
2467 } else {
2468 while (get_byte (view, e) != -1) {
2469 if (e >= view->update_activate) {
2470 view->update_activate += view->update_steps;
2471 if (verbose) {
2472 view_percent (view, e);
2473 mc_refresh ();
2475 if (got_interrupt ())
2476 break;
2478 b = get_byte (view, e++);
2480 if (*d == b) {
2481 d++;
2482 if (d - buffer == len) {
2483 disable_interrupt_key ();
2484 return e - len;
2486 } else {
2487 e -= d - buffer;
2488 d = buffer;
2492 disable_interrupt_key ();
2493 return INVALID_OFFSET;
2497 * Search in the hex mode. Supported input:
2498 * - numbers (oct, dec, hex). Each of them matches one byte.
2499 * - strings in double quotes. Matches exactly without quotes.
2501 static void
2502 hex_search (WView *view, const char *text)
2504 char *buffer; /* Parsed search string */
2505 char *cur; /* Current position in it */
2506 int block_len; /* Length of the search string */
2507 offset_type pos; /* Position of the string in the file */
2508 int parse_error = 0;
2510 if (!*text) {
2511 view->search_length = 0;
2512 return;
2515 /* buffer will never be longer that text */
2516 buffer = g_new (char, strlen (text));
2517 cur = buffer;
2519 /* First convert the string to a stream of bytes */
2520 while (*text) {
2521 int val;
2522 int ptr;
2524 /* Skip leading spaces */
2525 if (*text == ' ' || *text == '\t') {
2526 text++;
2527 continue;
2530 /* %i matches octal, decimal, and hexadecimal numbers */
2531 if (sscanf (text, "%i%n", &val, &ptr) > 0) {
2532 /* Allow signed and unsigned char in the user input */
2533 if (val < -128 || val > 255) {
2534 parse_error = 1;
2535 break;
2538 *cur++ = (char) val;
2539 text += ptr;
2540 continue;
2543 /* Try quoted string, strip quotes */
2544 if (*text == '"') {
2545 const char *next_quote;
2547 text++;
2548 next_quote = strchr (text, '"');
2549 if (next_quote) {
2550 memcpy (cur, text, next_quote - text);
2551 cur += next_quote - text;
2552 text = next_quote + 1;
2553 continue;
2555 /* fall through */
2558 parse_error = 1;
2559 break;
2562 block_len = cur - buffer;
2564 /* No valid bytes in the user input */
2565 if (block_len <= 0 || parse_error) {
2566 message (0, _("Search"), _("Invalid hex search expression"));
2567 g_free (buffer);
2568 view->search_length = 0;
2569 return;
2572 /* Then start the search */
2573 pos = block_search (view, buffer, block_len);
2575 g_free (buffer);
2577 if (pos == INVALID_OFFSET) {
2578 message (0, _("Search"), _(" Search string not found "));
2579 view->search_length = 0;
2580 return;
2583 view->search_start = pos;
2584 view->search_length = block_len;
2585 /* Set the edit cursor to the search position, left nibble */
2586 view->hex_cursor = view->search_start;
2587 view->hexedit_lownibble = FALSE;
2589 /* Adjust the file offset */
2590 view->dpy_start = pos - pos % view->bytes_per_line;
2593 static int
2594 regexp_view_search (WView *view, char *pattern, char *string,
2595 int match_type)
2597 static regex_t r;
2598 static char *old_pattern = NULL;
2599 static int old_type;
2600 regmatch_t pmatch[1];
2601 int i, flags = REG_ICASE;
2603 if (old_pattern == NULL || strcmp (old_pattern, pattern) != 0
2604 || old_type != match_type) {
2605 if (old_pattern != NULL) {
2606 regfree (&r);
2607 g_free (old_pattern);
2608 old_pattern = 0;
2610 for (i = 0; pattern[i] != '\0'; i++) {
2611 if (isupper ((unsigned char) pattern[i])) {
2612 flags = 0;
2613 break;
2616 flags |= REG_EXTENDED;
2617 if (regcomp (&r, pattern, flags)) {
2618 message (1, MSG_ERROR, _(" Invalid regular expression "));
2619 return -1;
2621 old_pattern = g_strdup (pattern);
2622 old_type = match_type;
2624 if (regexec (&r, string, 1, pmatch, 0) != 0)
2625 return 0;
2626 view->search_length = pmatch[0].rm_eo - pmatch[0].rm_so;
2627 view->search_start = pmatch[0].rm_so;
2628 return 1;
2631 static void
2632 do_regexp_search (WView *view)
2634 search (view, view->search_exp, regexp_view_search);
2635 /* Had a refresh here */
2636 view->dirty++;
2637 view_update (view);
2640 static void
2641 do_normal_search (WView *view)
2643 if (view->hex_mode)
2644 hex_search (view, view->search_exp);
2645 else
2646 search (view, view->search_exp, icase_search_p);
2647 /* Had a refresh here */
2648 view->dirty++;
2649 view_update (view);
2652 /* {{{ User-definable commands }}} */
2655 The functions in this section can be bound to hotkeys. They are all
2656 of the same type (taking a pointer to WView as parameter and
2657 returning void). TODO: In the not-too-distant future, these commands
2658 will become fully configurable, like they already are in the
2659 internal editor. By convention, all the function names end in
2660 "_cmd".
2663 static void
2664 view_help_cmd (void)
2666 interactive_display (NULL, "[Internal File Viewer]");
2669 /* Toggle between hexview and hexedit mode */
2670 static void
2671 view_toggle_hexedit_mode_cmd (WView *view)
2673 view_toggle_hexedit_mode (view);
2674 view_update (view);
2677 /* Toggle between wrapped and unwrapped view */
2678 static void
2679 view_toggle_wrap_mode_cmd (WView *view)
2681 view_toggle_wrap_mode (view);
2682 view_update (view);
2685 /* Toggle between hex view and text view */
2686 static void
2687 view_toggle_hex_mode_cmd (WView *view)
2689 view_toggle_hex_mode (view);
2690 view_update (view);
2693 static void
2694 view_moveto_line_cmd (WView *view)
2696 char *answer, *answer_end, prompt[BUF_SMALL];
2697 offset_type line, col;
2699 view_offset_to_coord (view, &line, &col, view->dpy_start);
2701 g_snprintf (prompt, sizeof (prompt),
2702 _(" The current line number is %d.\n"
2703 " Enter the new line number:"), (int) (line + 1));
2704 answer = input_dialog (_(" Goto line "), prompt, "");
2705 if (answer != NULL && answer[0] != '\0') {
2706 errno = 0;
2707 line = strtoul (answer, &answer_end, 10);
2708 if (*answer_end == '\0' && errno == 0 && line >= 1)
2709 view_moveto (view, line - 1, 0);
2711 g_free (answer);
2712 view->dirty++;
2713 view_update (view);
2716 static void
2717 view_moveto_addr_cmd (WView *view)
2719 char *line, *error, prompt[BUF_SMALL];
2720 offset_type addr;
2722 g_snprintf (prompt, sizeof (prompt),
2723 _(" The current address is 0x%lx.\n"
2724 " Enter the new address:"), view->hex_cursor);
2725 line = input_dialog (_(" Goto Address "), prompt, "");
2726 if (line != NULL) {
2727 if (*line != '\0') {
2728 addr = strtoul (line, &error, 0);
2729 if ((*error == '\0') && get_byte (view, addr) != -1) {
2730 view_moveto_offset (view, addr);
2733 g_free (line);
2735 view->dirty++;
2736 view_update (view);
2739 static void
2740 view_hexedit_save_changes_cmd (WView *view)
2742 (void) view_hexedit_save_changes (view);
2745 /* {{{ Searching }}} */
2747 static void
2748 regexp_search (WView *view, int direction)
2750 const char *defval;
2751 char *regexp;
2753 defval = (view->search_exp != NULL) ? view->search_exp : "";
2755 regexp = input_dialog (_("Search"), _(" Enter regexp:"), defval);
2756 if (regexp == NULL || regexp[0] == '\0')
2757 goto cleanup;
2759 g_free (view->search_exp);
2760 view->search_exp = regexp;
2761 regexp = NULL;
2763 view->direction = direction;
2764 do_regexp_search (view);
2765 view->last_search = do_regexp_search;
2767 cleanup:
2768 g_free (regexp);
2771 /* {{{ User-definable commands }}} */
2773 static void
2774 view_regexp_search_cmd (WView *view)
2776 regexp_search (view, 1);
2779 /* Both views */
2780 static void
2781 view_normal_search_cmd (WView *view)
2783 char *defval, *exp = NULL;
2785 enum {
2786 SEARCH_DLG_HEIGHT = 8,
2787 SEARCH_DLG_WIDTH = 58
2790 static int replace_backwards;
2791 int treplace_backwards = replace_backwards;
2793 static QuickWidget quick_widgets[] = {
2794 {quick_button, 6, 10, 5, SEARCH_DLG_HEIGHT, N_("&Cancel"), 0,
2795 B_CANCEL,
2796 0, 0, NULL},
2797 {quick_button, 2, 10, 5, SEARCH_DLG_HEIGHT, N_("&OK"), 0, B_ENTER,
2798 0, 0, NULL},
2799 {quick_checkbox, 3, SEARCH_DLG_WIDTH, 4, SEARCH_DLG_HEIGHT,
2800 N_("&Backwards"), 0, 0,
2801 0, 0, NULL},
2802 {quick_input, 3, SEARCH_DLG_WIDTH, 3, SEARCH_DLG_HEIGHT, "", 52, 0,
2803 0, 0, N_("Search")},
2804 {quick_label, 2, SEARCH_DLG_WIDTH, 2, SEARCH_DLG_HEIGHT,
2805 N_(" Enter search string:"), 0, 0,
2806 0, 0, 0},
2807 NULL_QuickWidget
2809 static QuickDialog Quick_input = {
2810 SEARCH_DLG_WIDTH, SEARCH_DLG_HEIGHT, -1, 0, N_("Search"),
2811 "[Input Line Keys]", quick_widgets, 0
2814 defval = g_strdup ((view->search_exp != NULL) ? view->search_exp : "");
2815 convert_to_display (defval);
2817 quick_widgets[2].result = &treplace_backwards;
2818 quick_widgets[3].str_result = &exp;
2819 quick_widgets[3].text = defval;
2821 if (quick_dialog (&Quick_input) == B_CANCEL)
2822 goto cleanup;
2824 replace_backwards = treplace_backwards;
2826 if (exp == NULL || exp[0] == '\0')
2827 goto cleanup;
2829 convert_from_input (exp);
2831 g_free (view->search_exp);
2832 view->search_exp = exp;
2833 exp = NULL;
2835 view->direction = replace_backwards ? -1 : 1;
2836 do_normal_search (view);
2837 view->last_search = do_normal_search;
2839 cleanup:
2840 g_free (exp);
2841 g_free (defval);
2844 static void
2845 view_toggle_magic_mode_cmd (WView *view)
2847 view_toggle_magic_mode (view);
2848 view_update (view);
2851 static void
2852 view_toggle_nroff_mode_cmd (WView *view)
2854 view_toggle_nroff_mode (view);
2855 view_update (view);
2858 static void
2859 view_quit_cmd (WView *view)
2861 if (view_ok_to_quit (view))
2862 dlg_stop (view->widget.parent);
2865 /* {{{ Miscellaneous functions }}} */
2867 /* Define labels and handlers for functional keys */
2868 static void
2869 view_labels (WView *view)
2871 Dlg_head *h = view->widget.parent;
2873 buttonbar_set_label (h, 1, gettext_ui("ButtonBar|Help"), view_help_cmd);
2875 my_define (h, 10, gettext_ui("ButtonBar|Quit"), view_quit_cmd, view);
2876 my_define (h, 4, view->hex_mode
2877 ? gettext_ui("ButtonBar|Ascii")
2878 : gettext_ui("ButtonBar|Hex"),
2879 view_toggle_hex_mode_cmd, view);
2880 my_define (h, 5, view->hex_mode
2881 ? gettext_ui("ButtonBar|Goto")
2882 : gettext_ui("ButtonBar|Line"),
2883 view->hex_mode ? view_moveto_addr_cmd : view_moveto_line_cmd, view);
2885 if (view->hex_mode) {
2886 if (view->hexedit_mode) {
2887 my_define (h, 2, gettext_ui("ButtonBar|View"),
2888 view_toggle_hexedit_mode_cmd, view);
2889 } else if (view->datasource == DS_FILE) {
2890 my_define (h, 2, gettext_ui("ButtonBar|Edit"),
2891 view_toggle_hexedit_mode_cmd, view);
2892 } else {
2893 my_define (h, 2, "", NULL, view);
2895 my_define (h, 6, gettext_ui("ButtonBar|Save"),
2896 view_hexedit_save_changes_cmd, view);
2897 } else {
2898 my_define (h, 2, view->text_wrap_mode
2899 ? gettext_ui("ButtonBar|UnWrap")
2900 : gettext_ui("ButtonBar|Wrap"),
2901 view_toggle_wrap_mode_cmd, view);
2902 my_define (h, 6, gettext_ui("ButtonBar|RxSrch"),
2903 view_regexp_search_cmd, view);
2906 my_define (h, 7, view->hex_mode
2907 ? gettext_ui("ButtonBar|HxSrch")
2908 : gettext_ui("ButtonBar|Search"),
2909 view_normal_search_cmd, view);
2910 my_define (h, 8, view->magic_mode
2911 ? gettext_ui("ButtonBar|Raw")
2912 : gettext_ui("ButtonBar|Parse"),
2913 view_toggle_magic_mode_cmd, view);
2915 /* don't override the key to access the main menu */
2916 if (!view_is_in_panel (view)) {
2917 my_define (h, 9, view->text_nroff_mode
2918 ? gettext_ui("ButtonBar|Unform")
2919 : gettext_ui("ButtonBar|Format"),
2920 view_toggle_nroff_mode_cmd, view);
2921 my_define (h, 3, gettext_ui("ButtonBar|Quit"), view_quit_cmd, view);
2925 /* {{{ Event handling }}} */
2927 /* Check for left and right arrows, possibly with modifiers */
2928 static cb_ret_t
2929 check_left_right_keys (WView *view, int c)
2931 if (c == KEY_LEFT) {
2932 view_move_left (view, 1);
2933 return MSG_HANDLED;
2936 if (c == KEY_RIGHT) {
2937 view_move_right (view, 1);
2938 return MSG_HANDLED;
2941 /* Ctrl with arrows moves by 10 postions in the unwrap mode */
2942 if (view->hex_mode || view->text_wrap_mode)
2943 return MSG_NOT_HANDLED;
2945 if (c == (KEY_M_CTRL | KEY_LEFT)) {
2946 if (view->dpy_text_column >= 10)
2947 view->dpy_text_column -= 10;
2948 else
2949 view->dpy_text_column = 0;
2950 view->dirty++;
2951 return MSG_HANDLED;
2954 if (c == (KEY_M_CTRL | KEY_RIGHT)) {
2955 if (view->dpy_text_column <= OFFSETTYPE_MAX - 10)
2956 view->dpy_text_column += 10;
2957 else
2958 view->dpy_text_column = OFFSETTYPE_MAX;
2959 view->dirty++;
2960 return MSG_HANDLED;
2963 return MSG_NOT_HANDLED;
2966 /* {{{ User-definable commands }}} */
2968 static void
2969 view_continue_search_cmd (WView *view)
2971 if (view->last_search) {
2972 view->last_search (view);
2973 } else {
2974 /* if not... then ask for an expression */
2975 view_normal_search_cmd (view);
2979 static void
2980 view_toggle_ruler_cmd (WView *view)
2982 static const enum ruler_type next[3] = {
2983 RULER_TOP,
2984 RULER_BOTTOM,
2985 RULER_NONE
2988 assert ((size_t) ruler < 3);
2989 ruler = next[(size_t) ruler];
2990 view->dirty++;
2993 /* {{{ Event handling }}} */
2995 static void view_cmk_move_up (void *w, int n) {
2996 view_move_up ((WView *) w, n);
2998 static void view_cmk_move_down (void *w, int n) {
2999 view_move_down ((WView *) w, n);
3001 static void view_cmk_moveto_top (void *w, int n) {
3002 (void) &n;
3003 view_moveto_top ((WView *) w);
3005 static void view_cmk_moveto_bottom (void *w, int n) {
3006 (void) &n;
3007 view_moveto_bottom ((WView *) w);
3010 /* Both views */
3011 static cb_ret_t
3012 view_handle_key (WView *view, int c)
3014 c = convert_from_input_c (c);
3016 if (view->hex_mode) {
3017 switch (c) {
3018 case '\t':
3019 view->hexview_in_text = !view->hexview_in_text;
3020 view->dirty++;
3021 return MSG_HANDLED;
3023 case XCTRL ('a'):
3024 view_moveto_bol (view);
3025 view->dirty++;
3026 return MSG_HANDLED;
3028 case XCTRL ('b'):
3029 view_move_left (view, 1);
3030 return MSG_HANDLED;
3032 case XCTRL ('e'):
3033 view_moveto_eol (view);
3034 return MSG_HANDLED;
3036 case XCTRL ('f'):
3037 view_move_right (view, 1);
3038 return MSG_HANDLED;
3041 if (view->hexedit_mode
3042 && view_handle_editkey (view, c) == MSG_HANDLED)
3043 return MSG_HANDLED;
3046 if (check_left_right_keys (view, c))
3047 return MSG_HANDLED;
3049 if (check_movement_keys (c, view->data_area.height + 1, view,
3050 view_cmk_move_up, view_cmk_move_down,
3051 view_cmk_moveto_top, view_cmk_moveto_bottom))
3052 return MSG_HANDLED;
3054 switch (c) {
3056 case '?':
3057 regexp_search (view, -1);
3058 return MSG_HANDLED;
3060 case '/':
3061 regexp_search (view, 1);
3062 return MSG_HANDLED;
3064 /* Continue search */
3065 case XCTRL ('r'):
3066 case XCTRL ('s'):
3067 case 'n':
3068 case KEY_F (17):
3069 view_continue_search_cmd (view);
3070 return MSG_HANDLED;
3072 /* toggle ruler */
3073 case ALT ('r'):
3074 view_toggle_ruler_cmd (view);
3075 return MSG_HANDLED;
3077 case 'h':
3078 view_move_left (view, 1);
3079 return MSG_HANDLED;
3081 case 'j':
3082 case '\n':
3083 case 'e':
3084 view_move_down (view, 1);
3085 return MSG_HANDLED;
3087 case 'd':
3088 view_move_down (view, (view->data_area.height + 1) / 2);
3089 return MSG_HANDLED;
3091 case 'u':
3092 view_move_up (view, (view->data_area.height + 1) / 2);
3093 return MSG_HANDLED;
3095 case 'k':
3096 case 'y':
3097 view_move_up (view, 1);
3098 return MSG_HANDLED;
3100 case 'l':
3101 view_move_right (view, 1);
3102 return MSG_HANDLED;
3104 case ' ':
3105 case 'f':
3106 view_move_down (view, view->data_area.height);
3107 return MSG_HANDLED;
3109 case XCTRL ('o'):
3110 view_other_cmd ();
3111 return MSG_HANDLED;
3113 /* Unlike Ctrl-O, run a new shell if the subshell is not running. */
3114 case '!':
3115 exec_shell ();
3116 return MSG_HANDLED;
3118 case 'b':
3119 view_move_up (view, view->data_area.height);
3120 return MSG_HANDLED;
3122 case KEY_IC:
3123 view_move_up (view, 2);
3124 return MSG_HANDLED;
3126 case KEY_DC:
3127 view_move_down (view, 2);
3128 return MSG_HANDLED;
3130 case 'm':
3131 view->marks[view->marker] = view->dpy_start;
3132 return MSG_HANDLED;
3134 case 'r':
3135 view->dpy_start = view->marks[view->marker];
3136 view->dirty++;
3137 return MSG_HANDLED;
3139 /* Use to indicate parent that we want to see the next/previous file */
3140 /* Does not work in panel mode */
3141 case XCTRL ('f'):
3142 case XCTRL ('b'):
3143 if (!view_is_in_panel (view))
3144 view->move_dir = c == XCTRL ('f') ? 1 : -1;
3145 /* FALLTHROUGH */
3146 case 'q':
3147 case XCTRL ('g'):
3148 case ESC_CHAR:
3149 if (view_ok_to_quit (view))
3150 view->want_to_quit = TRUE;
3151 return MSG_HANDLED;
3153 #ifdef HAVE_CHARSET
3154 case XCTRL ('t'):
3155 do_select_codepage ();
3156 view->dirty++;
3157 view_update (view);
3158 return MSG_HANDLED;
3159 #endif /* HAVE_CHARSET */
3161 #ifdef MC_ENABLE_DEBUGGING_CODE
3162 case 't': /* mnemonic: "test" */
3163 view_ccache_dump (view);
3164 return MSG_HANDLED;
3165 #endif
3167 if (c >= '0' && c <= '9')
3168 view->marker = c - '0';
3170 /* Key not used */
3171 return MSG_NOT_HANDLED;
3174 /* Both views */
3175 static int
3176 view_event (WView *view, Gpm_Event *event, int *result)
3178 screen_dimen y, x;
3180 *result = MOU_NORMAL;
3182 /* We are not interested in the release events */
3183 if (!(event->type & (GPM_DOWN | GPM_DRAG)))
3184 return 0;
3186 /* Wheel events */
3187 if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) {
3188 view_move_up (view, 2);
3189 return 1;
3191 if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
3192 view_move_down (view, 2);
3193 return 1;
3196 x = event->x;
3197 y = event->y;
3199 /* Scrolling left and right */
3200 if (!view->text_wrap_mode) {
3201 if (x < view->data_area.width * 1/4) {
3202 view_move_left (view, 1);
3203 goto processed;
3204 } else if (x < view->data_area.width * 3/4) {
3205 /* ignore the click */
3206 } else {
3207 view_move_right (view, 1);
3208 goto processed;
3212 /* Scrolling up and down */
3213 if (y < view->data_area.top + view->data_area.height * 1/3) {
3214 if (mouse_move_pages_viewer)
3215 view_move_up (view, view->data_area.height / 2);
3216 else
3217 view_move_up (view, 1);
3218 goto processed;
3219 } else if (y < view->data_area.top + view->data_area.height * 2/3) {
3220 /* ignore the click */
3221 } else {
3222 if (mouse_move_pages_viewer)
3223 view_move_down (view, view->data_area.height / 2);
3224 else
3225 view_move_down (view, 1);
3226 goto processed;
3229 return 0;
3231 processed:
3232 *result = MOU_REPEAT;
3233 return 1;
3236 /* Real view only */
3237 static int
3238 real_view_event (Gpm_Event *event, void *x)
3240 WView *view = (WView *) x;
3241 int result;
3243 if (view_event (view, event, &result))
3244 view_update (view);
3245 return result;
3248 static void
3249 view_adjust_size (Dlg_head *h)
3251 WView *view;
3252 WButtonBar *bar;
3254 /* Look up the viewer and the buttonbar, we assume only two widgets here */
3255 view = (WView *) find_widget_type (h, view_callback);
3256 bar = find_buttonbar (h);
3257 widget_set_size (&view->widget, 0, 0, LINES - 1, COLS);
3258 widget_set_size ((Widget *) bar, LINES - 1, 0, 1, COLS);
3260 view_compute_areas (view);
3261 view_update_bytes_per_line (view);
3264 /* Callback for the view dialog */
3265 static cb_ret_t
3266 view_dialog_callback (Dlg_head *h, dlg_msg_t msg, int parm)
3268 switch (msg) {
3269 case DLG_RESIZE:
3270 view_adjust_size (h);
3271 return MSG_HANDLED;
3273 default:
3274 return default_dlg_callback (h, msg, parm);
3278 /* {{{ External interface }}} */
3280 /* Real view only */
3282 mc_internal_viewer (const char *command, const char *file,
3283 int *move_dir_p, int start_line)
3285 gboolean succeeded;
3286 WView *wview;
3287 WButtonBar *bar;
3288 Dlg_head *view_dlg;
3290 /* Create dialog and widgets, put them on the dialog */
3291 view_dlg =
3292 create_dlg (0, 0, LINES, COLS, NULL, view_dialog_callback,
3293 "[Internal File Viewer]", NULL, DLG_WANT_TAB);
3295 wview = view_new (0, 0, COLS, LINES - 1, 0);
3297 bar = buttonbar_new (1);
3299 add_widget (view_dlg, bar);
3300 add_widget (view_dlg, wview);
3302 succeeded = view_load (wview, command, file, start_line);
3303 if (succeeded) {
3304 run_dlg (view_dlg);
3305 if (move_dir_p)
3306 *move_dir_p = wview->move_dir;
3307 } else {
3308 if (move_dir_p)
3309 *move_dir_p = 0;
3311 destroy_dlg (view_dlg);
3313 return succeeded;
3316 /* {{{ Miscellaneous functions }}} */
3318 static void
3319 view_hook (void *v)
3321 WView *view = (WView *) v;
3322 WPanel *panel;
3324 /* If the user is busy typing, wait until he finishes to update the
3325 screen */
3326 if (!is_idle ()) {
3327 if (!hook_present (idle_hook, view_hook))
3328 add_hook (&idle_hook, view_hook, v);
3329 return;
3332 delete_hook (&idle_hook, view_hook);
3334 if (get_current_type () == view_listing)
3335 panel = current_panel;
3336 else if (get_other_type () == view_listing)
3337 panel = other_panel;
3338 else
3339 return;
3341 view_load (view, 0, panel->dir.list[panel->selected].fname, 0);
3342 display (view);
3345 /* {{{ Event handling }}} */
3347 static cb_ret_t
3348 view_callback (Widget *w, widget_msg_t msg, int parm)
3350 WView *view = (WView *) w;
3351 cb_ret_t i;
3352 Dlg_head *h = view->widget.parent;
3354 view_compute_areas (view);
3355 view_update_bytes_per_line (view);
3357 switch (msg) {
3358 case WIDGET_INIT:
3359 if (view_is_in_panel (view))
3360 add_hook (&select_file_hook, view_hook, view);
3361 else
3362 view->dpy_bbar_dirty = TRUE;
3363 return MSG_HANDLED;
3365 case WIDGET_DRAW:
3366 display (view);
3367 return MSG_HANDLED;
3369 case WIDGET_CURSOR:
3370 if (view->hex_mode)
3371 view_place_cursor (view);
3372 return MSG_HANDLED;
3374 case WIDGET_KEY:
3375 i = view_handle_key ((WView *) view, parm);
3376 if (view->want_to_quit && !view_is_in_panel (view))
3377 dlg_stop (h);
3378 else {
3379 view_update (view);
3381 return i;
3383 case WIDGET_FOCUS:
3384 view->dpy_bbar_dirty = TRUE;
3385 view_update (view);
3386 return MSG_HANDLED;
3388 case WIDGET_DESTROY:
3389 view_done (view);
3390 if (view_is_in_panel (view))
3391 delete_hook (&select_file_hook, view_hook);
3392 return MSG_HANDLED;
3394 default:
3395 return default_proc (msg, parm);
3399 /* {{{ External interface }}} */
3401 WView *
3402 view_new (int y, int x, int cols, int lines, int is_panel)
3404 WView *view = g_new0 (WView, 1);
3405 size_t i;
3407 init_widget (&view->widget, y, x, lines, cols,
3408 view_callback,
3409 real_view_event);
3411 view->filename = NULL;
3412 view->command = NULL;
3414 view_set_datasource_none (view);
3416 view->growbuf_in_use = FALSE;
3417 /* leave the other growbuf fields uninitialized */
3419 view->hex_mode = FALSE;
3420 view->hexedit_mode = FALSE;
3421 view->hexview_in_text = FALSE;
3422 view->text_nroff_mode = FALSE;
3423 view->text_wrap_mode = FALSE;
3424 view->magic_mode = FALSE;
3426 view->hexedit_lownibble = FALSE;
3427 view->coord_cache = NULL;
3429 view->dpy_frame_size = is_panel ? 1 : 0;
3430 view->dpy_start = 0;
3431 view->dpy_text_column = 0;
3432 view->dpy_end= 0;
3433 view->hex_cursor = 0;
3434 view->cursor_col = 0;
3435 view->cursor_row = 0;
3436 view->change_list = NULL;
3438 /* {status,ruler,data}_area are left uninitialized */
3440 view->dirty = 0;
3441 view->dpy_bbar_dirty = TRUE;
3442 view->bytes_per_line = 1;
3444 view->search_start = 0;
3445 view->search_length = 0;
3446 view->search_exp = NULL;
3447 view->direction = 1; /* forward */
3448 view->last_search = 0; /* it's a function */
3450 view->want_to_quit = FALSE;
3451 view->marker = 0;
3452 for (i = 0; i < sizeof(view->marks) / sizeof(view->marks[0]); i++)
3453 view->marks[i] = 0;
3455 view->move_dir = 0;
3456 view->update_steps = 0;
3457 view->update_activate = 0;
3459 if (default_hex_mode)
3460 view_toggle_hex_mode (view);
3461 if (default_nroff_flag)
3462 view_toggle_nroff_mode (view);
3463 if (global_wrap_mode)
3464 view_toggle_wrap_mode (view);
3465 if (default_magic_flag)
3466 view_toggle_magic_mode (view);
3468 return view;