Reverted the use of bool in favour of gboolean
[midnight-commander.git] / src / view.c
blobd83f00fa321cd460d9b218f72bfa23e75d2de937
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 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>
16 This program is free software; you can redistribute it and/or modify
17 it under the terms of the GNU General Public License as published by
18 the Free Software Foundation; either version 2 of the License, or
19 (at your option) any later version.
21 This program is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, write to the Free Software
28 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
31 #ifdef HAVE_CONFIG_H
32 # include <config.h>
33 #endif
35 #include <assert.h>
36 #include <ctype.h>
37 #include <errno.h>
38 #include <limits.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <unistd.h>
46 #include <mhl/types.h>
47 #include <mhl/memory.h>
48 #include <mhl/string.h>
50 #include "global.h"
51 #include "tty.h"
52 #include "cmd.h" /* For view_other_cmd */
53 #include "dialog.h" /* Needed by widget.h */
54 #include "widget.h" /* Needed for buttonbar_new */
55 #include "color.h"
56 #include "mouse.h"
57 #include "help.h"
58 #include "key.h" /* For mi_getch() */
59 #include "layout.h"
60 #include "setup.h"
61 #include "wtools.h" /* For query_set_sel() */
62 #include "dir.h"
63 #include "panel.h" /* Needed for current_panel and other_panel */
64 #include "win.h"
65 #include "execute.h"
66 #include "main.h" /* slow_terminal */
67 #include "view.h"
68 #include "history.h"
69 #include "charsets.h"
70 #include "selcodepage.h"
72 /* Block size for reading files in parts */
73 #define VIEW_PAGE_SIZE ((size_t) 8192)
74 #define VIEW_COORD_CACHE_GRANUL 1024
76 typedef unsigned char byte;
78 /* Offset in bytes into a file */
79 typedef unsigned long offset_type;
80 #define INVALID_OFFSET ((offset_type) -1)
81 #define OFFSETTYPE_MAX (~((offset_type) 0))
82 #define OFFSETTYPE_PRIX "lX"
83 #define OFFSETTYPE_PRId "lu"
85 /* A width or height on the screen */
86 typedef unsigned int screen_dimen;
88 /* A cache entry for mapping offsets into line/column pairs and vice versa.
89 * cc_offset, cc_line, and cc_column are the 0-based values of the offset,
90 * line and column of that cache entry. cc_nroff_column is the column
91 * corresponding to cc_offset in nroff mode.
93 struct coord_cache_entry {
94 offset_type cc_offset;
95 offset_type cc_line;
96 offset_type cc_column;
97 offset_type cc_nroff_column;
100 /* A node for building a change list on change_list */
101 struct hexedit_change_node {
102 struct hexedit_change_node *next;
103 offset_type offset;
104 byte value;
107 /* data sources of the view */
108 enum view_ds {
109 DS_NONE, /* No data available */
110 DS_STDIO_PIPE, /* Data comes from a pipe using popen/pclose */
111 DS_VFS_PIPE, /* Data comes from a piped-in VFS file */
112 DS_FILE, /* Data comes from a VFS file */
113 DS_STRING /* Data comes from a string in memory */
116 struct area {
117 screen_dimen top, left;
118 screen_dimen height, width;
121 struct WView {
122 Widget widget;
124 char *filename; /* Name of the file */
125 char *command; /* Command used to pipe data in */
127 enum view_ds datasource; /* Where the displayed data comes from */
129 /* stdio pipe data source */
130 FILE *ds_stdio_pipe; /* Output of a shell command */
132 /* vfs pipe data source */
133 int ds_vfs_pipe; /* Non-seekable vfs file descriptor */
135 /* vfs file data source */
136 int ds_file_fd; /* File with random access */
137 off_t ds_file_filesize; /* Size of the file */
138 off_t ds_file_offset; /* Offset of the currently loaded data */
139 byte *ds_file_data; /* Currently loaded data */
140 size_t ds_file_datalen; /* Number of valid bytes in file_data */
141 size_t ds_file_datasize; /* Number of allocated bytes in file_data */
143 /* string data source */
144 byte *ds_string_data; /* The characters of the string */
145 size_t ds_string_len; /* The length of the string */
147 /* Growing buffers information */
148 gboolean growbuf_in_use; /* Use the growing buffers? */
149 byte **growbuf_blockptr; /* Pointer to the block pointers */
150 size_t growbuf_blocks; /* The number of blocks in *block_ptr */
151 size_t growbuf_lastindex; /* Number of bytes in the last page of the
152 growing buffer */
153 gboolean growbuf_finished; /* TRUE when all data has been read. */
155 /* Editor modes */
156 gboolean hex_mode; /* Hexview or Hexedit */
157 gboolean hexedit_mode; /* Hexedit */
158 gboolean hexview_in_text; /* Is the hexview cursor in the text area? */
159 gboolean text_nroff_mode; /* Nroff-style highlighting */
160 gboolean text_wrap_mode; /* Wrap text lines to fit them on the screen */
161 gboolean magic_mode; /* Preprocess the file using external programs */
163 /* Additional editor state */
164 gboolean hexedit_lownibble; /* Are we editing the last significant nibble? */
165 GArray *coord_cache; /* Cache for mapping offsets to cursor positions */
167 /* Display information */
168 screen_dimen dpy_frame_size;/* Size of the frame surrounding the real viewer */
169 offset_type dpy_start; /* Offset of the displayed data */
170 offset_type dpy_end; /* Offset after the displayed data */
171 offset_type dpy_text_column;/* Number of skipped columns in non-wrap
172 * text mode */
173 offset_type hex_cursor; /* Hexview cursor position in file */
174 screen_dimen cursor_col; /* Cursor column */
175 screen_dimen cursor_row; /* Cursor row */
176 struct hexedit_change_node *change_list; /* Linked list of changes */
177 struct area status_area; /* Where the status line is displayed */
178 struct area ruler_area; /* Where the ruler is displayed */
179 struct area data_area; /* Where the data is displayed */
181 int dirty; /* Number of skipped updates */
182 gboolean dpy_bbar_dirty; /* Does the button bar need to be updated? */
184 /* Mode variables */
185 int bytes_per_line; /* Number of bytes per line in hex mode */
187 /* Search variables */
188 offset_type search_start; /* First character to start searching from */
189 offset_type search_length; /* Length of found string or 0 if none was found */
190 char *search_exp; /* The search expression */
191 int direction; /* 1= forward; -1 backward */
192 void (*last_search)(WView *);
193 /* Pointer to the last search command */
194 gboolean want_to_quit; /* Prepare for cleanup ... */
196 /* Markers */
197 int marker; /* mark to use */
198 offset_type marks [10]; /* 10 marks: 0..9 */
200 int move_dir; /* return value from widget:
201 * 0 do nothing
202 * -1 view previous file
203 * 1 view next file
206 offset_type update_steps; /* The number of bytes between percent
207 * increments */
208 offset_type update_activate;/* Last point where we updated the status */
212 /* {{{ Global Variables }}} */
214 /* Maxlimit for skipping updates */
215 int max_dirt_limit = 10;
217 /* If set, show a ruler */
218 static enum ruler_type {
219 RULER_NONE,
220 RULER_TOP,
221 RULER_BOTTOM
222 } ruler = RULER_NONE;
224 /* Scrolling is done in pages or line increments */
225 int mouse_move_pages_viewer = 1;
227 /* wrap mode default */
228 int global_wrap_mode = 1;
230 int default_hex_mode = 0;
231 int default_magic_flag = 1;
232 int default_nroff_flag = 1;
233 int altered_hex_mode = 0;
234 int altered_magic_flag = 0;
235 int altered_nroff_flag = 0;
237 static const char hex_char[] = "0123456789ABCDEF";
239 int mcview_remember_file_position = FALSE;
241 /* {{{ Function Prototypes }}} */
243 /* Our widget callback */
244 static cb_ret_t view_callback (Widget *, widget_msg_t, int);
246 static int regexp_view_search (WView * view, char *pattern, char *string,
247 int match_type);
248 static void view_labels (WView * view);
250 static void view_init_growbuf (WView *);
251 static void view_place_cursor (WView *view);
252 static void display (WView *);
253 static void view_done (WView *);
255 /* {{{ Helper Functions }}} */
257 /* difference or zero */
258 static inline screen_dimen
259 dimen_doz (screen_dimen a, screen_dimen b)
261 return (a >= b) ? a - b : 0;
264 static inline screen_dimen
265 dimen_min (screen_dimen a, screen_dimen b)
267 return (a < b) ? a : b;
270 static inline offset_type
271 offset_doz (offset_type a, offset_type b)
273 return (a >= b) ? a - b : 0;
276 static inline offset_type
277 offset_rounddown (offset_type a, offset_type b)
279 assert (b != 0);
280 return a - a % b;
283 /* {{{ Simple Primitive Functions for WView }}} */
285 static inline gboolean
286 view_is_in_panel (WView *view)
288 return (view->dpy_frame_size != 0);
291 static void
292 view_compute_areas (WView *view)
294 struct area view_area;
295 screen_dimen height, rest, y;
297 /* The viewer is surrounded by a frame of size view->dpy_frame_size.
298 * Inside that frame, there are: The status line (at the top),
299 * the data area and an optional ruler, which is shown above or
300 * below the data area. */
302 view_area.top = view->dpy_frame_size;
303 view_area.left = view->dpy_frame_size;
304 view_area.height = dimen_doz(view->widget.lines, 2 * view->dpy_frame_size);
305 view_area.width = dimen_doz(view->widget.cols, 2 * view->dpy_frame_size);
307 /* Most coordinates of the areas equal those of the whole viewer */
308 view->status_area = view_area;
309 view->ruler_area = view_area;
310 view->data_area = view_area;
312 /* Compute the heights of the areas */
313 rest = view_area.height;
315 height = dimen_min(rest, 1);
316 view->status_area.height = height;
317 rest -= height;
319 height = dimen_min(rest, (ruler == RULER_NONE || view->hex_mode) ? 0 : 2);
320 view->ruler_area.height = height;
321 rest -= height;
323 view->data_area.height = rest;
325 /* Compute the position of the areas */
326 y = view_area.top;
328 view->status_area.top = y;
329 y += view->status_area.height;
331 if (ruler == RULER_TOP) {
332 view->ruler_area.top = y;
333 y += view->ruler_area.height;
336 view->data_area.top = y;
337 y += view->data_area.height;
339 if (ruler == RULER_BOTTOM) {
340 view->ruler_area.top = y;
341 y += view->ruler_area.height;
345 static void
346 view_hexedit_free_change_list (WView *view)
348 struct hexedit_change_node *curr, *next;
350 for (curr = view->change_list; curr != NULL; curr = next) {
351 next = curr->next;
352 g_free (curr);
354 view->change_list = NULL;
355 view->dirty++;
358 /* {{{ Growing buffer }}} */
360 static void
361 view_init_growbuf (WView *view)
363 view->growbuf_in_use = TRUE;
364 view->growbuf_blockptr = NULL;
365 view->growbuf_blocks = 0;
366 view->growbuf_lastindex = VIEW_PAGE_SIZE;
367 view->growbuf_finished = FALSE;
370 static void
371 view_growbuf_free (WView *view)
373 size_t i;
375 assert (view->growbuf_in_use);
377 for (i = 0; i < view->growbuf_blocks; i++)
378 g_free (view->growbuf_blockptr[i]);
379 g_free (view->growbuf_blockptr);
380 view->growbuf_blockptr = NULL;
381 view->growbuf_in_use = FALSE;
384 static offset_type
385 view_growbuf_filesize (WView *view)
387 assert(view->growbuf_in_use);
389 if (view->growbuf_blocks == 0)
390 return 0;
391 else
392 return ((offset_type) view->growbuf_blocks - 1) * VIEW_PAGE_SIZE
393 + view->growbuf_lastindex;
396 /* Copies the output from the pipe to the growing buffer, until either
397 * the end-of-pipe is reached or the interval [0..ofs) of the growing
398 * buffer is completely filled. */
399 static void
400 view_growbuf_read_until (WView *view, offset_type ofs)
402 ssize_t nread;
403 byte *p;
404 size_t bytesfree;
405 gboolean short_read;
407 assert (view->growbuf_in_use);
409 if (view->growbuf_finished)
410 return;
412 short_read = FALSE;
413 while (view_growbuf_filesize (view) < ofs || short_read) {
414 if (view->growbuf_lastindex == VIEW_PAGE_SIZE) {
415 /* Append a new block to the growing buffer */
416 byte *newblock = g_try_malloc (VIEW_PAGE_SIZE);
417 byte **newblocks = g_try_malloc (sizeof (*newblocks) * (view->growbuf_blocks + 1));
418 if (!newblock || !newblocks) {
419 g_free (newblock);
420 g_free (newblocks);
421 return;
423 memcpy (newblocks, view->growbuf_blockptr, sizeof (*newblocks) * view->growbuf_blocks);
424 g_free (view->growbuf_blockptr);
425 view->growbuf_blockptr = newblocks;
426 view->growbuf_blockptr[view->growbuf_blocks++] = newblock;
427 view->growbuf_lastindex = 0;
429 p = view->growbuf_blockptr[view->growbuf_blocks - 1] + view->growbuf_lastindex;
430 bytesfree = VIEW_PAGE_SIZE - view->growbuf_lastindex;
432 if (view->datasource == DS_STDIO_PIPE) {
433 nread = fread (p, 1, bytesfree, view->ds_stdio_pipe);
434 if (nread == 0) {
435 view->growbuf_finished = TRUE;
436 (void) pclose (view->ds_stdio_pipe);
437 display (view);
438 close_error_pipe (D_NORMAL, NULL);
439 view->ds_stdio_pipe = NULL;
440 return;
442 } else {
443 assert (view->datasource == DS_VFS_PIPE);
444 do {
445 nread = mc_read (view->ds_vfs_pipe, p, bytesfree);
446 } while (nread == -1 && errno == EINTR);
447 if (nread == -1 || nread == 0) {
448 view->growbuf_finished = TRUE;
449 (void) mc_close (view->ds_vfs_pipe);
450 view->ds_vfs_pipe = -1;
451 return;
454 short_read = ((size_t)nread < bytesfree);
455 view->growbuf_lastindex += nread;
459 static int
460 get_byte_growing_buffer (WView *view, offset_type byte_index)
462 offset_type pageno = byte_index / VIEW_PAGE_SIZE;
463 offset_type pageindex = byte_index % VIEW_PAGE_SIZE;
465 assert (view->growbuf_in_use);
467 if ((size_t) pageno != pageno)
468 return -1;
470 view_growbuf_read_until (view, byte_index + 1);
471 if (view->growbuf_blocks == 0)
472 return -1;
473 if (pageno < view->growbuf_blocks - 1)
474 return view->growbuf_blockptr[pageno][pageindex];
475 if (pageno == view->growbuf_blocks - 1 && pageindex < view->growbuf_lastindex)
476 return view->growbuf_blockptr[pageno][pageindex];
477 return -1;
480 /* {{{ Data sources }}} */
483 The data source provides the viewer with data from either a file, a
484 string or the output of a command. The get_byte() function can be
485 used to get the value of a byte at a specific offset. If the offset
486 is out of range, -1 is returned. The function get_byte_indexed(a,b)
487 returns the byte at the offset a+b, or -1 if a+b is out of range.
489 The view_set_byte() function has the effect that later calls to
490 get_byte() will return the specified byte for this offset. This
491 function is designed only for use by the hexedit component after
492 saving its changes. Inspect the source before you want to use it for
493 other purposes.
495 The view_get_filesize() function returns the current size of the
496 data source. If the growing buffer is used, this size may increase
497 later on. Use the view_may_still_grow() function when you want to
498 know if the size can change later.
501 static offset_type
502 view_get_filesize (WView *view)
504 switch (view->datasource) {
505 case DS_NONE:
506 return 0;
507 case DS_STDIO_PIPE:
508 case DS_VFS_PIPE:
509 return view_growbuf_filesize (view);
510 case DS_FILE:
511 return view->ds_file_filesize;
512 case DS_STRING:
513 return view->ds_string_len;
514 default:
515 assert(!"Unknown datasource type");
516 return 0;
520 static inline gboolean
521 view_may_still_grow (WView *view)
523 return (view->growbuf_in_use && !view->growbuf_finished);
526 /* returns TRUE if the idx lies in the half-open interval
527 * [offset; offset + size), FALSE otherwise.
529 static inline gboolean
530 already_loaded (offset_type offset, offset_type idx, size_t size)
532 return (offset <= idx && idx - offset < size);
535 static inline void
536 view_file_load_data (WView *view, offset_type byte_index)
538 offset_type blockoffset;
539 ssize_t res;
540 size_t bytes_read;
542 assert (view->datasource == DS_FILE);
544 if (already_loaded (view->ds_file_offset, byte_index, view->ds_file_datalen))
545 return;
547 if (byte_index >= view->ds_file_filesize)
548 return;
550 blockoffset = offset_rounddown (byte_index, view->ds_file_datasize);
551 if (mc_lseek (view->ds_file_fd, blockoffset, SEEK_SET) == -1)
552 goto error;
554 bytes_read = 0;
555 while (bytes_read < view->ds_file_datasize) {
556 res = mc_read (view->ds_file_fd, view->ds_file_data + bytes_read, view->ds_file_datasize - bytes_read);
557 if (res == -1)
558 goto error;
559 if (res == 0)
560 break;
561 bytes_read += (size_t) res;
563 view->ds_file_offset = blockoffset;
564 if (bytes_read > view->ds_file_filesize - view->ds_file_offset) {
565 /* the file has grown in the meantime -- stick to the old size */
566 view->ds_file_datalen = view->ds_file_filesize - view->ds_file_offset;
567 } else {
568 view->ds_file_datalen = bytes_read;
570 return;
572 error:
573 view->ds_file_datalen = 0;
576 static int
577 get_byte_none (WView *view, offset_type byte_index)
579 assert (view->datasource == DS_NONE);
580 (void) &view;
581 (void) byte_index;
582 return -1;
585 static inline int
586 get_byte_file (WView *view, offset_type byte_index)
588 assert (view->datasource == DS_FILE);
590 view_file_load_data (view, byte_index);
591 if (already_loaded(view->ds_file_offset, byte_index, view->ds_file_datalen))
592 return view->ds_file_data[byte_index - view->ds_file_offset];
593 return -1;
596 static int
597 get_byte_string (WView *view, offset_type byte_index)
599 assert (view->datasource == DS_STRING);
600 if (byte_index < view->ds_string_len)
601 return view->ds_string_data[byte_index];
602 return -1;
605 static inline int
606 get_byte (WView *view, offset_type offset)
608 switch (view->datasource) {
609 case DS_STDIO_PIPE:
610 case DS_VFS_PIPE:
611 return get_byte_growing_buffer (view, offset);
612 case DS_FILE:
613 return get_byte_file (view, offset);
614 case DS_STRING:
615 return get_byte_string (view, offset);
616 case DS_NONE:
617 return get_byte_none (view, offset);
619 assert(!"Unknown datasource type");
620 return -1;
623 static inline int
624 get_byte_indexed (WView *view, offset_type base, offset_type ofs)
626 if (base <= OFFSETTYPE_MAX - ofs)
627 return get_byte (view, base + ofs);
628 return -1;
631 static void
632 view_set_byte (WView *view, offset_type offset, byte b)
634 (void) &b;
635 assert (offset < view_get_filesize (view));
636 assert (view->datasource == DS_FILE);
637 view->ds_file_datalen = 0; /* just force reloading */
640 static void
641 view_set_datasource_none (WView *view)
643 view->datasource = DS_NONE;
646 static void
647 view_set_datasource_vfs_pipe (WView *view, int fd)
649 assert (fd != -1);
650 view->datasource = DS_VFS_PIPE;
651 view->ds_vfs_pipe = fd;
653 view_init_growbuf (view);
656 static void
657 view_set_datasource_stdio_pipe (WView *view, FILE *fp)
659 assert (fp != NULL);
660 view->datasource = DS_STDIO_PIPE;
661 view->ds_stdio_pipe = fp;
663 view_init_growbuf (view);
666 static void
667 view_set_datasource_string (WView *view, const char *s)
669 view->datasource = DS_STRING;
670 view->ds_string_data = (byte *) g_strdup (s);
671 view->ds_string_len = strlen (s);
674 static void
675 view_set_datasource_file (WView *view, int fd, const struct stat *st)
677 view->datasource = DS_FILE;
678 view->ds_file_fd = fd;
679 view->ds_file_filesize = st->st_size;
680 view->ds_file_offset = 0;
681 view->ds_file_data = g_malloc (4096);
682 view->ds_file_datalen = 0;
683 view->ds_file_datasize = 4096;
686 static void
687 view_close_datasource (WView *view)
689 switch (view->datasource) {
690 case DS_NONE:
691 break;
692 case DS_STDIO_PIPE:
693 if (view->ds_stdio_pipe != NULL) {
694 (void) pclose (view->ds_stdio_pipe);
695 display (view);
696 close_error_pipe (D_NORMAL, NULL);
697 view->ds_stdio_pipe = NULL;
699 view_growbuf_free (view);
700 break;
701 case DS_VFS_PIPE:
702 if (view->ds_vfs_pipe != -1) {
703 (void) mc_close (view->ds_vfs_pipe);
704 view->ds_vfs_pipe = -1;
706 view_growbuf_free (view);
707 break;
708 case DS_FILE:
709 (void) mc_close (view->ds_file_fd);
710 view->ds_file_fd = -1;
711 g_free (view->ds_file_data);
712 view->ds_file_data = NULL;
713 break;
714 case DS_STRING:
715 g_free (view->ds_string_data);
716 view->ds_string_data = NULL;
717 break;
718 default:
719 assert (!"Unknown datasource type");
721 view->datasource = DS_NONE;
724 /* {{{ The Coordinate Cache }}} */
727 This cache provides you with a fast lookup to map file offsets into
728 line/column pairs and vice versa. The interface to the mapping is
729 provided by the functions view_coord_to_offset() and
730 view_offset_to_coord().
732 The cache is implemented as a simple sorted array holding entries
733 that map some of the offsets to their line/column pair. Entries that
734 are not cached themselves are interpolated (exactly) from their
735 neighbor entries. The algorithm used for determining the line/column
736 for a specific offset needs to be kept synchronized with the one used
737 in display().
740 enum ccache_type {
741 CCACHE_OFFSET,
742 CCACHE_LINECOL
745 static inline gboolean
746 coord_cache_entry_less (const struct coord_cache_entry *a,
747 const struct coord_cache_entry *b, enum ccache_type crit,
748 gboolean nroff_mode)
750 if (crit == CCACHE_OFFSET)
751 return (a->cc_offset < b->cc_offset);
753 if (a->cc_line < b->cc_line)
754 return TRUE;
756 if (a->cc_line == b->cc_line) {
757 if (nroff_mode) {
758 return (a->cc_nroff_column < b->cc_nroff_column);
759 } else {
760 return (a->cc_column < b->cc_column);
763 return FALSE;
766 #ifdef MC_ENABLE_DEBUGGING_CODE
767 static void view_coord_to_offset (WView *, offset_type *, offset_type, offset_type);
768 static void view_offset_to_coord (WView *, offset_type *, offset_type *, offset_type);
770 static void
771 view_ccache_dump (WView *view)
773 FILE *f;
774 offset_type offset, line, column, nextline_offset, filesize;
775 guint i;
776 const struct coord_cache_entry *cache;
778 assert (view->coord_cache != NULL);
780 filesize = view_get_filesize (view);
781 cache = &(g_array_index (view->coord_cache, struct coord_cache_entry, 0));
783 f = fopen("mcview-ccache.out", "w");
784 if (f == NULL)
785 return;
786 (void)setvbuf(f, NULL, _IONBF, 0);
788 /* cache entries */
789 for (i = 0; i < view->coord_cache->len; i++) {
790 (void) fprintf (f,
791 "entry %8u "
792 "offset %8"OFFSETTYPE_PRId" "
793 "line %8"OFFSETTYPE_PRId" "
794 "column %8"OFFSETTYPE_PRId" "
795 "nroff_column %8"OFFSETTYPE_PRId"\n",
796 (unsigned int) i, cache[i].cc_offset, cache[i].cc_line,
797 cache[i].cc_column, cache[i].cc_nroff_column);
799 (void)fprintf (f, "\n");
801 /* offset -> line/column translation */
802 for (offset = 0; offset < filesize; offset++) {
803 view_offset_to_coord (view, &line, &column, offset);
804 (void)fprintf (f,
805 "offset %8"OFFSETTYPE_PRId" "
806 "line %8"OFFSETTYPE_PRId" "
807 "column %8"OFFSETTYPE_PRId"\n",
808 offset, line, column);
811 /* line/column -> offset translation */
812 for (line = 0; TRUE; line++) {
813 view_coord_to_offset (view, &nextline_offset, line + 1, 0);
814 (void)fprintf (f, "nextline_offset %8"OFFSETTYPE_PRId"\n",
815 nextline_offset);
817 for (column = 0; TRUE; column++) {
818 view_coord_to_offset (view, &offset, line, column);
819 if (offset >= nextline_offset)
820 break;
822 (void)fprintf (f, "line %8"OFFSETTYPE_PRId" column %8"OFFSETTYPE_PRId" offset %8"OFFSETTYPE_PRId"\n",
823 line, column, offset);
826 if (nextline_offset >= filesize - 1)
827 break;
830 (void)fclose (f);
832 #endif
834 static inline gboolean
835 is_nroff_sequence (WView *view, offset_type offset)
837 int c0, c1, c2;
839 /* The following commands are ordered to speed up the calculation. */
841 c1 = get_byte_indexed (view, offset, 1);
842 if (c1 == -1 || c1 != '\b')
843 return FALSE;
845 c0 = get_byte_indexed (view, offset, 0);
846 if (c0 == -1 || !is_printable(c0))
847 return FALSE;
849 c2 = get_byte_indexed (view, offset, 2);
850 if (c2 == -1 || !is_printable(c2))
851 return FALSE;
853 return (c0 == c2 || c0 == '_' || (c0 == '+' && c2 == 'o'));
856 /* Find and return the index of the last cache entry that is
857 * smaller than ''coord'', according to the criterion ''sort_by''. */
858 static inline guint
859 view_ccache_find (WView *view, const struct coord_cache_entry *cache,
860 const struct coord_cache_entry *coord, enum ccache_type sort_by)
862 guint base, i, limit;
864 limit = view->coord_cache->len;
865 assert (limit != 0);
867 base = 0;
868 while (limit > 1) {
869 i = base + limit / 2;
870 if (coord_cache_entry_less (coord, &cache[i], sort_by, view->text_nroff_mode)) {
871 /* continue the search in the lower half of the cache */
872 } else {
873 /* continue the search in the upper half of the cache */
874 base = i;
876 limit = (limit + 1) / 2;
878 return base;
881 /* Look up the missing components of ''coord'', which are given by
882 * ''lookup_what''. The function returns the smallest value that
883 * matches the existing components of ''coord''.
885 static void
886 view_ccache_lookup (WView *view, struct coord_cache_entry *coord,
887 enum ccache_type lookup_what)
889 guint i;
890 struct coord_cache_entry *cache, current, next, entry;
891 enum ccache_type sorter;
892 offset_type limit;
893 enum {
894 NROFF_START,
895 NROFF_BACKSPACE,
896 NROFF_CONTINUATION
897 } nroff_state;
899 if (!view->coord_cache) {
900 view->coord_cache = g_array_new (FALSE, FALSE, sizeof(struct coord_cache_entry));
901 current.cc_offset = 0;
902 current.cc_line = 0;
903 current.cc_column = 0;
904 current.cc_nroff_column = 0;
905 g_array_append_val (view->coord_cache, current);
908 sorter = (lookup_what == CCACHE_OFFSET) ? CCACHE_LINECOL : CCACHE_OFFSET;
910 retry:
911 /* find the two neighbor entries in the cache */
912 cache = &(g_array_index (view->coord_cache, struct coord_cache_entry, 0));
913 i = view_ccache_find (view, cache, coord, sorter);
914 /* now i points to the lower neighbor in the cache */
916 current = cache[i];
917 if (i + 1 < view->coord_cache->len)
918 limit = cache[i + 1].cc_offset;
919 else
920 limit = current.cc_offset + VIEW_COORD_CACHE_GRANUL;
922 entry = current;
923 nroff_state = NROFF_START;
924 for (; current.cc_offset < limit; current = next) {
925 int c, nextc;
927 if ((c = get_byte (view, current.cc_offset)) == -1)
928 break;
930 if (!coord_cache_entry_less (&current, coord, sorter, view->text_nroff_mode)) {
931 if (lookup_what == CCACHE_OFFSET
932 && view->text_nroff_mode
933 && nroff_state != NROFF_START) {
934 /* don't break here */
935 } else {
936 break;
940 /* Provide useful default values for ''next'' */
941 next.cc_offset = current.cc_offset + 1;
942 next.cc_line = current.cc_line;
943 next.cc_column = current.cc_column + 1;
944 next.cc_nroff_column = current.cc_nroff_column + 1;
946 /* and override some of them as necessary. */
947 if (c == '\r') {
948 nextc = get_byte_indexed(view, current.cc_offset, 1);
950 /* Ignore '\r' if it is followed by '\r' or '\n'. If it is
951 * followed by anything else, it is a Mac line ending and
952 * produces a line break.
954 if (nextc == '\r' || nextc == '\n') {
955 next.cc_column = current.cc_column;
956 next.cc_nroff_column = current.cc_nroff_column;
957 } else {
958 next.cc_line = current.cc_line + 1;
959 next.cc_column = 0;
960 next.cc_nroff_column = 0;
963 } else if (nroff_state == NROFF_BACKSPACE) {
964 next.cc_nroff_column = current.cc_nroff_column - 1;
966 } else if (c == '\t') {
967 next.cc_column = offset_rounddown (current.cc_column, 8) + 8;
968 next.cc_nroff_column =
969 offset_rounddown (current.cc_nroff_column, 8) + 8;
971 } else if (c == '\n') {
972 next.cc_line = current.cc_line + 1;
973 next.cc_column = 0;
974 next.cc_nroff_column = 0;
976 } else {
977 /* Use all default values from above */
980 switch (nroff_state) {
981 case NROFF_START:
982 case NROFF_CONTINUATION:
983 if (is_nroff_sequence (view, current.cc_offset))
984 nroff_state = NROFF_BACKSPACE;
985 else
986 nroff_state = NROFF_START;
987 break;
988 case NROFF_BACKSPACE:
989 nroff_state = NROFF_CONTINUATION;
990 break;
993 /* Cache entries must guarantee that for each i < j,
994 * line[i] <= line[j] and column[i] < column[j]. In the case of
995 * nroff sequences and '\r' characters, this is not guaranteed,
996 * so we cannot save them. */
997 if (nroff_state == NROFF_START && c != '\r')
998 entry = next;
1001 if (i + 1 == view->coord_cache->len && entry.cc_offset != cache[i].cc_offset) {
1002 g_array_append_val (view->coord_cache, entry);
1003 goto retry;
1006 if (lookup_what == CCACHE_OFFSET) {
1007 coord->cc_offset = current.cc_offset;
1008 } else {
1009 coord->cc_line = current.cc_line;
1010 coord->cc_column = current.cc_column;
1011 coord->cc_nroff_column = current.cc_nroff_column;
1015 static void
1016 view_coord_to_offset (WView *view, offset_type *ret_offset,
1017 offset_type line, offset_type column)
1019 struct coord_cache_entry coord;
1021 coord.cc_line = line;
1022 coord.cc_column = column;
1023 coord.cc_nroff_column = column;
1024 view_ccache_lookup (view, &coord, CCACHE_OFFSET);
1025 *ret_offset = coord.cc_offset;
1028 static void
1029 view_offset_to_coord (WView *view, offset_type *ret_line,
1030 offset_type *ret_column, offset_type offset)
1032 struct coord_cache_entry coord;
1034 coord.cc_offset = offset;
1035 view_ccache_lookup (view, &coord, CCACHE_LINECOL);
1036 *ret_line = coord.cc_line;
1037 *ret_column = (view->text_nroff_mode)
1038 ? coord.cc_nroff_column
1039 : coord.cc_column;
1042 /* {{{ Cursor Movement }}} */
1045 The following variables have to do with the current position and are
1046 updated by the cursor movement functions.
1048 In hex view and wrapped text view mode, dpy_start marks the offset of
1049 the top-left corner on the screen, in non-wrapping text mode it is
1050 the beginning of the current line. In hex mode, hex_cursor is the
1051 offset of the cursor. In non-wrapping text mode, dpy_text_column is
1052 the number of columns that are hidden on the left side on the screen.
1054 In hex mode, dpy_start is updated by the view_fix_cursor_position()
1055 function in order to keep the other functions simple. In
1056 non-wrapping text mode dpy_start and dpy_text_column are normalized
1057 such that dpy_text_column < view_get_datacolumns().
1060 /* prototypes for functions used by view_moveto_bottom() */
1061 static void view_move_up (WView *, offset_type);
1062 static void view_moveto_bol (WView *);
1064 static void
1065 view_scroll_to_cursor (WView *view)
1067 if (view->hex_mode) {
1068 const offset_type bytes = view->bytes_per_line;
1069 const offset_type displaysize = view->data_area.height * bytes;
1070 const offset_type cursor = view->hex_cursor;
1071 offset_type topleft = view->dpy_start;
1073 if (topleft + displaysize <= cursor)
1074 topleft = offset_rounddown (cursor, bytes)
1075 - (displaysize - bytes);
1076 if (cursor < topleft)
1077 topleft = offset_rounddown (cursor, bytes);
1078 view->dpy_start = topleft;
1079 } else if (view->text_wrap_mode) {
1080 offset_type line, col, columns;
1082 columns = view->data_area.width;
1083 view_offset_to_coord (view, &line, &col, view->dpy_start + view->dpy_text_column);
1084 if (columns != 0)
1085 col = offset_rounddown (col, columns);
1086 view_coord_to_offset (view, &(view->dpy_start), line, col);
1087 view->dpy_text_column = 0;
1088 } else {
1089 /* nothing to do */
1093 static void
1094 view_movement_fixups (WView *view, gboolean reset_search)
1096 view_scroll_to_cursor (view);
1097 if (reset_search) {
1098 view->search_start = view->dpy_start;
1099 view->search_length = 0;
1101 view->dirty++;
1104 static void
1105 view_moveto_top (WView *view)
1107 view->dpy_start = 0;
1108 view->hex_cursor = 0;
1109 view->dpy_text_column = 0;
1110 view_movement_fixups (view, TRUE);
1113 static void
1114 view_moveto_bottom (WView *view)
1116 offset_type datalines, lines_up, filesize, last_offset;
1118 if (view->growbuf_in_use)
1119 view_growbuf_read_until (view, OFFSETTYPE_MAX);
1121 filesize = view_get_filesize (view);
1122 last_offset = offset_doz(filesize, 1);
1123 datalines = view->data_area.height;
1124 lines_up = offset_doz(datalines, 1);
1126 if (view->hex_mode) {
1127 view->hex_cursor = filesize;
1128 view_move_up (view, lines_up);
1129 view->hex_cursor = last_offset;
1130 } else {
1131 view->dpy_start = last_offset;
1132 view_moveto_bol (view);
1133 view_move_up (view, lines_up);
1135 view_movement_fixups (view, TRUE);
1138 static void
1139 view_moveto_bol (WView *view)
1141 if (view->hex_mode) {
1142 view->hex_cursor -= view->hex_cursor % view->bytes_per_line;
1143 } else if (view->text_wrap_mode) {
1144 /* do nothing */
1145 } else {
1146 offset_type line, column;
1147 view_offset_to_coord (view, &line, &column, view->dpy_start);
1148 view_coord_to_offset (view, &(view->dpy_start), line, 0);
1149 view->dpy_text_column = 0;
1151 view_movement_fixups (view, TRUE);
1154 static void
1155 view_moveto_eol (WView *view)
1157 if (view->hex_mode) {
1158 offset_type filesize, bol;
1160 bol = offset_rounddown (view->hex_cursor, view->bytes_per_line);
1161 if (get_byte_indexed (view, bol, view->bytes_per_line - 1) != -1) {
1162 view->hex_cursor = bol + view->bytes_per_line - 1;
1163 } else {
1164 filesize = view_get_filesize (view);
1165 view->hex_cursor = offset_doz(filesize, 1);
1167 } else if (view->text_wrap_mode) {
1168 /* nothing to do */
1169 } else {
1170 offset_type line, col;
1172 view_offset_to_coord (view, &line, &col, view->dpy_start);
1173 view_coord_to_offset (view, &(view->dpy_start), line, OFFSETTYPE_MAX);
1175 view_movement_fixups (view, FALSE);
1178 static void
1179 view_moveto_offset (WView *view, offset_type offset)
1181 if (view->hex_mode) {
1182 view->hex_cursor = offset;
1183 view->dpy_start = offset - offset % view->bytes_per_line;
1184 } else {
1185 view->dpy_start = offset;
1187 view_movement_fixups (view, TRUE);
1190 static void
1191 view_moveto (WView *view, offset_type line, offset_type col)
1193 offset_type offset;
1195 view_coord_to_offset (view, &offset, line, col);
1196 view_moveto_offset (view, offset);
1199 static void
1200 view_move_up (WView *view, offset_type lines)
1202 if (view->hex_mode) {
1203 offset_type bytes = lines * view->bytes_per_line;
1204 if (view->hex_cursor >= bytes) {
1205 view->hex_cursor -= bytes;
1206 if (view->hex_cursor < view->dpy_start)
1207 view->dpy_start = offset_doz (view->dpy_start, bytes);
1208 } else {
1209 view->hex_cursor %= view->bytes_per_line;
1211 } else if (view->text_wrap_mode) {
1212 const screen_dimen width = view->data_area.width;
1213 offset_type i, col, line, linestart;
1215 for (i = 0; i < lines; i++) {
1216 view_offset_to_coord (view, &line, &col, view->dpy_start);
1217 if (col >= width) {
1218 col -= width;
1219 } else if (line >= 1) {
1220 view_coord_to_offset (view, &linestart, line, 0);
1221 view_offset_to_coord (view, &line, &col, linestart - 1);
1223 /* if the only thing that would be displayed were a
1224 * single newline character, advance to the previous
1225 * part of the line. */
1226 if (col > 0 && col % width == 0)
1227 col -= width;
1228 else
1229 col -= col % width;
1230 } else {
1231 /* nothing to do */
1233 view_coord_to_offset (view, &(view->dpy_start), line, col);
1235 } else {
1236 offset_type line, column;
1238 view_offset_to_coord (view, &line, &column, view->dpy_start);
1239 line = offset_doz(line, lines);
1240 view_coord_to_offset (view, &(view->dpy_start), line, column);
1242 view_movement_fixups (view, (lines != 1));
1245 static void
1246 view_move_down (WView *view, offset_type lines)
1248 if (view->hex_mode) {
1249 offset_type i, limit, last_byte;
1251 last_byte = view_get_filesize (view);
1252 if (last_byte >= (offset_type) view->bytes_per_line)
1253 limit = last_byte - view->bytes_per_line;
1254 else
1255 limit = 0;
1256 for (i = 0; i < lines && view->hex_cursor < limit; i++) {
1257 view->hex_cursor += view->bytes_per_line;
1258 if (lines != 1)
1259 view->dpy_start += view->bytes_per_line;
1262 } else if (view->dpy_end == view_get_filesize (view)) {
1263 /* don't move further down. There's nothing more to see. */
1265 } else if (view->text_wrap_mode) {
1266 offset_type line, col, i;
1268 for (i = 0; i < lines; i++) {
1269 offset_type new_offset, chk_line, chk_col;
1271 view_offset_to_coord (view, &line, &col, view->dpy_start);
1272 col += view->data_area.width;
1273 view_coord_to_offset (view, &new_offset, line, col);
1275 /* skip to the next line if the only thing that would be
1276 * displayed is the newline character. */
1277 view_offset_to_coord (view, &chk_line, &chk_col, new_offset);
1278 if (chk_line == line && chk_col == col
1279 && get_byte (view, new_offset) == '\n')
1280 new_offset++;
1282 view->dpy_start = new_offset;
1285 } else {
1286 offset_type line, col;
1288 view_offset_to_coord (view, &line, &col, view->dpy_start);
1289 line += lines;
1290 view_coord_to_offset (view, &(view->dpy_start), line, col);
1292 view_movement_fixups (view, (lines != 1));
1295 static void
1296 view_move_left (WView *view, offset_type columns)
1298 if (view->hex_mode) {
1299 assert (columns == 1);
1300 if (view->hexview_in_text || !view->hexedit_lownibble) {
1301 if (view->hex_cursor > 0)
1302 view->hex_cursor--;
1304 if (!view->hexview_in_text)
1305 view->hexedit_lownibble = !view->hexedit_lownibble;
1306 } else if (view->text_wrap_mode) {
1307 /* nothing to do */
1308 } else {
1309 if (view->dpy_text_column >= columns)
1310 view->dpy_text_column -= columns;
1311 else
1312 view->dpy_text_column = 0;
1314 view_movement_fixups (view, FALSE);
1317 static void
1318 view_move_right (WView *view, offset_type columns)
1320 if (view->hex_mode) {
1321 assert (columns == 1);
1322 if (view->hexview_in_text || view->hexedit_lownibble) {
1323 if (get_byte_indexed (view, view->hex_cursor, 1) != -1)
1324 view->hex_cursor++;
1326 if (!view->hexview_in_text)
1327 view->hexedit_lownibble = !view->hexedit_lownibble;
1328 } else if (view->text_wrap_mode) {
1329 /* nothing to do */
1330 } else {
1331 view->dpy_text_column += columns;
1333 view_movement_fixups (view, FALSE);
1336 /* {{{ Toggling of viewer modes }}} */
1338 static void
1339 view_toggle_hex_mode (WView *view)
1341 view->hex_mode = !view->hex_mode;
1343 if (view->hex_mode) {
1344 view->hex_cursor = view->dpy_start;
1345 view->dpy_start =
1346 offset_rounddown (view->dpy_start, view->bytes_per_line);
1347 view->widget.options |= W_WANT_CURSOR;
1348 } else {
1349 view->dpy_start = view->hex_cursor;
1350 view_moveto_bol (view);
1351 view->widget.options &= ~W_WANT_CURSOR;
1353 altered_hex_mode = 1;
1354 view->dpy_bbar_dirty = TRUE;
1355 view->dirty++;
1358 static void
1359 view_toggle_hexedit_mode (WView *view)
1361 view->hexedit_mode = !view->hexedit_mode;
1362 view->dpy_bbar_dirty = TRUE;
1363 view->dirty++;
1366 static void
1367 view_toggle_wrap_mode (WView *view)
1369 view->text_wrap_mode = !view->text_wrap_mode;
1370 if (view->text_wrap_mode) {
1371 view_scroll_to_cursor (view);
1372 } else {
1373 offset_type line;
1375 view_offset_to_coord (view, &line, &(view->dpy_text_column), view->dpy_start);
1376 view_coord_to_offset (view, &(view->dpy_start), line, 0);
1378 view->dpy_bbar_dirty = TRUE;
1379 view->dirty++;
1382 static void
1383 view_toggle_nroff_mode (WView *view)
1385 view->text_nroff_mode = !view->text_nroff_mode;
1386 altered_nroff_flag = 1;
1387 view->dpy_bbar_dirty = TRUE;
1388 view->dirty++;
1391 static void
1392 view_toggle_magic_mode (WView *view)
1394 char *filename, *command;
1396 altered_magic_flag = 1;
1397 view->magic_mode = !view->magic_mode;
1398 filename = g_strdup (view->filename);
1399 command = g_strdup (view->command);
1401 view_done (view);
1402 view_load (view, command, filename, 0);
1403 g_free (filename);
1404 g_free (command);
1405 view->dpy_bbar_dirty = TRUE;
1406 view->dirty++;
1409 /* {{{ Miscellaneous functions }}} */
1411 static void
1412 view_done (WView *view)
1414 /* Save current file position */
1415 if (mcview_remember_file_position && view->filename != NULL) {
1416 char *canon_fname;
1417 offset_type line, col;
1419 canon_fname = vfs_canon (view->filename);
1420 view_offset_to_coord (view, &line, &col, view->dpy_start);
1421 save_file_position (canon_fname, line + 1, col);
1422 g_free (canon_fname);
1425 /* Write back the global viewer mode */
1426 default_hex_mode = view->hex_mode;
1427 default_nroff_flag = view->text_nroff_mode;
1428 default_magic_flag = view->magic_mode;
1429 global_wrap_mode = view->text_wrap_mode;
1431 /* Free memory used by the viewer */
1433 /* view->widget needs no destructor */
1435 g_free (view->filename), view->filename = NULL;
1436 g_free (view->command), view->command = NULL;
1438 view_close_datasource (view);
1439 /* the growing buffer is freed with the datasource */
1441 if (view->coord_cache) {
1442 g_array_free (view->coord_cache, TRUE), view->coord_cache = NULL;
1445 view_hexedit_free_change_list (view);
1446 /* FIXME: what about view->search_exp? */
1449 static void
1450 view_show_error (WView *view, const char *msg)
1452 view_close_datasource (view);
1453 if (view_is_in_panel (view)) {
1454 view_set_datasource_string (view, msg);
1455 } else {
1456 message (D_ERROR, MSG_ERROR, "%s", msg);
1460 static gboolean
1461 view_load_command_output (WView *view, const char *command)
1463 FILE *fp;
1465 view_close_datasource (view);
1467 open_error_pipe ();
1468 if ((fp = popen (command, "r")) == NULL) {
1469 /* Avoid two messages. Message from stderr has priority. */
1470 display (view);
1471 if (!close_error_pipe (view_is_in_panel (view) ? -1 : D_ERROR, NULL))
1472 view_show_error (view, _(" Cannot spawn child process "));
1473 return FALSE;
1476 /* First, check if filter produced any output */
1477 view_set_datasource_stdio_pipe (view, fp);
1478 if (get_byte (view, 0) == -1) {
1479 view_close_datasource (view);
1481 /* Avoid two messages. Message from stderr has priority. */
1482 display (view);
1483 if (!close_error_pipe (view_is_in_panel (view) ? -1 : D_ERROR, NULL))
1484 view_show_error (view, _("Empty output from child filter"));
1485 return FALSE;
1487 return TRUE;
1490 gboolean
1491 view_load (WView *view, const char *command, const char *file,
1492 int start_line)
1494 int i, type;
1495 int fd = -1;
1496 char tmp[BUF_MEDIUM];
1497 struct stat st;
1498 gboolean retval = FALSE;
1500 assert (view->bytes_per_line != 0);
1501 view_done (view);
1503 /* Set up the state */
1504 view_set_datasource_none (view);
1505 view->filename = g_strdup (file);
1506 view->command = 0;
1508 /* Clear the markers */
1509 view->marker = 0;
1510 for (i = 0; i < 10; i++)
1511 view->marks[i] = 0;
1513 if (!view_is_in_panel (view)) {
1514 view->dpy_text_column = 0;
1517 if (command && (view->magic_mode || file == NULL || file[0] == '\0')) {
1518 retval = view_load_command_output (view, command);
1519 } else if (file != NULL && file[0] != '\0') {
1520 /* Open the file */
1521 if ((fd = mc_open (file, O_RDONLY | O_NONBLOCK)) == -1) {
1522 snprintf (tmp, sizeof (tmp), _(" Cannot open \"%s\"\n %s "),
1523 file, unix_error_string (errno));
1524 view_show_error (view, tmp);
1525 goto finish;
1528 /* Make sure we are working with a regular file */
1529 if (mc_fstat (fd, &st) == -1) {
1530 mc_close (fd);
1531 snprintf (tmp, sizeof (tmp), _(" Cannot stat \"%s\"\n %s "),
1532 file, unix_error_string (errno));
1533 view_show_error (view, tmp);
1534 goto finish;
1537 if (!S_ISREG (st.st_mode)) {
1538 mc_close (fd);
1539 view_show_error (view, _(" Cannot view: not a regular file "));
1540 goto finish;
1543 if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1) {
1544 /* Must be one of those nice files that grow (/proc) */
1545 view_set_datasource_vfs_pipe (view, fd);
1546 } else {
1547 type = get_compression_type (fd);
1549 if (view->magic_mode && (type != COMPRESSION_NONE)) {
1550 g_free (view->filename);
1551 view->filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
1553 view_set_datasource_file (view, fd, &st);
1555 retval = TRUE;
1558 finish:
1559 view->command = g_strdup (command);
1560 view->dpy_start = 0;
1561 view->search_start = 0;
1562 view->search_length = 0;
1563 view->dpy_text_column = 0;
1564 view->last_search = 0; /* Start a new search */
1566 assert (view->bytes_per_line != 0);
1567 if (mcview_remember_file_position && file != NULL && start_line == 0) {
1568 long line, col;
1569 char *canon_fname;
1571 canon_fname = vfs_canon (file);
1572 load_file_position (file, &line, &col);
1573 g_free (canon_fname);
1574 view_moveto (view, offset_doz(line, 1), col);
1575 } else if (start_line > 0) {
1576 view_moveto (view, start_line - 1, 0);
1579 view->hexedit_lownibble = FALSE;
1580 view->hexview_in_text = FALSE;
1581 view->change_list = NULL;
1583 return retval;
1586 /* {{{ Display management }}} */
1588 static void
1589 view_update_bytes_per_line (WView *view)
1591 const screen_dimen cols = view->data_area.width;
1592 int bytes;
1594 if (cols < 8 + 17)
1595 bytes = 4;
1596 else
1597 bytes = 4 * ((cols - 8) / ((cols < 80) ? 17 : 18));
1598 assert(bytes != 0);
1600 view->bytes_per_line = bytes;
1601 view->dirty = max_dirt_limit + 1; /* To force refresh */
1604 static void
1605 view_percent (WView *view, offset_type p)
1607 const screen_dimen top = view->status_area.top;
1608 const screen_dimen right = view->status_area.left + view->status_area.width;
1609 const screen_dimen height = view->status_area.height;
1610 int percent;
1611 offset_type filesize;
1613 if (height < 1 || right < 4)
1614 return;
1615 if (view_may_still_grow (view))
1616 return;
1617 filesize = view_get_filesize (view);
1619 if (filesize == 0 || view->dpy_end == filesize)
1620 percent = 100;
1621 else if (p > (INT_MAX / 100))
1622 percent = p / (filesize / 100);
1623 else
1624 percent = p * 100 / filesize;
1626 widget_move (view, top, right - 4);
1627 tty_printf ("%3d%%", percent);
1630 static void
1631 view_display_status (WView *view)
1633 const screen_dimen top = view->status_area.top;
1634 const screen_dimen left = view->status_area.left;
1635 const screen_dimen width = view->status_area.width;
1636 const screen_dimen height = view->status_area.height;
1637 const char *file_label, *file_name;
1638 screen_dimen file_label_width;
1639 int i;
1641 if (height < 1)
1642 return;
1644 tty_setcolor (SELECTED_COLOR);
1645 widget_move (view, top, left);
1646 hline (' ', width);
1648 file_label = _("File: %s");
1649 file_label_width = strlen (file_label) - 2;
1650 file_name = view->filename ? view->filename
1651 : view->command ? view->command
1652 : "";
1654 if (width < file_label_width + 6)
1655 addstr ((char *) name_trunc (file_name, width));
1656 else {
1657 i = (width > 22 ? 22 : width) - file_label_width;
1658 tty_printf (file_label, name_trunc (file_name, i));
1659 if (width > 46) {
1660 widget_move (view, top, left + 24);
1661 /* FIXME: the format strings need to be changed when offset_type changes */
1662 if (view->hex_mode)
1663 tty_printf (_("Offset 0x%08lx"), (unsigned long) view->hex_cursor);
1664 else {
1665 offset_type line, col;
1666 view_offset_to_coord (view, &line, &col, view->dpy_start);
1667 tty_printf (_("Line %lu Col %lu"),
1668 (unsigned long) line + 1,
1669 (unsigned long) (view->text_wrap_mode ? col : view->dpy_text_column));
1672 if (width > 62) {
1673 offset_type filesize;
1674 filesize = view_get_filesize (view);
1675 widget_move (view, top, left + 43);
1676 if (!view_may_still_grow (view)) {
1677 tty_printf (_("%s bytes"), size_trunc (filesize));
1678 } else {
1679 tty_printf (_(">= %s bytes"), size_trunc (filesize));
1682 if (width > 26) {
1683 view_percent (view, view->hex_mode
1684 ? view->hex_cursor
1685 : view->dpy_end);
1688 tty_setcolor (SELECTED_COLOR);
1691 static inline void
1692 view_display_clean (WView *view)
1694 tty_setcolor (NORMAL_COLOR);
1695 widget_erase ((Widget *) view);
1696 if (view->dpy_frame_size != 0) {
1697 draw_double_box (view->widget.parent, view->widget.y,
1698 view->widget.x, view->widget.lines,
1699 view->widget.cols);
1703 typedef enum {
1704 MARK_NORMAL,
1705 MARK_SELECTED,
1706 MARK_CURSOR,
1707 MARK_CHANGED
1708 } mark_t;
1710 static inline int
1711 view_count_backspaces (WView *view, off_t offset)
1713 int backspaces = 0;
1714 while (offset >= 2 * backspaces
1715 && get_byte (view, offset - 2 * backspaces) == '\b')
1716 backspaces++;
1717 return backspaces;
1720 static void
1721 view_display_ruler (WView *view)
1723 static const char ruler_chars[] = "|----*----";
1724 const screen_dimen top = view->ruler_area.top;
1725 const screen_dimen left = view->ruler_area.left;
1726 const screen_dimen width = view->ruler_area.width;
1727 const screen_dimen height = view->ruler_area.height;
1728 const screen_dimen line_row = (ruler == RULER_TOP) ? 0 : 1;
1729 const screen_dimen nums_row = (ruler == RULER_TOP) ? 1 : 0;
1731 char r_buff[10];
1732 offset_type cl;
1733 screen_dimen c;
1735 if (ruler == RULER_NONE || height < 1)
1736 return;
1738 tty_setcolor (MARKED_COLOR);
1739 for (c = 0; c < width; c++) {
1740 cl = view->dpy_text_column + c;
1741 if (line_row < height) {
1742 widget_move (view, top + line_row, left + c);
1743 tty_print_char (ruler_chars[cl % 10]);
1746 if ((cl != 0) && (cl % 10) == 0) {
1747 snprintf (r_buff, sizeof (r_buff), "%"OFFSETTYPE_PRId, cl);
1748 if (nums_row < height) {
1749 widget_move (view, top + nums_row, left + c - 1);
1750 tty_print_string (r_buff);
1754 attrset (NORMAL_COLOR);
1757 static void
1758 view_display_hex (WView *view)
1760 const screen_dimen top = view->data_area.top;
1761 const screen_dimen left = view->data_area.left;
1762 const screen_dimen height = view->data_area.height;
1763 const screen_dimen width = view->data_area.width;
1764 const int ngroups = view->bytes_per_line / 4;
1765 const screen_dimen text_start =
1766 8 + 13 * ngroups + ((width < 80) ? 0 : (ngroups - 1 + 1));
1767 /* 8 characters are used for the file offset, and every hex group
1768 * takes 13 characters. On ``big'' screens, the groups are separated
1769 * by an extra vertical line, and there is an extra space before the
1770 * text column.
1773 screen_dimen row, col;
1774 offset_type from;
1775 int c;
1776 mark_t boldflag = MARK_NORMAL;
1777 struct hexedit_change_node *curr = view->change_list;
1778 size_t i;
1780 char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */
1781 int bytes; /* Number of bytes already printed on the line */
1783 view_display_clean (view);
1785 /* Find the first displayable changed byte */
1786 from = view->dpy_start;
1787 while (curr && (curr->offset < from)) {
1788 curr = curr->next;
1791 for (row = 0; get_byte (view, from) != -1 && row < height; row++) {
1792 col = 0;
1794 /* Print the hex offset */
1795 snprintf (hex_buff, sizeof (hex_buff), "%08"OFFSETTYPE_PRIX" ", from);
1796 widget_move (view, top + row, left);
1797 tty_setcolor (MARKED_COLOR);
1798 for (i = 0; col < width && hex_buff[i] != '\0'; i++) {
1799 tty_print_char(hex_buff[i]);
1800 col += 1;
1802 tty_setcolor (NORMAL_COLOR);
1804 for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++) {
1806 if ((c = get_byte (view, from)) == -1)
1807 break;
1809 /* Save the cursor position for view_place_cursor() */
1810 if (from == view->hex_cursor && !view->hexview_in_text) {
1811 view->cursor_row = row;
1812 view->cursor_col = col;
1815 /* Determine the state of the current byte */
1816 boldflag =
1817 (from == view->hex_cursor) ? MARK_CURSOR
1818 : (curr != NULL && from == curr->offset) ? MARK_CHANGED
1819 : (view->search_start <= from &&
1820 from < view->search_start + view->search_length
1821 ) ? MARK_SELECTED
1822 : MARK_NORMAL;
1824 /* Determine the value of the current byte */
1825 if (curr != NULL && from == curr->offset) {
1826 c = curr->value;
1827 curr = curr->next;
1830 /* Select the color for the hex number */
1831 tty_setcolor (
1832 boldflag == MARK_NORMAL ? NORMAL_COLOR :
1833 boldflag == MARK_SELECTED ? MARKED_COLOR :
1834 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
1835 /* boldflag == MARK_CURSOR */
1836 view->hexview_in_text ? MARKED_SELECTED_COLOR :
1837 VIEW_UNDERLINED_COLOR);
1839 /* Print the hex number */
1840 widget_move (view, top + row, left + col);
1841 if (col < width) {
1842 tty_print_char (hex_char[c / 16]);
1843 col += 1;
1845 if (col < width) {
1846 tty_print_char (hex_char[c % 16]);
1847 col += 1;
1850 /* Print the separator */
1851 tty_setcolor (NORMAL_COLOR);
1852 if (bytes != view->bytes_per_line - 1) {
1853 if (col < width) {
1854 tty_print_char (' ');
1855 col += 1;
1858 /* After every four bytes, print a group separator */
1859 if (bytes % 4 == 3) {
1860 if (view->data_area.width >= 80 && col < width) {
1861 tty_print_one_vline ();
1862 col += 1;
1864 if (col < width) {
1865 tty_print_char (' ');
1866 col += 1;
1871 /* Select the color for the character; this differs from the
1872 * hex color when boldflag == MARK_CURSOR */
1873 tty_setcolor (
1874 boldflag == MARK_NORMAL ? NORMAL_COLOR :
1875 boldflag == MARK_SELECTED ? MARKED_COLOR :
1876 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
1877 /* boldflag == MARK_CURSOR */
1878 view->hexview_in_text ? VIEW_UNDERLINED_COLOR :
1879 MARKED_SELECTED_COLOR);
1881 c = convert_to_display_c (c);
1882 if (!is_printable (c))
1883 c = '.';
1885 /* Print corresponding character on the text side */
1886 if (text_start + bytes < width) {
1887 widget_move (view, top + row, left + text_start + bytes);
1888 tty_print_char (c);
1891 /* Save the cursor position for view_place_cursor() */
1892 if (from == view->hex_cursor && view->hexview_in_text) {
1893 view->cursor_row = row;
1894 view->cursor_col = text_start + bytes;
1899 /* Be polite to the other functions */
1900 tty_setcolor (NORMAL_COLOR);
1902 view_place_cursor (view);
1903 view->dpy_end = from;
1906 static void
1907 view_display_text (WView * view)
1909 const screen_dimen left = view->data_area.left;
1910 const screen_dimen top = view->data_area.top;
1911 const screen_dimen width = view->data_area.width;
1912 const screen_dimen height = view->data_area.height;
1913 screen_dimen row, col;
1914 offset_type from;
1915 int c;
1916 struct hexedit_change_node *curr = view->change_list;
1918 view_display_clean (view);
1919 view_display_ruler (view);
1921 /* Find the first displayable changed byte */
1922 from = view->dpy_start;
1923 while (curr && (curr->offset < from)) {
1924 curr = curr->next;
1927 tty_setcolor (NORMAL_COLOR);
1928 for (row = 0, col = 0; row < height && (c = get_byte (view, from)) != -1; from++) {
1930 if (view->text_nroff_mode && c == '\b') {
1931 int c_prev;
1932 int c_next;
1934 if ((c_next = get_byte_indexed (view, from, 1)) != -1
1935 && is_printable (c_next)
1936 && from >= 1
1937 && (c_prev = get_byte (view, from - 1)) != -1
1938 && is_printable (c_prev)
1939 && (c_prev == c_next || c_prev == '_'
1940 || (c_prev == '+' && c_next == 'o'))) {
1941 if (col == 0) {
1942 if (row == 0) {
1943 /* We're inside an nroff character sequence at the
1944 * beginning of the screen -- just skip the
1945 * backspace and continue with the next character. */
1946 continue;
1948 row--;
1949 col = width;
1951 col--;
1952 if (c_prev == '_' && (c_next != '_' || view_count_backspaces (view, from) == 1))
1953 tty_setcolor (VIEW_UNDERLINED_COLOR);
1954 else
1955 tty_setcolor (MARKED_COLOR);
1956 continue;
1960 if ((c == '\n') || (col >= width && view->text_wrap_mode)) {
1961 col = 0;
1962 row++;
1963 if (c == '\n' || row >= height)
1964 continue;
1967 if (c == '\r') {
1968 c = get_byte_indexed(view, from, 1);
1969 if (c == '\r' || c == '\n')
1970 continue;
1971 col = 0;
1972 row++;
1973 continue;
1976 if (c == '\t') {
1977 offset_type line, column;
1978 view_offset_to_coord (view, &line, &column, from);
1979 col += (8 - column % 8);
1980 if (view->text_wrap_mode && col >= width && width != 0) {
1981 row += col / width;
1982 col %= width;
1984 continue;
1987 if (view->search_start <= from
1988 && from < view->search_start + view->search_length) {
1989 tty_setcolor (SELECTED_COLOR);
1992 if (col >= view->dpy_text_column
1993 && col - view->dpy_text_column < width) {
1994 widget_move (view, top + row, left + (col - view->dpy_text_column));
1995 c = convert_to_display_c (c);
1996 if (!is_printable (c))
1997 c = '.';
1998 tty_print_char (c);
2000 col++;
2001 tty_setcolor (NORMAL_COLOR);
2003 view->dpy_end = from;
2006 /* Displays as much data from view->dpy_start as fits on the screen */
2007 static void
2008 display (WView *view)
2010 view_compute_areas (view);
2011 if (view->hex_mode) {
2012 view_display_hex (view);
2013 } else {
2014 view_display_text (view);
2016 view_display_status (view);
2019 static void
2020 view_place_cursor (WView *view)
2022 const screen_dimen top = view->data_area.top;
2023 const screen_dimen left = view->data_area.left;
2024 screen_dimen col;
2026 col = view->cursor_col;
2027 if (!view->hexview_in_text && view->hexedit_lownibble)
2028 col++;
2029 widget_move (&view->widget, top + view->cursor_row, left + col);
2032 static void
2033 view_update (WView *view)
2035 static int dirt_limit = 1;
2037 if (view->dpy_bbar_dirty) {
2038 view->dpy_bbar_dirty = FALSE;
2039 view_labels (view);
2040 buttonbar_redraw (view->widget.parent);
2043 if (view->dirty > dirt_limit) {
2044 /* Too many updates skipped -> force a update */
2045 display (view);
2046 view->dirty = 0;
2047 /* Raise the update skipping limit */
2048 dirt_limit++;
2049 if (dirt_limit > max_dirt_limit)
2050 dirt_limit = max_dirt_limit;
2052 if (view->dirty) {
2053 if (is_idle ()) {
2054 /* We have time to update the screen properly */
2055 display (view);
2056 view->dirty = 0;
2057 if (dirt_limit > 1)
2058 dirt_limit--;
2059 } else {
2060 /* We are busy -> skipping full update,
2061 only the status line is updated */
2062 view_display_status (view);
2064 /* Here we had a refresh, if fast scrolling does not work
2065 restore the refresh, although this should not happen */
2069 /* {{{ Hex editor }}} */
2071 static void
2072 enqueue_change (struct hexedit_change_node **head,
2073 struct hexedit_change_node *node)
2075 /* chnode always either points to the head of the list or
2076 * to one of the ->next fields in the list. The value at
2077 * this location will be overwritten with the new node. */
2078 struct hexedit_change_node **chnode = head;
2080 while (*chnode != NULL && (*chnode)->offset < node->offset)
2081 chnode = &((*chnode)->next);
2083 node->next = *chnode;
2084 *chnode = node;
2087 static cb_ret_t
2088 view_handle_editkey (WView *view, int key)
2090 struct hexedit_change_node *node;
2091 byte byte_val;
2093 /* Has there been a change at this position? */
2094 node = view->change_list;
2095 while (node && (node->offset != view->hex_cursor))
2096 node = node->next;
2098 if (!view->hexview_in_text) {
2099 /* Hex editing */
2100 unsigned int hexvalue = 0;
2102 if (key >= '0' && key <= '9')
2103 hexvalue = 0 + (key - '0');
2104 else if (key >= 'A' && key <= 'F')
2105 hexvalue = 10 + (key - 'A');
2106 else if (key >= 'a' && key <= 'f')
2107 hexvalue = 10 + (key - 'a');
2108 else
2109 return MSG_NOT_HANDLED;
2111 if (node)
2112 byte_val = node->value;
2113 else
2114 byte_val = get_byte (view, view->hex_cursor);
2116 if (view->hexedit_lownibble) {
2117 byte_val = (byte_val & 0xf0) | (hexvalue);
2118 } else {
2119 byte_val = (byte_val & 0x0f) | (hexvalue << 4);
2121 } else {
2122 /* Text editing */
2123 if (key < 256 && (is_printable (key) || (key == '\n')))
2124 byte_val = key;
2125 else
2126 return MSG_NOT_HANDLED;
2128 if (!node) {
2129 node = g_new (struct hexedit_change_node, 1);
2130 node->offset = view->hex_cursor;
2131 node->value = byte_val;
2132 enqueue_change (&view->change_list, node);
2133 } else {
2134 node->value = byte_val;
2136 view->dirty++;
2137 view_update (view);
2138 view_move_right (view, 1);
2139 return MSG_HANDLED;
2142 static gboolean
2143 view_hexedit_save_changes (WView *view)
2145 struct hexedit_change_node *curr, *next;
2146 int fp, answer;
2147 char *text, *error;
2149 if (view->change_list == NULL)
2150 return TRUE;
2152 retry_save:
2153 assert (view->filename != NULL);
2154 fp = mc_open (view->filename, O_WRONLY);
2155 if (fp == -1)
2156 goto save_error;
2158 for (curr = view->change_list; curr != NULL; curr = next) {
2159 next = curr->next;
2161 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
2162 || mc_write (fp, &(curr->value), 1) != 1)
2163 goto save_error;
2165 /* delete the saved item from the change list */
2166 view->change_list = next;
2167 view->dirty++;
2168 view_set_byte (view, curr->offset, curr->value);
2169 g_free (curr);
2172 if (mc_close (fp) == -1) {
2173 error = g_strdup (strerror (errno));
2174 message (D_ERROR, _(" Save file "),
2175 _(" Error while closing the file: \n %s \n"
2176 " Data may have been written or not. "), error);
2177 g_free (error);
2179 view_update (view);
2180 return TRUE;
2182 save_error:
2183 error = g_strdup (strerror (errno));
2184 text = g_strdup_printf (_(" Cannot save file: \n %s "), error);
2185 g_free (error);
2186 (void) mc_close (fp);
2188 answer = query_dialog (_(" Save file "), text, D_ERROR,
2189 2, _("&Retry"), _("&Cancel"));
2190 g_free (text);
2192 if (answer == 0)
2193 goto retry_save;
2194 return FALSE;
2197 /* {{{ Miscellaneous functions }}} */
2199 static gboolean
2200 view_ok_to_quit (WView *view)
2202 int r;
2204 if (view->change_list == NULL)
2205 return TRUE;
2207 r = query_dialog (_("Quit"),
2208 _(" File was modified, Save with exit? "), D_NORMAL, 3,
2209 _("&Cancel quit"), _("&Yes"), _("&No"));
2211 switch (r) {
2212 case 1:
2213 return view_hexedit_save_changes (view);
2214 case 2:
2215 view_hexedit_free_change_list (view);
2216 return TRUE;
2217 default:
2218 return FALSE;
2222 static inline void
2223 my_define (Dlg_head *h, int idx, const char *text, void (*fn) (WView *),
2224 WView *view)
2226 buttonbar_set_label_data (h, idx, text, (buttonbarfn) fn, view);
2229 /* {{{ Searching }}} */
2231 /* Case insensitive search of text in data */
2232 static int
2233 icase_search_p (WView *view, char *text, char *data, int nothing)
2235 const char *q;
2236 int lng;
2237 const int direction = view->direction;
2239 (void) nothing;
2241 /* If we are searching backwards, reverse the string */
2242 if (direction == -1) {
2243 g_strreverse (text);
2244 g_strreverse (data);
2247 q = _icase_search (text, data, &lng);
2249 if (direction == -1) {
2250 g_strreverse (text);
2251 g_strreverse (data);
2254 if (q != 0) {
2255 if (direction > 0)
2256 view->search_start = q - data - lng;
2257 else
2258 view->search_start = strlen (data) - (q - data);
2259 view->search_length = lng;
2260 return 1;
2262 return 0;
2265 static char *
2266 grow_string_buffer (char *text, gulong *size)
2268 char *new;
2270 /* The grow steps */
2271 *size += 160;
2272 new = g_realloc (text, *size);
2273 if (text == NULL) {
2274 *new = '\0';
2276 return new;
2279 static char *
2280 get_line_at (WView *view, offset_type *p, offset_type *skipped)
2282 char *buffer = NULL;
2283 gulong buffer_size = 0;
2284 offset_type usable_size = 0;
2285 int ch;
2286 const int direction = view->direction;
2287 offset_type pos = *p;
2288 offset_type i = 0;
2289 int prev = '\0';
2291 *skipped = 0;
2293 if (pos == 0 && direction == -1)
2294 return 0;
2296 /* skip over all the possible zeros in the file */
2297 while ((ch = get_byte (view, pos)) == 0) {
2298 if (pos == 0 && direction == -1)
2299 return 0;
2300 pos += direction;
2301 i++;
2303 *skipped = i;
2305 if (i == 0 && (pos != 0 || direction == -1)) {
2306 prev = get_byte (view, pos - direction);
2307 if ((prev == -1) || (prev == '\n'))
2308 prev = '\0';
2311 for (i = 1; ch != -1; ch = get_byte (view, pos)) {
2312 if (i >= usable_size) {
2313 buffer = grow_string_buffer (buffer, &buffer_size);
2314 usable_size = buffer_size - 2; /* prev & null terminator */
2317 buffer[i++] = ch;
2319 if (pos == 0 && direction == -1)
2320 break;
2322 pos += direction;
2324 if (ch == '\n' || ch == '\0') {
2325 i--; /* Strip newline/zero */
2326 break;
2330 if (buffer) {
2331 buffer[0] = prev;
2332 buffer[i] = '\0';
2334 /* If we are searching backwards, reverse the string */
2335 if (direction == -1) {
2336 g_strreverse (buffer + 1);
2340 *p = pos;
2341 return buffer;
2344 static void
2345 search_update_steps (WView *view)
2347 offset_type filesize = view_get_filesize (view);
2348 if (filesize != 0)
2349 view->update_steps = 40000;
2350 else /* viewing a data stream, not a file */
2351 view->update_steps = filesize / 100;
2353 /* Do not update the percent display but every 20 ks */
2354 if (view->update_steps < 20000)
2355 view->update_steps = 20000;
2358 static void
2359 search (WView *view, char *text,
2360 int (*search) (WView *, char *, char *, int))
2362 char *s = NULL; /* The line we read from the view buffer */
2363 offset_type p, beginning, search_start;
2364 int found_len;
2365 int search_status;
2366 Dlg_head *d = 0;
2368 /* Used to keep track of where the line starts, when looking forward
2369 * is the index before transfering the line; the reverse case uses
2370 * the position returned after the line has been read */
2371 offset_type forward_line_start;
2372 offset_type reverse_line_start;
2373 offset_type t;
2375 if (verbose) {
2376 d = create_message (D_NORMAL, _("Search"), _("Searching %s"), text);
2377 mc_refresh ();
2380 found_len = view->search_length;
2381 search_start = view->search_start;
2383 if (view->direction == 1) {
2384 p = search_start + ((found_len) ? 1 : 0);
2385 } else {
2386 p = search_start - ((found_len && search_start >= 1) ? 1 : 0);
2388 beginning = p;
2390 /* Compute the percent steps */
2391 search_update_steps (view);
2392 view->update_activate = 0;
2394 enable_interrupt_key ();
2395 for (;; g_free (s)) {
2396 if (p >= view->update_activate) {
2397 view->update_activate += view->update_steps;
2398 if (verbose) {
2399 view_percent (view, p);
2400 mc_refresh ();
2402 if (got_interrupt ())
2403 break;
2405 forward_line_start = p;
2406 s = get_line_at (view, &p, &t);
2407 reverse_line_start = p;
2409 if (!s)
2410 break;
2412 search_status = (*search) (view, text, s + 1, match_normal);
2413 if (search_status < 0) {
2414 g_free (s);
2415 break;
2418 if (search_status == 0)
2419 continue;
2421 /* We found the string */
2423 /* Handle ^ and $ when regexp search starts at the middle of the line */
2424 if (*s && !view->search_start && (search == regexp_view_search)) {
2425 if ((*text == '^' && view->direction == 1)
2426 || (view->direction == -1 && text[strlen (text) - 1] == '$')
2428 continue;
2431 /* Record the position used to continue the search */
2432 if (view->direction == 1)
2433 t += forward_line_start;
2434 else
2435 t = reverse_line_start ? reverse_line_start + 2 : 0;
2436 view->search_start += t;
2438 if (t != beginning) {
2439 view->dpy_start = t;
2442 g_free (s);
2443 break;
2445 disable_interrupt_key ();
2446 if (verbose) {
2447 dlg_run_done (d);
2448 destroy_dlg (d);
2450 if (!s) {
2451 message (D_NORMAL, _("Search"), _(" Search string not found "));
2452 view->search_length = 0;
2456 /* Search buffer (its size is len) in the complete buffer
2457 * returns the position where the block was found or INVALID_OFFSET
2458 * if not found */
2459 static offset_type
2460 block_search (WView *view, const char *buffer, int len)
2462 int direction = view->direction;
2463 const char *d = buffer;
2464 char b;
2465 offset_type e;
2467 enable_interrupt_key ();
2468 if (direction == 1)
2469 e = view->search_start + ((view->search_length) ? 1 : 0);
2470 else
2471 e = view->search_start
2472 - ((view->search_length && view->search_start >= 1) ? 1 : 0);
2474 search_update_steps (view);
2475 view->update_activate = 0;
2477 if (direction == -1) {
2478 for (d += len - 1;; e--) {
2479 if (e <= view->update_activate) {
2480 view->update_activate -= view->update_steps;
2481 if (verbose) {
2482 view_percent (view, e);
2483 mc_refresh ();
2485 if (got_interrupt ())
2486 break;
2488 b = get_byte (view, e);
2490 if (*d == b) {
2491 if (d == buffer) {
2492 disable_interrupt_key ();
2493 return e;
2495 d--;
2496 } else {
2497 e += buffer + len - 1 - d;
2498 d = buffer + len - 1;
2500 if (e == 0)
2501 break;
2503 } else {
2504 while (get_byte (view, e) != -1) {
2505 if (e >= view->update_activate) {
2506 view->update_activate += view->update_steps;
2507 if (verbose) {
2508 view_percent (view, e);
2509 mc_refresh ();
2511 if (got_interrupt ())
2512 break;
2514 b = get_byte (view, e++);
2516 if (*d == b) {
2517 d++;
2518 if (d - buffer == len) {
2519 disable_interrupt_key ();
2520 return e - len;
2522 } else {
2523 e -= d - buffer;
2524 d = buffer;
2528 disable_interrupt_key ();
2529 return INVALID_OFFSET;
2533 * Search in the hex mode. Supported input:
2534 * - numbers (oct, dec, hex). Each of them matches one byte.
2535 * - strings in double quotes. Matches exactly without quotes.
2537 static void
2538 hex_search (WView *view, const char *text)
2540 char *buffer; /* Parsed search string */
2541 char *cur; /* Current position in it */
2542 int block_len; /* Length of the search string */
2543 offset_type pos; /* Position of the string in the file */
2544 int parse_error = 0;
2546 if (!*text) {
2547 view->search_length = 0;
2548 return;
2551 /* buffer will never be longer that text */
2552 buffer = g_new (char, strlen (text));
2553 cur = buffer;
2555 /* First convert the string to a stream of bytes */
2556 while (*text) {
2557 int val;
2558 int ptr;
2560 /* Skip leading spaces */
2561 if (*text == ' ' || *text == '\t') {
2562 text++;
2563 continue;
2566 /* %i matches octal, decimal, and hexadecimal numbers */
2567 if (sscanf (text, "%i%n", &val, &ptr) > 0) {
2568 /* Allow signed and unsigned char in the user input */
2569 if (val < -128 || val > 255) {
2570 parse_error = 1;
2571 break;
2574 *cur++ = (char) val;
2575 text += ptr;
2576 continue;
2579 /* Try quoted string, strip quotes */
2580 if (*text == '"') {
2581 const char *next_quote;
2583 text++;
2584 next_quote = strchr (text, '"');
2585 if (next_quote) {
2586 memcpy (cur, text, next_quote - text);
2587 cur += next_quote - text;
2588 text = next_quote + 1;
2589 continue;
2591 /* fall through */
2594 parse_error = 1;
2595 break;
2598 block_len = cur - buffer;
2600 /* No valid bytes in the user input */
2601 if (block_len <= 0 || parse_error) {
2602 message (D_NORMAL, _("Search"), _("Invalid hex search expression"));
2603 g_free (buffer);
2604 view->search_length = 0;
2605 return;
2608 /* Then start the search */
2609 pos = block_search (view, buffer, block_len);
2611 g_free (buffer);
2613 if (pos == INVALID_OFFSET) {
2614 message (D_NORMAL, _("Search"), _(" Search string not found "));
2615 view->search_length = 0;
2616 return;
2619 view->search_start = pos;
2620 view->search_length = block_len;
2621 /* Set the edit cursor to the search position, left nibble */
2622 view->hex_cursor = view->search_start;
2623 view->hexedit_lownibble = FALSE;
2625 /* Adjust the file offset */
2626 view->dpy_start = pos - pos % view->bytes_per_line;
2629 static int
2630 regexp_view_search (WView *view, char *pattern, char *string,
2631 int match_type)
2633 static regex_t r;
2634 static char *old_pattern = NULL;
2635 static int old_type;
2636 regmatch_t pmatch[1];
2637 int i, flags = REG_ICASE;
2639 if (old_pattern == NULL || strcmp (old_pattern, pattern) != 0
2640 || old_type != match_type) {
2641 if (old_pattern != NULL) {
2642 regfree (&r);
2643 g_free (old_pattern);
2644 old_pattern = 0;
2646 for (i = 0; pattern[i] != '\0'; i++) {
2647 if (isupper ((unsigned char) pattern[i])) {
2648 flags = 0;
2649 break;
2652 flags |= REG_EXTENDED;
2653 if (regcomp (&r, pattern, flags)) {
2654 message (D_ERROR, MSG_ERROR, _(" Invalid regular expression "));
2655 return -1;
2657 old_pattern = g_strdup (pattern);
2658 old_type = match_type;
2660 if (regexec (&r, string, 1, pmatch, 0) != 0)
2661 return 0;
2662 view->search_length = pmatch[0].rm_eo - pmatch[0].rm_so;
2663 view->search_start = pmatch[0].rm_so;
2664 return 1;
2667 static void
2668 do_regexp_search (WView *view)
2670 search (view, view->search_exp, regexp_view_search);
2671 /* Had a refresh here */
2672 view->dirty++;
2673 view_update (view);
2676 static void
2677 do_normal_search (WView *view)
2679 if (view->hex_mode)
2680 hex_search (view, view->search_exp);
2681 else
2682 search (view, view->search_exp, icase_search_p);
2683 /* Had a refresh here */
2684 view->dirty++;
2685 view_update (view);
2688 /* {{{ User-definable commands }}} */
2691 The functions in this section can be bound to hotkeys. They are all
2692 of the same type (taking a pointer to WView as parameter and
2693 returning void). TODO: In the not-too-distant future, these commands
2694 will become fully configurable, like they already are in the
2695 internal editor. By convention, all the function names end in
2696 "_cmd".
2699 static void
2700 view_help_cmd (void)
2702 interactive_display (NULL, "[Internal File Viewer]");
2705 /* Toggle between hexview and hexedit mode */
2706 static void
2707 view_toggle_hexedit_mode_cmd (WView *view)
2709 view_toggle_hexedit_mode (view);
2710 view_update (view);
2713 /* Toggle between wrapped and unwrapped view */
2714 static void
2715 view_toggle_wrap_mode_cmd (WView *view)
2717 view_toggle_wrap_mode (view);
2718 view_update (view);
2721 /* Toggle between hex view and text view */
2722 static void
2723 view_toggle_hex_mode_cmd (WView *view)
2725 view_toggle_hex_mode (view);
2726 view_update (view);
2729 static void
2730 view_moveto_line_cmd (WView *view)
2732 char *answer, *answer_end, prompt[BUF_SMALL];
2733 offset_type line, col;
2735 view_offset_to_coord (view, &line, &col, view->dpy_start);
2737 snprintf (prompt, sizeof (prompt),
2738 _(" The current line number is %d.\n"
2739 " Enter the new line number:"), (int) (line + 1));
2740 answer = input_dialog (_(" Goto line "), prompt, MC_HISTORY_VIEW_GOTO_LINE, "");
2741 if (answer != NULL && answer[0] != '\0') {
2742 errno = 0;
2743 line = strtoul (answer, &answer_end, 10);
2744 if (*answer_end == '\0' && errno == 0 && line >= 1)
2745 view_moveto (view, line - 1, 0);
2747 g_free (answer);
2748 view->dirty++;
2749 view_update (view);
2752 static void
2753 view_moveto_addr_cmd (WView *view)
2755 char *line, *error, prompt[BUF_SMALL];
2756 offset_type addr;
2758 snprintf (prompt, sizeof (prompt),
2759 _(" The current address is 0x%lx.\n"
2760 " Enter the new address:"), view->hex_cursor);
2761 line = input_dialog (_(" Goto Address "), prompt, MC_HISTORY_VIEW_GOTO_ADDR, "");
2762 if (line != NULL) {
2763 if (*line != '\0') {
2764 addr = strtoul (line, &error, 0);
2765 if ((*error == '\0') && get_byte (view, addr) != -1) {
2766 view_moveto_offset (view, addr);
2767 } else {
2768 message (D_ERROR, _("Warning"), _(" Invalid address "));
2771 g_free (line);
2773 view->dirty++;
2774 view_update (view);
2777 static void
2778 view_hexedit_save_changes_cmd (WView *view)
2780 (void) view_hexedit_save_changes (view);
2783 /* {{{ Searching }}} */
2785 static void
2786 regexp_search (WView *view, int direction)
2788 const char *defval;
2789 char *regexp;
2790 static char *last_regexp;
2792 defval = (last_regexp != NULL ? last_regexp : "");
2794 regexp = input_dialog (_("Search"), _(" Enter regexp:"), MC_HISTORY_VIEW_SEARCH_REGEX, defval);
2795 if (regexp == NULL || regexp[0] == '\0') {
2796 g_free (regexp);
2797 return;
2800 g_free (last_regexp);
2801 view->search_exp = last_regexp = regexp;
2803 view->direction = direction;
2804 do_regexp_search (view);
2805 view->last_search = do_regexp_search;
2808 /* {{{ User-definable commands }}} */
2810 static void
2811 view_regexp_search_cmd (WView *view)
2813 regexp_search (view, 1);
2816 /* Both views */
2817 static void
2818 view_normal_search_cmd (WView *view)
2820 char *defval, *exp = NULL;
2821 static char *last_search_string;
2823 enum {
2824 SEARCH_DLG_HEIGHT = 8,
2825 SEARCH_DLG_WIDTH = 58
2828 static int replace_backwards;
2829 int treplace_backwards = replace_backwards;
2831 static QuickWidget quick_widgets[] = {
2832 {quick_button, 6, 10, 5, SEARCH_DLG_HEIGHT, N_("&Cancel"), 0,
2833 B_CANCEL,
2834 0, 0, NULL},
2835 {quick_button, 2, 10, 5, SEARCH_DLG_HEIGHT, N_("&OK"), 0, B_ENTER,
2836 0, 0, NULL},
2837 {quick_checkbox, 3, SEARCH_DLG_WIDTH, 4, SEARCH_DLG_HEIGHT,
2838 N_("&Backwards"), 0, 0,
2839 0, 0, NULL},
2840 {quick_input, 3, SEARCH_DLG_WIDTH, 3, SEARCH_DLG_HEIGHT, "", 52, 0,
2841 0, 0, N_("Search")},
2842 {quick_label, 2, SEARCH_DLG_WIDTH, 2, SEARCH_DLG_HEIGHT,
2843 N_(" Enter search string:"), 0, 0,
2844 0, 0, 0},
2845 NULL_QuickWidget
2847 static QuickDialog Quick_input = {
2848 SEARCH_DLG_WIDTH, SEARCH_DLG_HEIGHT, -1, 0, N_("Search"),
2849 "[Input Line Keys]", quick_widgets, 0
2852 defval = g_strdup (last_search_string != NULL ? last_search_string : "");
2853 convert_to_display (defval);
2855 quick_widgets[2].result = &treplace_backwards;
2856 quick_widgets[3].str_result = &exp;
2857 quick_widgets[3].text = defval;
2859 if (quick_dialog (&Quick_input) == B_CANCEL)
2860 goto cleanup;
2862 replace_backwards = treplace_backwards;
2864 if (exp == NULL || exp[0] == '\0')
2865 goto cleanup;
2867 convert_from_input (exp);
2869 g_free (last_search_string);
2870 view->search_exp = last_search_string = exp;
2871 exp = NULL;
2873 view->direction = replace_backwards ? -1 : 1;
2874 do_normal_search (view);
2875 view->last_search = do_normal_search;
2877 cleanup:
2878 g_free (exp);
2879 g_free (defval);
2882 static void
2883 view_toggle_magic_mode_cmd (WView *view)
2885 view_toggle_magic_mode (view);
2886 view_update (view);
2889 static void
2890 view_toggle_nroff_mode_cmd (WView *view)
2892 view_toggle_nroff_mode (view);
2893 view_update (view);
2896 static void
2897 view_quit_cmd (WView *view)
2899 if (view_ok_to_quit (view))
2900 dlg_stop (view->widget.parent);
2903 /* {{{ Miscellaneous functions }}} */
2905 /* Define labels and handlers for functional keys */
2906 static void
2907 view_labels (WView *view)
2909 Dlg_head *h = view->widget.parent;
2911 buttonbar_set_label (h, 1, Q_("ButtonBar|Help"), view_help_cmd);
2913 my_define (h, 10, Q_("ButtonBar|Quit"), view_quit_cmd, view);
2914 my_define (h, 4, view->hex_mode
2915 ? Q_("ButtonBar|Ascii")
2916 : Q_("ButtonBar|Hex"),
2917 view_toggle_hex_mode_cmd, view);
2918 my_define (h, 5, view->hex_mode
2919 ? Q_("ButtonBar|Goto")
2920 : Q_("ButtonBar|Line"),
2921 view->hex_mode ? view_moveto_addr_cmd : view_moveto_line_cmd, view);
2923 if (view->hex_mode) {
2924 if (view->hexedit_mode) {
2925 my_define (h, 2, Q_("ButtonBar|View"),
2926 view_toggle_hexedit_mode_cmd, view);
2927 } else if (view->datasource == DS_FILE) {
2928 my_define (h, 2, Q_("ButtonBar|Edit"),
2929 view_toggle_hexedit_mode_cmd, view);
2930 } else {
2931 buttonbar_clear_label (h, 2);
2933 my_define (h, 6, Q_("ButtonBar|Save"),
2934 view_hexedit_save_changes_cmd, view);
2935 } else {
2936 my_define (h, 2, view->text_wrap_mode
2937 ? Q_("ButtonBar|UnWrap")
2938 : Q_("ButtonBar|Wrap"),
2939 view_toggle_wrap_mode_cmd, view);
2940 my_define (h, 6, Q_("ButtonBar|RxSrch"),
2941 view_regexp_search_cmd, view);
2944 my_define (h, 7, view->hex_mode
2945 ? Q_("ButtonBar|HxSrch")
2946 : Q_("ButtonBar|Search"),
2947 view_normal_search_cmd, view);
2948 my_define (h, 8, view->magic_mode
2949 ? Q_("ButtonBar|Raw")
2950 : Q_("ButtonBar|Parse"),
2951 view_toggle_magic_mode_cmd, view);
2953 /* don't override the key to access the main menu */
2954 if (!view_is_in_panel (view)) {
2955 my_define (h, 9, view->text_nroff_mode
2956 ? Q_("ButtonBar|Unform")
2957 : Q_("ButtonBar|Format"),
2958 view_toggle_nroff_mode_cmd, view);
2959 my_define (h, 3, Q_("ButtonBar|Quit"), view_quit_cmd, view);
2963 /* {{{ Event handling }}} */
2965 /* Check for left and right arrows, possibly with modifiers */
2966 static cb_ret_t
2967 check_left_right_keys (WView *view, int c)
2969 if (c == KEY_LEFT) {
2970 view_move_left (view, 1);
2971 return MSG_HANDLED;
2974 if (c == KEY_RIGHT) {
2975 view_move_right (view, 1);
2976 return MSG_HANDLED;
2979 /* Ctrl with arrows moves by 10 postions in the unwrap mode */
2980 if (view->hex_mode || view->text_wrap_mode)
2981 return MSG_NOT_HANDLED;
2983 if (c == (KEY_M_CTRL | KEY_LEFT)) {
2984 if (view->dpy_text_column >= 10)
2985 view->dpy_text_column -= 10;
2986 else
2987 view->dpy_text_column = 0;
2988 view->dirty++;
2989 return MSG_HANDLED;
2992 if (c == (KEY_M_CTRL | KEY_RIGHT)) {
2993 if (view->dpy_text_column <= OFFSETTYPE_MAX - 10)
2994 view->dpy_text_column += 10;
2995 else
2996 view->dpy_text_column = OFFSETTYPE_MAX;
2997 view->dirty++;
2998 return MSG_HANDLED;
3001 return MSG_NOT_HANDLED;
3004 /* {{{ User-definable commands }}} */
3006 static void
3007 view_continue_search_cmd (WView *view)
3009 if (view->last_search) {
3010 view->last_search (view);
3011 } else {
3012 /* if not... then ask for an expression */
3013 view_normal_search_cmd (view);
3017 static void
3018 view_toggle_ruler_cmd (WView *view)
3020 static const enum ruler_type next[3] = {
3021 RULER_TOP,
3022 RULER_BOTTOM,
3023 RULER_NONE
3026 assert ((size_t) ruler < 3);
3027 ruler = next[(size_t) ruler];
3028 view->dirty++;
3031 /* {{{ Event handling }}} */
3033 static void view_cmk_move_up (void *w, int n) {
3034 view_move_up ((WView *) w, n);
3036 static void view_cmk_move_down (void *w, int n) {
3037 view_move_down ((WView *) w, n);
3039 static void view_cmk_moveto_top (void *w, int n) {
3040 (void) &n;
3041 view_moveto_top ((WView *) w);
3043 static void view_cmk_moveto_bottom (void *w, int n) {
3044 (void) &n;
3045 view_moveto_bottom ((WView *) w);
3048 /* Both views */
3049 static cb_ret_t
3050 view_handle_key (WView *view, int c)
3052 c = convert_from_input_c (c);
3054 if (view->hex_mode) {
3055 switch (c) {
3056 case '\t':
3057 view->hexview_in_text = !view->hexview_in_text;
3058 view->dirty++;
3059 return MSG_HANDLED;
3061 case XCTRL ('a'):
3062 view_moveto_bol (view);
3063 view->dirty++;
3064 return MSG_HANDLED;
3066 case XCTRL ('b'):
3067 view_move_left (view, 1);
3068 return MSG_HANDLED;
3070 case XCTRL ('e'):
3071 view_moveto_eol (view);
3072 return MSG_HANDLED;
3074 case XCTRL ('f'):
3075 view_move_right (view, 1);
3076 return MSG_HANDLED;
3079 if (view->hexedit_mode
3080 && view_handle_editkey (view, c) == MSG_HANDLED)
3081 return MSG_HANDLED;
3084 if (check_left_right_keys (view, c))
3085 return MSG_HANDLED;
3087 if (check_movement_keys (c, view->data_area.height + 1, view,
3088 view_cmk_move_up, view_cmk_move_down,
3089 view_cmk_moveto_top, view_cmk_moveto_bottom))
3090 return MSG_HANDLED;
3092 switch (c) {
3094 case '?':
3095 regexp_search (view, -1);
3096 return MSG_HANDLED;
3098 case '/':
3099 regexp_search (view, 1);
3100 return MSG_HANDLED;
3102 /* Continue search */
3103 case XCTRL ('r'):
3104 case XCTRL ('s'):
3105 case 'n':
3106 case KEY_F (17):
3107 view_continue_search_cmd (view);
3108 return MSG_HANDLED;
3110 /* toggle ruler */
3111 case ALT ('r'):
3112 view_toggle_ruler_cmd (view);
3113 return MSG_HANDLED;
3115 case 'h':
3116 view_move_left (view, 1);
3117 return MSG_HANDLED;
3119 case 'j':
3120 case '\n':
3121 case 'e':
3122 view_move_down (view, 1);
3123 return MSG_HANDLED;
3125 case 'd':
3126 view_move_down (view, (view->data_area.height + 1) / 2);
3127 return MSG_HANDLED;
3129 case 'u':
3130 view_move_up (view, (view->data_area.height + 1) / 2);
3131 return MSG_HANDLED;
3133 case 'k':
3134 case 'y':
3135 view_move_up (view, 1);
3136 return MSG_HANDLED;
3138 case 'l':
3139 view_move_right (view, 1);
3140 return MSG_HANDLED;
3142 case ' ':
3143 case 'f':
3144 view_move_down (view, view->data_area.height);
3145 return MSG_HANDLED;
3147 case XCTRL ('o'):
3148 view_other_cmd ();
3149 return MSG_HANDLED;
3151 /* Unlike Ctrl-O, run a new shell if the subshell is not running. */
3152 case '!':
3153 exec_shell ();
3154 return MSG_HANDLED;
3156 case 'b':
3157 view_move_up (view, view->data_area.height);
3158 return MSG_HANDLED;
3160 case KEY_IC:
3161 view_move_up (view, 2);
3162 return MSG_HANDLED;
3164 case KEY_DC:
3165 view_move_down (view, 2);
3166 return MSG_HANDLED;
3168 case 'm':
3169 view->marks[view->marker] = view->dpy_start;
3170 return MSG_HANDLED;
3172 case 'r':
3173 view->dpy_start = view->marks[view->marker];
3174 view->dirty++;
3175 return MSG_HANDLED;
3177 /* Use to indicate parent that we want to see the next/previous file */
3178 /* Does not work in panel mode */
3179 case XCTRL ('f'):
3180 case XCTRL ('b'):
3181 if (!view_is_in_panel (view))
3182 view->move_dir = c == XCTRL ('f') ? 1 : -1;
3183 /* FALLTHROUGH */
3184 case 'q':
3185 case XCTRL ('g'):
3186 case ESC_CHAR:
3187 if (view_ok_to_quit (view))
3188 view->want_to_quit = TRUE;
3189 return MSG_HANDLED;
3191 #ifdef HAVE_CHARSET
3192 case XCTRL ('t'):
3193 do_select_codepage ();
3194 view->dirty++;
3195 view_update (view);
3196 return MSG_HANDLED;
3197 #endif /* HAVE_CHARSET */
3199 #ifdef MC_ENABLE_DEBUGGING_CODE
3200 case 't': /* mnemonic: "test" */
3201 view_ccache_dump (view);
3202 return MSG_HANDLED;
3203 #endif
3205 if (c >= '0' && c <= '9')
3206 view->marker = c - '0';
3208 /* Key not used */
3209 return MSG_NOT_HANDLED;
3212 /* Both views */
3213 static int
3214 view_event (WView *view, Gpm_Event *event, int *result)
3216 screen_dimen y, x;
3218 *result = MOU_NORMAL;
3220 /* We are not interested in the release events */
3221 if (!(event->type & (GPM_DOWN | GPM_DRAG)))
3222 return 0;
3224 /* Wheel events */
3225 if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) {
3226 view_move_up (view, 2);
3227 return 1;
3229 if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
3230 view_move_down (view, 2);
3231 return 1;
3234 x = event->x;
3235 y = event->y;
3237 /* Scrolling left and right */
3238 if (!view->text_wrap_mode) {
3239 if (x < view->data_area.width * 1/4) {
3240 view_move_left (view, 1);
3241 goto processed;
3242 } else if (x < view->data_area.width * 3/4) {
3243 /* ignore the click */
3244 } else {
3245 view_move_right (view, 1);
3246 goto processed;
3250 /* Scrolling up and down */
3251 if (y < view->data_area.top + view->data_area.height * 1/3) {
3252 if (mouse_move_pages_viewer)
3253 view_move_up (view, view->data_area.height / 2);
3254 else
3255 view_move_up (view, 1);
3256 goto processed;
3257 } else if (y < view->data_area.top + view->data_area.height * 2/3) {
3258 /* ignore the click */
3259 } else {
3260 if (mouse_move_pages_viewer)
3261 view_move_down (view, view->data_area.height / 2);
3262 else
3263 view_move_down (view, 1);
3264 goto processed;
3267 return 0;
3269 processed:
3270 *result = MOU_REPEAT;
3271 return 1;
3274 /* Real view only */
3275 static int
3276 real_view_event (Gpm_Event *event, void *x)
3278 WView *view = (WView *) x;
3279 int result;
3281 if (view_event (view, event, &result))
3282 view_update (view);
3283 return result;
3286 static void
3287 view_adjust_size (Dlg_head *h)
3289 WView *view;
3290 WButtonBar *bar;
3292 /* Look up the viewer and the buttonbar, we assume only two widgets here */
3293 view = (WView *) find_widget_type (h, view_callback);
3294 bar = find_buttonbar (h);
3295 widget_set_size (&view->widget, 0, 0, LINES - 1, COLS);
3296 widget_set_size ((Widget *) bar, LINES - 1, 0, 1, COLS);
3298 view_compute_areas (view);
3299 view_update_bytes_per_line (view);
3302 /* Callback for the view dialog */
3303 static cb_ret_t
3304 view_dialog_callback (Dlg_head *h, dlg_msg_t msg, int parm)
3306 switch (msg) {
3307 case DLG_RESIZE:
3308 view_adjust_size (h);
3309 return MSG_HANDLED;
3311 default:
3312 return default_dlg_callback (h, msg, parm);
3316 /* {{{ External interface }}} */
3318 /* Real view only */
3320 mc_internal_viewer (const char *command, const char *file,
3321 int *move_dir_p, int start_line)
3323 gboolean succeeded;
3324 WView *wview;
3325 WButtonBar *bar;
3326 Dlg_head *view_dlg;
3328 /* Create dialog and widgets, put them on the dialog */
3329 view_dlg =
3330 create_dlg (0, 0, LINES, COLS, NULL, view_dialog_callback,
3331 "[Internal File Viewer]", NULL, DLG_WANT_TAB);
3333 wview = view_new (0, 0, COLS, LINES - 1, 0);
3335 bar = buttonbar_new (1);
3337 add_widget (view_dlg, bar);
3338 add_widget (view_dlg, wview);
3340 succeeded = view_load (wview, command, file, start_line);
3341 if (succeeded) {
3342 run_dlg (view_dlg);
3343 if (move_dir_p)
3344 *move_dir_p = wview->move_dir;
3345 } else {
3346 if (move_dir_p)
3347 *move_dir_p = 0;
3349 destroy_dlg (view_dlg);
3351 return succeeded;
3354 /* {{{ Miscellaneous functions }}} */
3356 static void
3357 view_hook (void *v)
3359 WView *view = (WView *) v;
3360 WPanel *panel;
3362 /* If the user is busy typing, wait until he finishes to update the
3363 screen */
3364 if (!is_idle ()) {
3365 if (!hook_present (idle_hook, view_hook))
3366 add_hook (&idle_hook, view_hook, v);
3367 return;
3370 delete_hook (&idle_hook, view_hook);
3372 if (get_current_type () == view_listing)
3373 panel = current_panel;
3374 else if (get_other_type () == view_listing)
3375 panel = other_panel;
3376 else
3377 return;
3379 view_load (view, 0, panel->dir.list[panel->selected].fname, 0);
3380 display (view);
3383 /* {{{ Event handling }}} */
3385 static cb_ret_t
3386 view_callback (Widget *w, widget_msg_t msg, int parm)
3388 WView *view = (WView *) w;
3389 cb_ret_t i;
3390 Dlg_head *h = view->widget.parent;
3392 view_compute_areas (view);
3393 view_update_bytes_per_line (view);
3395 switch (msg) {
3396 case WIDGET_INIT:
3397 if (view_is_in_panel (view))
3398 add_hook (&select_file_hook, view_hook, view);
3399 else
3400 view->dpy_bbar_dirty = TRUE;
3401 return MSG_HANDLED;
3403 case WIDGET_DRAW:
3404 display (view);
3405 return MSG_HANDLED;
3407 case WIDGET_CURSOR:
3408 if (view->hex_mode)
3409 view_place_cursor (view);
3410 return MSG_HANDLED;
3412 case WIDGET_KEY:
3413 i = view_handle_key ((WView *) view, parm);
3414 if (view->want_to_quit && !view_is_in_panel (view))
3415 dlg_stop (h);
3416 else {
3417 view_update (view);
3419 return i;
3421 case WIDGET_FOCUS:
3422 view->dpy_bbar_dirty = TRUE;
3423 view_update (view);
3424 return MSG_HANDLED;
3426 case WIDGET_DESTROY:
3427 view_done (view);
3428 if (view_is_in_panel (view))
3429 delete_hook (&select_file_hook, view_hook);
3430 return MSG_HANDLED;
3432 default:
3433 return default_proc (msg, parm);
3437 /* {{{ External interface }}} */
3439 WView *
3440 view_new (int y, int x, int cols, int lines, int is_panel)
3442 WView *view = g_new0 (WView, 1);
3443 size_t i;
3445 init_widget (&view->widget, y, x, lines, cols,
3446 view_callback,
3447 real_view_event);
3449 view->filename = NULL;
3450 view->command = NULL;
3452 view_set_datasource_none (view);
3454 view->growbuf_in_use = FALSE;
3455 /* leave the other growbuf fields uninitialized */
3457 view->hex_mode = FALSE;
3458 view->hexedit_mode = FALSE;
3459 view->hexview_in_text = FALSE;
3460 view->text_nroff_mode = FALSE;
3461 view->text_wrap_mode = FALSE;
3462 view->magic_mode = FALSE;
3464 view->hexedit_lownibble = FALSE;
3465 view->coord_cache = NULL;
3467 view->dpy_frame_size = is_panel ? 1 : 0;
3468 view->dpy_start = 0;
3469 view->dpy_text_column = 0;
3470 view->dpy_end= 0;
3471 view->hex_cursor = 0;
3472 view->cursor_col = 0;
3473 view->cursor_row = 0;
3474 view->change_list = NULL;
3476 /* {status,ruler,data}_area are left uninitialized */
3478 view->dirty = 0;
3479 view->dpy_bbar_dirty = TRUE;
3480 view->bytes_per_line = 1;
3482 view->search_start = 0;
3483 view->search_length = 0;
3484 view->search_exp = NULL;
3485 view->direction = 1; /* forward */
3486 view->last_search = 0; /* it's a function */
3488 view->want_to_quit = FALSE;
3489 view->marker = 0;
3490 for (i = 0; i < sizeof(view->marks) / sizeof(view->marks[0]); i++)
3491 view->marks[i] = 0;
3493 view->move_dir = 0;
3494 view->update_steps = 0;
3495 view->update_activate = 0;
3497 if (default_hex_mode)
3498 view_toggle_hex_mode (view);
3499 if (default_nroff_flag)
3500 view_toggle_nroff_mode (view);
3501 if (global_wrap_mode)
3502 view_toggle_wrap_mode (view);
3503 if (default_magic_flag)
3504 view_toggle_magic_mode (view);
3506 return view;