Merge branch '132_search_skip_hidden'
[midnight-commander.git] / src / view.c
blobb86ca99ef76ebbda1b3b92a7cc5a109a0bb369c1
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 "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"
64 #include "history.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 (D_NORMAL, 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 if (byte_index >= view->ds_file_filesize)
544 return;
546 blockoffset = offset_rounddown (byte_index, view->ds_file_datasize);
547 if (mc_lseek (view->ds_file_fd, blockoffset, SEEK_SET) == -1)
548 goto error;
550 bytes_read = 0;
551 while (bytes_read < view->ds_file_datasize) {
552 res = mc_read (view->ds_file_fd, view->ds_file_data + bytes_read, view->ds_file_datasize - bytes_read);
553 if (res == -1)
554 goto error;
555 if (res == 0)
556 break;
557 bytes_read += (size_t) res;
559 view->ds_file_offset = blockoffset;
560 if (bytes_read > view->ds_file_filesize - view->ds_file_offset) {
561 /* the file has grown in the meantime -- stick to the old size */
562 view->ds_file_datalen = view->ds_file_filesize - view->ds_file_offset;
563 } else {
564 view->ds_file_datalen = bytes_read;
566 return;
568 error:
569 view->ds_file_datalen = 0;
572 static int
573 get_byte_none (WView *view, offset_type byte_index)
575 assert (view->datasource == DS_NONE);
576 (void) &view;
577 (void) byte_index;
578 return -1;
581 static inline int
582 get_byte_file (WView *view, offset_type byte_index)
584 assert (view->datasource == DS_FILE);
586 view_file_load_data (view, byte_index);
587 if (already_loaded(view->ds_file_offset, byte_index, view->ds_file_datalen))
588 return view->ds_file_data[byte_index - view->ds_file_offset];
589 return -1;
592 static int
593 get_byte_string (WView *view, offset_type byte_index)
595 assert (view->datasource == DS_STRING);
596 if (byte_index < view->ds_string_len)
597 return view->ds_string_data[byte_index];
598 return -1;
601 static inline int
602 get_byte (WView *view, offset_type offset)
604 switch (view->datasource) {
605 case DS_STDIO_PIPE:
606 case DS_VFS_PIPE:
607 return get_byte_growing_buffer (view, offset);
608 case DS_FILE:
609 return get_byte_file (view, offset);
610 case DS_STRING:
611 return get_byte_string (view, offset);
612 case DS_NONE:
613 return get_byte_none (view, offset);
615 assert(!"Unknown datasource type");
616 return -1;
619 static inline int
620 get_byte_indexed (WView *view, offset_type base, offset_type ofs)
622 if (base <= OFFSETTYPE_MAX - ofs)
623 return get_byte (view, base + ofs);
624 return -1;
627 static void
628 view_set_byte (WView *view, offset_type offset, byte b)
630 (void) &b;
631 assert (offset < view_get_filesize (view));
632 assert (view->datasource == DS_FILE);
633 view->ds_file_datalen = 0; /* just force reloading */
636 static void
637 view_set_datasource_none (WView *view)
639 view->datasource = DS_NONE;
642 static void
643 view_set_datasource_vfs_pipe (WView *view, int fd)
645 assert (fd != -1);
646 view->datasource = DS_VFS_PIPE;
647 view->ds_vfs_pipe = fd;
649 view_init_growbuf (view);
652 static void
653 view_set_datasource_stdio_pipe (WView *view, FILE *fp)
655 assert (fp != NULL);
656 view->datasource = DS_STDIO_PIPE;
657 view->ds_stdio_pipe = fp;
659 view_init_growbuf (view);
662 static void
663 view_set_datasource_string (WView *view, const char *s)
665 view->datasource = DS_STRING;
666 view->ds_string_data = (byte *) g_strdup (s);
667 view->ds_string_len = strlen (s);
670 static void
671 view_set_datasource_file (WView *view, int fd, const struct stat *st)
673 view->datasource = DS_FILE;
674 view->ds_file_fd = fd;
675 view->ds_file_filesize = st->st_size;
676 view->ds_file_offset = 0;
677 view->ds_file_data = g_malloc (4096);
678 view->ds_file_datalen = 0;
679 view->ds_file_datasize = 4096;
682 static void
683 view_close_datasource (WView *view)
685 switch (view->datasource) {
686 case DS_NONE:
687 break;
688 case DS_STDIO_PIPE:
689 if (view->ds_stdio_pipe != NULL) {
690 (void) pclose (view->ds_stdio_pipe);
691 display (view);
692 close_error_pipe (D_NORMAL, NULL);
693 view->ds_stdio_pipe = NULL;
695 view_growbuf_free (view);
696 break;
697 case DS_VFS_PIPE:
698 if (view->ds_vfs_pipe != -1) {
699 (void) mc_close (view->ds_vfs_pipe);
700 view->ds_vfs_pipe = -1;
702 view_growbuf_free (view);
703 break;
704 case DS_FILE:
705 (void) mc_close (view->ds_file_fd);
706 view->ds_file_fd = -1;
707 g_free (view->ds_file_data);
708 view->ds_file_data = NULL;
709 break;
710 case DS_STRING:
711 g_free (view->ds_string_data);
712 view->ds_string_data = NULL;
713 break;
714 default:
715 assert (!"Unknown datasource type");
717 view->datasource = DS_NONE;
720 /* {{{ The Coordinate Cache }}} */
723 This cache provides you with a fast lookup to map file offsets into
724 line/column pairs and vice versa. The interface to the mapping is
725 provided by the functions view_coord_to_offset() and
726 view_offset_to_coord().
728 The cache is implemented as a simple sorted array holding entries
729 that map some of the offsets to their line/column pair. Entries that
730 are not cached themselves are interpolated (exactly) from their
731 neighbor entries. The algorithm used for determining the line/column
732 for a specific offset needs to be kept synchronized with the one used
733 in display().
736 enum ccache_type {
737 CCACHE_OFFSET,
738 CCACHE_LINECOL
741 static inline gboolean
742 coord_cache_entry_less (const struct coord_cache_entry *a,
743 const struct coord_cache_entry *b, enum ccache_type crit,
744 gboolean nroff_mode)
746 if (crit == CCACHE_OFFSET)
747 return (a->cc_offset < b->cc_offset);
749 if (a->cc_line < b->cc_line)
750 return TRUE;
752 if (a->cc_line == b->cc_line) {
753 if (nroff_mode) {
754 return (a->cc_nroff_column < b->cc_nroff_column);
755 } else {
756 return (a->cc_column < b->cc_column);
759 return FALSE;
762 #ifdef MC_ENABLE_DEBUGGING_CODE
763 static void view_coord_to_offset (WView *, offset_type *, offset_type, offset_type);
764 static void view_offset_to_coord (WView *, offset_type *, offset_type *, offset_type);
766 static void
767 view_ccache_dump (WView *view)
769 FILE *f;
770 offset_type offset, line, column, nextline_offset, filesize;
771 guint i;
772 const struct coord_cache_entry *cache;
774 assert (view->coord_cache != NULL);
776 filesize = view_get_filesize (view);
777 cache = &(g_array_index (view->coord_cache, struct coord_cache_entry, 0));
779 f = fopen("mcview-ccache.out", "w");
780 if (f == NULL)
781 return;
782 (void)setvbuf(f, NULL, _IONBF, 0);
784 /* cache entries */
785 for (i = 0; i < view->coord_cache->len; i++) {
786 (void) fprintf (f,
787 "entry %8u "
788 "offset %8"OFFSETTYPE_PRId" "
789 "line %8"OFFSETTYPE_PRId" "
790 "column %8"OFFSETTYPE_PRId" "
791 "nroff_column %8"OFFSETTYPE_PRId"\n",
792 (unsigned int) i, cache[i].cc_offset, cache[i].cc_line,
793 cache[i].cc_column, cache[i].cc_nroff_column);
795 (void)fprintf (f, "\n");
797 /* offset -> line/column translation */
798 for (offset = 0; offset < filesize; offset++) {
799 view_offset_to_coord (view, &line, &column, offset);
800 (void)fprintf (f,
801 "offset %8"OFFSETTYPE_PRId" "
802 "line %8"OFFSETTYPE_PRId" "
803 "column %8"OFFSETTYPE_PRId"\n",
804 offset, line, column);
807 /* line/column -> offset translation */
808 for (line = 0; TRUE; line++) {
809 view_coord_to_offset (view, &nextline_offset, line + 1, 0);
810 (void)fprintf (f, "nextline_offset %8"OFFSETTYPE_PRId"\n",
811 nextline_offset);
813 for (column = 0; TRUE; column++) {
814 view_coord_to_offset (view, &offset, line, column);
815 if (offset >= nextline_offset)
816 break;
818 (void)fprintf (f, "line %8"OFFSETTYPE_PRId" column %8"OFFSETTYPE_PRId" offset %8"OFFSETTYPE_PRId"\n",
819 line, column, offset);
822 if (nextline_offset >= filesize - 1)
823 break;
826 (void)fclose (f);
828 #endif
830 static inline gboolean
831 is_nroff_sequence (WView *view, offset_type offset)
833 int c0, c1, c2;
835 /* The following commands are ordered to speed up the calculation. */
837 c1 = get_byte_indexed (view, offset, 1);
838 if (c1 == -1 || c1 != '\b')
839 return FALSE;
841 c0 = get_byte_indexed (view, offset, 0);
842 if (c0 == -1 || !is_printable(c0))
843 return FALSE;
845 c2 = get_byte_indexed (view, offset, 2);
846 if (c2 == -1 || !is_printable(c2))
847 return FALSE;
849 return (c0 == c2 || c0 == '_' || (c0 == '+' && c2 == 'o'));
852 /* Find and return the index of the last cache entry that is
853 * smaller than ''coord'', according to the criterion ''sort_by''. */
854 static inline guint
855 view_ccache_find (WView *view, const struct coord_cache_entry *cache,
856 const struct coord_cache_entry *coord, enum ccache_type sort_by)
858 guint base, i, limit;
860 limit = view->coord_cache->len;
861 assert (limit != 0);
863 base = 0;
864 while (limit > 1) {
865 i = base + limit / 2;
866 if (coord_cache_entry_less (coord, &cache[i], sort_by, view->text_nroff_mode)) {
867 /* continue the search in the lower half of the cache */
868 } else {
869 /* continue the search in the upper half of the cache */
870 base = i;
872 limit = (limit + 1) / 2;
874 return base;
877 /* Look up the missing components of ''coord'', which are given by
878 * ''lookup_what''. The function returns the smallest value that
879 * matches the existing components of ''coord''.
881 static void
882 view_ccache_lookup (WView *view, struct coord_cache_entry *coord,
883 enum ccache_type lookup_what)
885 guint i;
886 struct coord_cache_entry *cache, current, next, entry;
887 enum ccache_type sorter;
888 offset_type limit;
889 enum {
890 NROFF_START,
891 NROFF_BACKSPACE,
892 NROFF_CONTINUATION
893 } nroff_state;
895 if (!view->coord_cache) {
896 view->coord_cache = g_array_new (FALSE, FALSE, sizeof(struct coord_cache_entry));
897 current.cc_offset = 0;
898 current.cc_line = 0;
899 current.cc_column = 0;
900 current.cc_nroff_column = 0;
901 g_array_append_val (view->coord_cache, current);
904 sorter = (lookup_what == CCACHE_OFFSET) ? CCACHE_LINECOL : CCACHE_OFFSET;
906 retry:
907 /* find the two neighbor entries in the cache */
908 cache = &(g_array_index (view->coord_cache, struct coord_cache_entry, 0));
909 i = view_ccache_find (view, cache, coord, sorter);
910 /* now i points to the lower neighbor in the cache */
912 current = cache[i];
913 if (i + 1 < view->coord_cache->len)
914 limit = cache[i + 1].cc_offset;
915 else
916 limit = current.cc_offset + VIEW_COORD_CACHE_GRANUL;
918 entry = current;
919 nroff_state = NROFF_START;
920 for (; current.cc_offset < limit; current = next) {
921 int c, nextc;
923 if ((c = get_byte (view, current.cc_offset)) == -1)
924 break;
926 if (!coord_cache_entry_less (&current, coord, sorter, view->text_nroff_mode)) {
927 if (lookup_what == CCACHE_OFFSET
928 && view->text_nroff_mode
929 && nroff_state != NROFF_START) {
930 /* don't break here */
931 } else {
932 break;
936 /* Provide useful default values for ''next'' */
937 next.cc_offset = current.cc_offset + 1;
938 next.cc_line = current.cc_line;
939 next.cc_column = current.cc_column + 1;
940 next.cc_nroff_column = current.cc_nroff_column + 1;
942 /* and override some of them as necessary. */
943 if (c == '\r') {
944 nextc = get_byte_indexed(view, current.cc_offset, 1);
946 /* Ignore '\r' if it is followed by '\r' or '\n'. If it is
947 * followed by anything else, it is a Mac line ending and
948 * produces a line break.
950 if (nextc == '\r' || nextc == '\n') {
951 next.cc_column = current.cc_column;
952 next.cc_nroff_column = current.cc_nroff_column;
953 } else {
954 next.cc_line = current.cc_line + 1;
955 next.cc_column = 0;
956 next.cc_nroff_column = 0;
959 } else if (nroff_state == NROFF_BACKSPACE) {
960 next.cc_nroff_column = current.cc_nroff_column - 1;
962 } else if (c == '\t') {
963 next.cc_column = offset_rounddown (current.cc_column, 8) + 8;
964 next.cc_nroff_column =
965 offset_rounddown (current.cc_nroff_column, 8) + 8;
967 } else if (c == '\n') {
968 next.cc_line = current.cc_line + 1;
969 next.cc_column = 0;
970 next.cc_nroff_column = 0;
972 } else {
973 /* Use all default values from above */
976 switch (nroff_state) {
977 case NROFF_START:
978 case NROFF_CONTINUATION:
979 if (is_nroff_sequence (view, current.cc_offset))
980 nroff_state = NROFF_BACKSPACE;
981 else
982 nroff_state = NROFF_START;
983 break;
984 case NROFF_BACKSPACE:
985 nroff_state = NROFF_CONTINUATION;
986 break;
989 /* Cache entries must guarantee that for each i < j,
990 * line[i] <= line[j] and column[i] < column[j]. In the case of
991 * nroff sequences and '\r' characters, this is not guaranteed,
992 * so we cannot save them. */
993 if (nroff_state == NROFF_START && c != '\r')
994 entry = next;
997 if (i + 1 == view->coord_cache->len && entry.cc_offset != cache[i].cc_offset) {
998 g_array_append_val (view->coord_cache, entry);
999 goto retry;
1002 if (lookup_what == CCACHE_OFFSET) {
1003 coord->cc_offset = current.cc_offset;
1004 } else {
1005 coord->cc_line = current.cc_line;
1006 coord->cc_column = current.cc_column;
1007 coord->cc_nroff_column = current.cc_nroff_column;
1011 static void
1012 view_coord_to_offset (WView *view, offset_type *ret_offset,
1013 offset_type line, offset_type column)
1015 struct coord_cache_entry coord;
1017 coord.cc_line = line;
1018 coord.cc_column = column;
1019 coord.cc_nroff_column = column;
1020 view_ccache_lookup (view, &coord, CCACHE_OFFSET);
1021 *ret_offset = coord.cc_offset;
1024 static void
1025 view_offset_to_coord (WView *view, offset_type *ret_line,
1026 offset_type *ret_column, offset_type offset)
1028 struct coord_cache_entry coord;
1030 coord.cc_offset = offset;
1031 view_ccache_lookup (view, &coord, CCACHE_LINECOL);
1032 *ret_line = coord.cc_line;
1033 *ret_column = (view->text_nroff_mode)
1034 ? coord.cc_nroff_column
1035 : coord.cc_column;
1038 /* {{{ Cursor Movement }}} */
1041 The following variables have to do with the current position and are
1042 updated by the cursor movement functions.
1044 In hex view and wrapped text view mode, dpy_start marks the offset of
1045 the top-left corner on the screen, in non-wrapping text mode it is
1046 the beginning of the current line. In hex mode, hex_cursor is the
1047 offset of the cursor. In non-wrapping text mode, dpy_text_column is
1048 the number of columns that are hidden on the left side on the screen.
1050 In hex mode, dpy_start is updated by the view_fix_cursor_position()
1051 function in order to keep the other functions simple. In
1052 non-wrapping text mode dpy_start and dpy_text_column are normalized
1053 such that dpy_text_column < view_get_datacolumns().
1056 /* prototypes for functions used by view_moveto_bottom() */
1057 static void view_move_up (WView *, offset_type);
1058 static void view_moveto_bol (WView *);
1060 static void
1061 view_scroll_to_cursor (WView *view)
1063 if (view->hex_mode) {
1064 const offset_type bytes = view->bytes_per_line;
1065 const offset_type displaysize = view->data_area.height * bytes;
1066 const offset_type cursor = view->hex_cursor;
1067 offset_type topleft = view->dpy_start;
1069 if (topleft + displaysize <= cursor)
1070 topleft = offset_rounddown (cursor, bytes)
1071 - (displaysize - bytes);
1072 if (cursor < topleft)
1073 topleft = offset_rounddown (cursor, bytes);
1074 view->dpy_start = topleft;
1075 } else if (view->text_wrap_mode) {
1076 offset_type line, col, columns;
1078 columns = view->data_area.width;
1079 view_offset_to_coord (view, &line, &col, view->dpy_start + view->dpy_text_column);
1080 if (columns != 0)
1081 col = offset_rounddown (col, columns);
1082 view_coord_to_offset (view, &(view->dpy_start), line, col);
1083 view->dpy_text_column = 0;
1084 } else {
1085 /* nothing to do */
1089 static void
1090 view_movement_fixups (WView *view, gboolean reset_search)
1092 view_scroll_to_cursor (view);
1093 if (reset_search) {
1094 view->search_start = view->dpy_start;
1095 view->search_length = 0;
1097 view->dirty++;
1100 static void
1101 view_moveto_top (WView *view)
1103 view->dpy_start = 0;
1104 view->hex_cursor = 0;
1105 view->dpy_text_column = 0;
1106 view_movement_fixups (view, TRUE);
1109 static void
1110 view_moveto_bottom (WView *view)
1112 offset_type datalines, lines_up, filesize, last_offset;
1114 if (view->growbuf_in_use)
1115 view_growbuf_read_until (view, OFFSETTYPE_MAX);
1117 filesize = view_get_filesize (view);
1118 last_offset = offset_doz(filesize, 1);
1119 datalines = view->data_area.height;
1120 lines_up = offset_doz(datalines, 1);
1122 if (view->hex_mode) {
1123 view->hex_cursor = filesize;
1124 view_move_up (view, lines_up);
1125 view->hex_cursor = last_offset;
1126 } else {
1127 view->dpy_start = last_offset;
1128 view_moveto_bol (view);
1129 view_move_up (view, lines_up);
1131 view_movement_fixups (view, TRUE);
1134 static void
1135 view_moveto_bol (WView *view)
1137 if (view->hex_mode) {
1138 view->hex_cursor -= view->hex_cursor % view->bytes_per_line;
1139 } else if (view->text_wrap_mode) {
1140 /* do nothing */
1141 } else {
1142 offset_type line, column;
1143 view_offset_to_coord (view, &line, &column, view->dpy_start);
1144 view_coord_to_offset (view, &(view->dpy_start), line, 0);
1145 view->dpy_text_column = 0;
1147 view_movement_fixups (view, TRUE);
1150 static void
1151 view_moveto_eol (WView *view)
1153 if (view->hex_mode) {
1154 offset_type filesize, bol;
1156 bol = offset_rounddown (view->hex_cursor, view->bytes_per_line);
1157 if (get_byte_indexed (view, bol, view->bytes_per_line - 1) != -1) {
1158 view->hex_cursor = bol + view->bytes_per_line - 1;
1159 } else {
1160 filesize = view_get_filesize (view);
1161 view->hex_cursor = offset_doz(filesize, 1);
1163 } else if (view->text_wrap_mode) {
1164 /* nothing to do */
1165 } else {
1166 offset_type line, col;
1168 view_offset_to_coord (view, &line, &col, view->dpy_start);
1169 view_coord_to_offset (view, &(view->dpy_start), line, OFFSETTYPE_MAX);
1171 view_movement_fixups (view, FALSE);
1174 static void
1175 view_moveto_offset (WView *view, offset_type offset)
1177 if (view->hex_mode) {
1178 view->hex_cursor = offset;
1179 view->dpy_start = offset - offset % view->bytes_per_line;
1180 } else {
1181 view->dpy_start = offset;
1183 view_movement_fixups (view, TRUE);
1186 static void
1187 view_moveto (WView *view, offset_type line, offset_type col)
1189 offset_type offset;
1191 view_coord_to_offset (view, &offset, line, col);
1192 view_moveto_offset (view, offset);
1195 static void
1196 view_move_up (WView *view, offset_type lines)
1198 if (view->hex_mode) {
1199 offset_type bytes = lines * view->bytes_per_line;
1200 if (view->hex_cursor >= bytes) {
1201 view->hex_cursor -= bytes;
1202 if (view->hex_cursor < view->dpy_start)
1203 view->dpy_start = offset_doz (view->dpy_start, bytes);
1204 } else {
1205 view->hex_cursor %= view->bytes_per_line;
1207 } else if (view->text_wrap_mode) {
1208 const screen_dimen width = view->data_area.width;
1209 offset_type i, col, line, linestart;
1211 for (i = 0; i < lines; i++) {
1212 view_offset_to_coord (view, &line, &col, view->dpy_start);
1213 if (col >= width) {
1214 col -= width;
1215 } else if (line >= 1) {
1216 view_coord_to_offset (view, &linestart, line, 0);
1217 view_offset_to_coord (view, &line, &col, linestart - 1);
1219 /* if the only thing that would be displayed were a
1220 * single newline character, advance to the previous
1221 * part of the line. */
1222 if (col > 0 && col % width == 0)
1223 col -= width;
1224 else
1225 col -= col % width;
1226 } else {
1227 /* nothing to do */
1229 view_coord_to_offset (view, &(view->dpy_start), line, col);
1231 } else {
1232 offset_type line, column;
1234 view_offset_to_coord (view, &line, &column, view->dpy_start);
1235 line = offset_doz(line, lines);
1236 view_coord_to_offset (view, &(view->dpy_start), line, column);
1238 view_movement_fixups (view, (lines != 1));
1241 static void
1242 view_move_down (WView *view, offset_type lines)
1244 if (view->hex_mode) {
1245 offset_type i, limit, last_byte;
1247 last_byte = view_get_filesize (view);
1248 if (last_byte >= (offset_type) view->bytes_per_line)
1249 limit = last_byte - view->bytes_per_line;
1250 else
1251 limit = 0;
1252 for (i = 0; i < lines && view->hex_cursor < limit; i++) {
1253 view->hex_cursor += view->bytes_per_line;
1254 if (lines != 1)
1255 view->dpy_start += view->bytes_per_line;
1258 } else if (view->dpy_end == view_get_filesize (view)) {
1259 /* don't move further down. There's nothing more to see. */
1261 } else if (view->text_wrap_mode) {
1262 offset_type line, col, i;
1264 for (i = 0; i < lines; i++) {
1265 offset_type new_offset, chk_line, chk_col;
1267 view_offset_to_coord (view, &line, &col, view->dpy_start);
1268 col += view->data_area.width;
1269 view_coord_to_offset (view, &new_offset, line, col);
1271 /* skip to the next line if the only thing that would be
1272 * displayed is the newline character. */
1273 view_offset_to_coord (view, &chk_line, &chk_col, new_offset);
1274 if (chk_line == line && chk_col == col
1275 && get_byte (view, new_offset) == '\n')
1276 new_offset++;
1278 view->dpy_start = new_offset;
1281 } else {
1282 offset_type line, col;
1284 view_offset_to_coord (view, &line, &col, view->dpy_start);
1285 line += lines;
1286 view_coord_to_offset (view, &(view->dpy_start), line, col);
1288 view_movement_fixups (view, (lines != 1));
1291 static void
1292 view_move_left (WView *view, offset_type columns)
1294 if (view->hex_mode) {
1295 assert (columns == 1);
1296 if (view->hexview_in_text || !view->hexedit_lownibble) {
1297 if (view->hex_cursor > 0)
1298 view->hex_cursor--;
1300 if (!view->hexview_in_text)
1301 view->hexedit_lownibble = !view->hexedit_lownibble;
1302 } else if (view->text_wrap_mode) {
1303 /* nothing to do */
1304 } else {
1305 if (view->dpy_text_column >= columns)
1306 view->dpy_text_column -= columns;
1307 else
1308 view->dpy_text_column = 0;
1310 view_movement_fixups (view, FALSE);
1313 static void
1314 view_move_right (WView *view, offset_type columns)
1316 if (view->hex_mode) {
1317 assert (columns == 1);
1318 if (view->hexview_in_text || view->hexedit_lownibble) {
1319 if (get_byte_indexed (view, view->hex_cursor, 1) != -1)
1320 view->hex_cursor++;
1322 if (!view->hexview_in_text)
1323 view->hexedit_lownibble = !view->hexedit_lownibble;
1324 } else if (view->text_wrap_mode) {
1325 /* nothing to do */
1326 } else {
1327 view->dpy_text_column += columns;
1329 view_movement_fixups (view, FALSE);
1332 /* {{{ Toggling of viewer modes }}} */
1334 static void
1335 view_toggle_hex_mode (WView *view)
1337 view->hex_mode = !view->hex_mode;
1339 if (view->hex_mode) {
1340 view->hex_cursor = view->dpy_start;
1341 view->dpy_start =
1342 offset_rounddown (view->dpy_start, view->bytes_per_line);
1343 view->widget.options |= W_WANT_CURSOR;
1344 } else {
1345 view->dpy_start = view->hex_cursor;
1346 view_moveto_bol (view);
1347 view->widget.options &= ~W_WANT_CURSOR;
1349 altered_hex_mode = 1;
1350 view->dpy_bbar_dirty = TRUE;
1351 view->dirty++;
1354 static void
1355 view_toggle_hexedit_mode (WView *view)
1357 view->hexedit_mode = !view->hexedit_mode;
1358 view->dpy_bbar_dirty = TRUE;
1359 view->dirty++;
1362 static void
1363 view_toggle_wrap_mode (WView *view)
1365 view->text_wrap_mode = !view->text_wrap_mode;
1366 if (view->text_wrap_mode) {
1367 view_scroll_to_cursor (view);
1368 } else {
1369 offset_type line;
1371 view_offset_to_coord (view, &line, &(view->dpy_text_column), view->dpy_start);
1372 view_coord_to_offset (view, &(view->dpy_start), line, 0);
1374 view->dpy_bbar_dirty = TRUE;
1375 view->dirty++;
1378 static void
1379 view_toggle_nroff_mode (WView *view)
1381 view->text_nroff_mode = !view->text_nroff_mode;
1382 altered_nroff_flag = 1;
1383 view->dpy_bbar_dirty = TRUE;
1384 view->dirty++;
1387 static void
1388 view_toggle_magic_mode (WView *view)
1390 char *filename, *command;
1392 altered_magic_flag = 1;
1393 view->magic_mode = !view->magic_mode;
1394 filename = g_strdup (view->filename);
1395 command = g_strdup (view->command);
1397 view_done (view);
1398 view_load (view, command, filename, 0);
1399 g_free (filename);
1400 g_free (command);
1401 view->dpy_bbar_dirty = TRUE;
1402 view->dirty++;
1405 /* {{{ Miscellaneous functions }}} */
1407 static void
1408 view_done (WView *view)
1410 /* Save current file position */
1411 if (mcview_remember_file_position && view->filename != NULL) {
1412 char *canon_fname;
1413 offset_type line, col;
1415 canon_fname = vfs_canon (view->filename);
1416 view_offset_to_coord (view, &line, &col, view->dpy_start);
1417 save_file_position (canon_fname, line + 1, col);
1418 g_free (canon_fname);
1421 /* Write back the global viewer mode */
1422 default_hex_mode = view->hex_mode;
1423 default_nroff_flag = view->text_nroff_mode;
1424 default_magic_flag = view->magic_mode;
1425 global_wrap_mode = view->text_wrap_mode;
1427 /* Free memory used by the viewer */
1429 /* view->widget needs no destructor */
1431 g_free (view->filename), view->filename = NULL;
1432 g_free (view->command), view->command = NULL;
1434 view_close_datasource (view);
1435 /* the growing buffer is freed with the datasource */
1437 if (view->coord_cache) {
1438 g_array_free (view->coord_cache, TRUE), view->coord_cache = NULL;
1441 view_hexedit_free_change_list (view);
1442 /* FIXME: what about view->search_exp? */
1445 static void
1446 view_show_error (WView *view, const char *msg)
1448 view_close_datasource (view);
1449 if (view_is_in_panel (view)) {
1450 view_set_datasource_string (view, msg);
1451 } else {
1452 message (D_ERROR, MSG_ERROR, "%s", msg);
1456 static gboolean
1457 view_load_command_output (WView *view, const char *command)
1459 FILE *fp;
1461 view_close_datasource (view);
1463 open_error_pipe ();
1464 if ((fp = popen (command, "r")) == NULL) {
1465 /* Avoid two messages. Message from stderr has priority. */
1466 display (view);
1467 if (!close_error_pipe (view_is_in_panel (view) ? -1 : D_ERROR, NULL))
1468 view_show_error (view, _(" Cannot spawn child process "));
1469 return FALSE;
1472 /* First, check if filter produced any output */
1473 view_set_datasource_stdio_pipe (view, fp);
1474 if (get_byte (view, 0) == -1) {
1475 view_close_datasource (view);
1477 /* Avoid two messages. Message from stderr has priority. */
1478 display (view);
1479 if (!close_error_pipe (view_is_in_panel (view) ? -1 : D_ERROR, NULL))
1480 view_show_error (view, _("Empty output from child filter"));
1481 return FALSE;
1483 return TRUE;
1486 gboolean
1487 view_load (WView *view, const char *command, const char *file,
1488 int start_line)
1490 int i, type;
1491 int fd = -1;
1492 char tmp[BUF_MEDIUM];
1493 struct stat st;
1494 gboolean retval = FALSE;
1496 assert (view->bytes_per_line != 0);
1497 view_done (view);
1499 /* Set up the state */
1500 view_set_datasource_none (view);
1501 view->filename = g_strdup (file);
1502 view->command = 0;
1504 /* Clear the markers */
1505 view->marker = 0;
1506 for (i = 0; i < 10; i++)
1507 view->marks[i] = 0;
1509 if (!view_is_in_panel (view)) {
1510 view->dpy_text_column = 0;
1513 if (command && (view->magic_mode || file == NULL || file[0] == '\0')) {
1514 retval = view_load_command_output (view, command);
1515 } else if (file != NULL && file[0] != '\0') {
1516 /* Open the file */
1517 if ((fd = mc_open (file, O_RDONLY | O_NONBLOCK)) == -1) {
1518 g_snprintf (tmp, sizeof (tmp), _(" Cannot open \"%s\"\n %s "),
1519 file, unix_error_string (errno));
1520 view_show_error (view, tmp);
1521 goto finish;
1524 /* Make sure we are working with a regular file */
1525 if (mc_fstat (fd, &st) == -1) {
1526 mc_close (fd);
1527 g_snprintf (tmp, sizeof (tmp), _(" Cannot stat \"%s\"\n %s "),
1528 file, unix_error_string (errno));
1529 view_show_error (view, tmp);
1530 goto finish;
1533 if (!S_ISREG (st.st_mode)) {
1534 mc_close (fd);
1535 view_show_error (view, _(" Cannot view: not a regular file "));
1536 goto finish;
1539 if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1) {
1540 /* Must be one of those nice files that grow (/proc) */
1541 view_set_datasource_vfs_pipe (view, fd);
1542 } else {
1543 type = get_compression_type (fd);
1545 if (view->magic_mode && (type != COMPRESSION_NONE)) {
1546 g_free (view->filename);
1547 view->filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
1549 view_set_datasource_file (view, fd, &st);
1551 retval = TRUE;
1554 finish:
1555 view->command = g_strdup (command);
1556 view->dpy_start = 0;
1557 view->search_start = 0;
1558 view->search_length = 0;
1559 view->dpy_text_column = 0;
1560 view->last_search = 0; /* Start a new search */
1562 assert (view->bytes_per_line != 0);
1563 if (mcview_remember_file_position && file != NULL && start_line == 0) {
1564 long line, col;
1565 char *canon_fname;
1567 canon_fname = vfs_canon (file);
1568 load_file_position (file, &line, &col);
1569 g_free (canon_fname);
1570 view_moveto (view, offset_doz(line, 1), col);
1571 } else if (start_line > 0) {
1572 view_moveto (view, start_line - 1, 0);
1575 view->hexedit_lownibble = FALSE;
1576 view->hexview_in_text = FALSE;
1577 view->change_list = NULL;
1579 return retval;
1582 /* {{{ Display management }}} */
1584 static void
1585 view_update_bytes_per_line (WView *view)
1587 const screen_dimen cols = view->data_area.width;
1588 int bytes;
1590 if (cols < 8 + 17)
1591 bytes = 4;
1592 else
1593 bytes = 4 * ((cols - 8) / ((cols < 80) ? 17 : 18));
1594 assert(bytes != 0);
1596 view->bytes_per_line = bytes;
1597 view->dirty = max_dirt_limit + 1; /* To force refresh */
1600 static void
1601 view_percent (WView *view, offset_type p)
1603 const screen_dimen top = view->status_area.top;
1604 const screen_dimen right = view->status_area.left + view->status_area.width;
1605 const screen_dimen height = view->status_area.height;
1606 int percent;
1607 offset_type filesize;
1609 if (height < 1 || right < 4)
1610 return;
1611 if (view_may_still_grow (view))
1612 return;
1613 filesize = view_get_filesize (view);
1615 if (filesize == 0 || view->dpy_end == filesize)
1616 percent = 100;
1617 else if (p > (INT_MAX / 100))
1618 percent = p / (filesize / 100);
1619 else
1620 percent = p * 100 / filesize;
1622 widget_move (view, top, right - 4);
1623 tty_printf ("%3d%%", percent);
1626 static void
1627 view_display_status (WView *view)
1629 const screen_dimen top = view->status_area.top;
1630 const screen_dimen left = view->status_area.left;
1631 const screen_dimen width = view->status_area.width;
1632 const screen_dimen height = view->status_area.height;
1633 const char *file_label, *file_name;
1634 screen_dimen file_label_width;
1635 int i;
1637 if (height < 1)
1638 return;
1640 tty_setcolor (SELECTED_COLOR);
1641 widget_move (view, top, left);
1642 hline (' ', width);
1644 file_label = _("File: %s");
1645 file_label_width = strlen (file_label) - 2;
1646 file_name = view->filename ? view->filename
1647 : view->command ? view->command
1648 : "";
1650 if (width < file_label_width + 6)
1651 addstr ((char *) name_trunc (file_name, width));
1652 else {
1653 i = (width > 22 ? 22 : width) - file_label_width;
1654 tty_printf (file_label, name_trunc (file_name, i));
1655 if (width > 46) {
1656 widget_move (view, top, left + 24);
1657 /* FIXME: the format strings need to be changed when offset_type changes */
1658 if (view->hex_mode)
1659 tty_printf (_("Offset 0x%08lx"), (unsigned long) view->hex_cursor);
1660 else {
1661 offset_type line, col;
1662 view_offset_to_coord (view, &line, &col, view->dpy_start);
1663 tty_printf (_("Line %lu Col %lu"),
1664 (unsigned long) line + 1,
1665 (unsigned long) (view->text_wrap_mode ? col : view->dpy_text_column));
1668 if (width > 62) {
1669 offset_type filesize;
1670 filesize = view_get_filesize (view);
1671 widget_move (view, top, left + 43);
1672 if (!view_may_still_grow (view)) {
1673 tty_printf (_("%s bytes"), size_trunc (filesize));
1674 } else {
1675 tty_printf (_(">= %s bytes"), size_trunc (filesize));
1678 if (width > 26) {
1679 view_percent (view, view->hex_mode
1680 ? view->hex_cursor
1681 : view->dpy_end);
1684 tty_setcolor (SELECTED_COLOR);
1687 static inline void
1688 view_display_clean (WView *view)
1690 tty_setcolor (NORMAL_COLOR);
1691 widget_erase ((Widget *) view);
1692 if (view->dpy_frame_size != 0) {
1693 draw_double_box (view->widget.parent, view->widget.y,
1694 view->widget.x, view->widget.lines,
1695 view->widget.cols);
1699 typedef enum {
1700 MARK_NORMAL,
1701 MARK_SELECTED,
1702 MARK_CURSOR,
1703 MARK_CHANGED
1704 } mark_t;
1706 static inline int
1707 view_count_backspaces (WView *view, off_t offset)
1709 int backspaces = 0;
1710 while (offset >= 2 * backspaces
1711 && get_byte (view, offset - 2 * backspaces) == '\b')
1712 backspaces++;
1713 return backspaces;
1716 static void
1717 view_display_ruler (WView *view)
1719 static const char ruler_chars[] = "|----*----";
1720 const screen_dimen top = view->ruler_area.top;
1721 const screen_dimen left = view->ruler_area.left;
1722 const screen_dimen width = view->ruler_area.width;
1723 const screen_dimen height = view->ruler_area.height;
1724 const screen_dimen line_row = (ruler == RULER_TOP) ? 0 : 1;
1725 const screen_dimen nums_row = (ruler == RULER_TOP) ? 1 : 0;
1727 char r_buff[10];
1728 offset_type cl;
1729 screen_dimen c;
1731 if (ruler == RULER_NONE || height < 1)
1732 return;
1734 tty_setcolor (MARKED_COLOR);
1735 for (c = 0; c < width; c++) {
1736 cl = view->dpy_text_column + c;
1737 if (line_row < height) {
1738 widget_move (view, top + line_row, left + c);
1739 tty_print_char (ruler_chars[cl % 10]);
1742 if ((cl != 0) && (cl % 10) == 0) {
1743 g_snprintf (r_buff, sizeof (r_buff), "%"OFFSETTYPE_PRId, cl);
1744 if (nums_row < height) {
1745 widget_move (view, top + nums_row, left + c - 1);
1746 tty_print_string (r_buff);
1750 attrset (NORMAL_COLOR);
1753 static void
1754 view_display_hex (WView *view)
1756 const screen_dimen top = view->data_area.top;
1757 const screen_dimen left = view->data_area.left;
1758 const screen_dimen height = view->data_area.height;
1759 const screen_dimen width = view->data_area.width;
1760 const int ngroups = view->bytes_per_line / 4;
1761 const screen_dimen text_start =
1762 8 + 13 * ngroups + ((width < 80) ? 0 : (ngroups - 1 + 1));
1763 /* 8 characters are used for the file offset, and every hex group
1764 * takes 13 characters. On ``big'' screens, the groups are separated
1765 * by an extra vertical line, and there is an extra space before the
1766 * text column.
1769 screen_dimen row, col;
1770 offset_type from;
1771 int c;
1772 mark_t boldflag = MARK_NORMAL;
1773 struct hexedit_change_node *curr = view->change_list;
1774 size_t i;
1776 char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */
1777 int bytes; /* Number of bytes already printed on the line */
1779 view_display_clean (view);
1781 /* Find the first displayable changed byte */
1782 from = view->dpy_start;
1783 while (curr && (curr->offset < from)) {
1784 curr = curr->next;
1787 for (row = 0; get_byte (view, from) != -1 && row < height; row++) {
1788 col = 0;
1790 /* Print the hex offset */
1791 g_snprintf (hex_buff, sizeof (hex_buff), "%08"OFFSETTYPE_PRIX" ", from);
1792 widget_move (view, top + row, left);
1793 tty_setcolor (MARKED_COLOR);
1794 for (i = 0; col < width && hex_buff[i] != '\0'; i++) {
1795 tty_print_char(hex_buff[i]);
1796 col += 1;
1798 tty_setcolor (NORMAL_COLOR);
1800 for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++) {
1802 if ((c = get_byte (view, from)) == -1)
1803 break;
1805 /* Save the cursor position for view_place_cursor() */
1806 if (from == view->hex_cursor && !view->hexview_in_text) {
1807 view->cursor_row = row;
1808 view->cursor_col = col;
1811 /* Determine the state of the current byte */
1812 boldflag =
1813 (from == view->hex_cursor) ? MARK_CURSOR
1814 : (curr != NULL && from == curr->offset) ? MARK_CHANGED
1815 : (view->search_start <= from &&
1816 from < view->search_start + view->search_length
1817 ) ? MARK_SELECTED
1818 : MARK_NORMAL;
1820 /* Determine the value of the current byte */
1821 if (curr != NULL && from == curr->offset) {
1822 c = curr->value;
1823 curr = curr->next;
1826 /* Select the color for the hex number */
1827 tty_setcolor (
1828 boldflag == MARK_NORMAL ? NORMAL_COLOR :
1829 boldflag == MARK_SELECTED ? MARKED_COLOR :
1830 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
1831 /* boldflag == MARK_CURSOR */
1832 view->hexview_in_text ? MARKED_SELECTED_COLOR :
1833 VIEW_UNDERLINED_COLOR);
1835 /* Print the hex number */
1836 widget_move (view, top + row, left + col);
1837 if (col < width) {
1838 tty_print_char (hex_char[c / 16]);
1839 col += 1;
1841 if (col < width) {
1842 tty_print_char (hex_char[c % 16]);
1843 col += 1;
1846 /* Print the separator */
1847 tty_setcolor (NORMAL_COLOR);
1848 if (bytes != view->bytes_per_line - 1) {
1849 if (col < width) {
1850 tty_print_char (' ');
1851 col += 1;
1854 /* After every four bytes, print a group separator */
1855 if (bytes % 4 == 3) {
1856 if (view->data_area.width >= 80 && col < width) {
1857 tty_print_one_vline ();
1858 col += 1;
1860 if (col < width) {
1861 tty_print_char (' ');
1862 col += 1;
1867 /* Select the color for the character; this differs from the
1868 * hex color when boldflag == MARK_CURSOR */
1869 tty_setcolor (
1870 boldflag == MARK_NORMAL ? NORMAL_COLOR :
1871 boldflag == MARK_SELECTED ? MARKED_COLOR :
1872 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
1873 /* boldflag == MARK_CURSOR */
1874 view->hexview_in_text ? VIEW_UNDERLINED_COLOR :
1875 MARKED_SELECTED_COLOR);
1877 c = convert_to_display_c (c);
1878 if (!is_printable (c))
1879 c = '.';
1881 /* Print corresponding character on the text side */
1882 if (text_start + bytes < width) {
1883 widget_move (view, top + row, left + text_start + bytes);
1884 tty_print_char (c);
1887 /* Save the cursor position for view_place_cursor() */
1888 if (from == view->hex_cursor && view->hexview_in_text) {
1889 view->cursor_row = row;
1890 view->cursor_col = text_start + bytes;
1895 /* Be polite to the other functions */
1896 tty_setcolor (NORMAL_COLOR);
1898 view_place_cursor (view);
1899 view->dpy_end = from;
1902 static void
1903 view_display_text (WView * view)
1905 const screen_dimen left = view->data_area.left;
1906 const screen_dimen top = view->data_area.top;
1907 const screen_dimen width = view->data_area.width;
1908 const screen_dimen height = view->data_area.height;
1909 screen_dimen row, col;
1910 offset_type from;
1911 int c;
1912 struct hexedit_change_node *curr = view->change_list;
1914 view_display_clean (view);
1915 view_display_ruler (view);
1917 /* Find the first displayable changed byte */
1918 from = view->dpy_start;
1919 while (curr && (curr->offset < from)) {
1920 curr = curr->next;
1923 tty_setcolor (NORMAL_COLOR);
1924 for (row = 0, col = 0; row < height && (c = get_byte (view, from)) != -1; from++) {
1926 if (view->text_nroff_mode && c == '\b') {
1927 int c_prev;
1928 int c_next;
1930 if ((c_next = get_byte_indexed (view, from, 1)) != -1
1931 && is_printable (c_next)
1932 && from >= 1
1933 && (c_prev = get_byte (view, from - 1)) != -1
1934 && is_printable (c_prev)
1935 && (c_prev == c_next || c_prev == '_'
1936 || (c_prev == '+' && c_next == 'o'))) {
1937 if (col == 0) {
1938 if (row == 0) {
1939 /* We're inside an nroff character sequence at the
1940 * beginning of the screen -- just skip the
1941 * backspace and continue with the next character. */
1942 continue;
1944 row--;
1945 col = width;
1947 col--;
1948 if (c_prev == '_' && (c_next != '_' || view_count_backspaces (view, from) == 1))
1949 tty_setcolor (VIEW_UNDERLINED_COLOR);
1950 else
1951 tty_setcolor (MARKED_COLOR);
1952 continue;
1956 if ((c == '\n') || (col >= width && view->text_wrap_mode)) {
1957 col = 0;
1958 row++;
1959 if (c == '\n' || row >= height)
1960 continue;
1963 if (c == '\r') {
1964 c = get_byte_indexed(view, from, 1);
1965 if (c == '\r' || c == '\n')
1966 continue;
1967 col = 0;
1968 row++;
1969 continue;
1972 if (c == '\t') {
1973 offset_type line, column;
1974 view_offset_to_coord (view, &line, &column, from);
1975 col += (8 - column % 8);
1976 if (view->text_wrap_mode && col >= width && width != 0) {
1977 row += col / width;
1978 col %= width;
1980 continue;
1983 if (view->search_start <= from
1984 && from < view->search_start + view->search_length) {
1985 tty_setcolor (SELECTED_COLOR);
1988 if (col >= view->dpy_text_column
1989 && col - view->dpy_text_column < width) {
1990 widget_move (view, top + row, left + (col - view->dpy_text_column));
1991 c = convert_to_display_c (c);
1992 if (!is_printable (c))
1993 c = '.';
1994 tty_print_char (c);
1996 col++;
1997 tty_setcolor (NORMAL_COLOR);
1999 view->dpy_end = from;
2002 /* Displays as much data from view->dpy_start as fits on the screen */
2003 static void
2004 display (WView *view)
2006 view_compute_areas (view);
2007 if (view->hex_mode) {
2008 view_display_hex (view);
2009 } else {
2010 view_display_text (view);
2012 view_display_status (view);
2015 static void
2016 view_place_cursor (WView *view)
2018 const screen_dimen top = view->data_area.top;
2019 const screen_dimen left = view->data_area.left;
2020 screen_dimen col;
2022 col = view->cursor_col;
2023 if (!view->hexview_in_text && view->hexedit_lownibble)
2024 col++;
2025 widget_move (&view->widget, top + view->cursor_row, left + col);
2028 static void
2029 view_update (WView *view)
2031 static int dirt_limit = 1;
2033 if (view->dpy_bbar_dirty) {
2034 view->dpy_bbar_dirty = FALSE;
2035 view_labels (view);
2036 buttonbar_redraw (view->widget.parent);
2039 if (view->dirty > dirt_limit) {
2040 /* Too many updates skipped -> force a update */
2041 display (view);
2042 view->dirty = 0;
2043 /* Raise the update skipping limit */
2044 dirt_limit++;
2045 if (dirt_limit > max_dirt_limit)
2046 dirt_limit = max_dirt_limit;
2048 if (view->dirty) {
2049 if (is_idle ()) {
2050 /* We have time to update the screen properly */
2051 display (view);
2052 view->dirty = 0;
2053 if (dirt_limit > 1)
2054 dirt_limit--;
2055 } else {
2056 /* We are busy -> skipping full update,
2057 only the status line is updated */
2058 view_display_status (view);
2060 /* Here we had a refresh, if fast scrolling does not work
2061 restore the refresh, although this should not happen */
2065 /* {{{ Hex editor }}} */
2067 static void
2068 enqueue_change (struct hexedit_change_node **head,
2069 struct hexedit_change_node *node)
2071 /* chnode always either points to the head of the list or
2072 * to one of the ->next fields in the list. The value at
2073 * this location will be overwritten with the new node. */
2074 struct hexedit_change_node **chnode = head;
2076 while (*chnode != NULL && (*chnode)->offset < node->offset)
2077 chnode = &((*chnode)->next);
2079 node->next = *chnode;
2080 *chnode = node;
2083 static cb_ret_t
2084 view_handle_editkey (WView *view, int key)
2086 struct hexedit_change_node *node;
2087 byte byte_val;
2089 /* Has there been a change at this position? */
2090 node = view->change_list;
2091 while (node && (node->offset != view->hex_cursor))
2092 node = node->next;
2094 if (!view->hexview_in_text) {
2095 /* Hex editing */
2096 unsigned int hexvalue = 0;
2098 if (key >= '0' && key <= '9')
2099 hexvalue = 0 + (key - '0');
2100 else if (key >= 'A' && key <= 'F')
2101 hexvalue = 10 + (key - 'A');
2102 else if (key >= 'a' && key <= 'f')
2103 hexvalue = 10 + (key - 'a');
2104 else
2105 return MSG_NOT_HANDLED;
2107 if (node)
2108 byte_val = node->value;
2109 else
2110 byte_val = get_byte (view, view->hex_cursor);
2112 if (view->hexedit_lownibble) {
2113 byte_val = (byte_val & 0xf0) | (hexvalue);
2114 } else {
2115 byte_val = (byte_val & 0x0f) | (hexvalue << 4);
2117 } else {
2118 /* Text editing */
2119 if (key < 256 && (is_printable (key) || (key == '\n')))
2120 byte_val = key;
2121 else
2122 return MSG_NOT_HANDLED;
2124 if (!node) {
2125 node = g_new (struct hexedit_change_node, 1);
2126 node->offset = view->hex_cursor;
2127 node->value = byte_val;
2128 enqueue_change (&view->change_list, node);
2129 } else {
2130 node->value = byte_val;
2132 view->dirty++;
2133 view_update (view);
2134 view_move_right (view, 1);
2135 return MSG_HANDLED;
2138 static gboolean
2139 view_hexedit_save_changes (WView *view)
2141 struct hexedit_change_node *curr, *next;
2142 int fp, answer;
2143 char *text, *error;
2145 if (view->change_list == NULL)
2146 return TRUE;
2148 retry_save:
2149 assert (view->filename != NULL);
2150 fp = mc_open (view->filename, O_WRONLY);
2151 if (fp == -1)
2152 goto save_error;
2154 for (curr = view->change_list; curr != NULL; curr = next) {
2155 next = curr->next;
2157 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
2158 || mc_write (fp, &(curr->value), 1) != 1)
2159 goto save_error;
2161 /* delete the saved item from the change list */
2162 view->change_list = next;
2163 view->dirty++;
2164 view_set_byte (view, curr->offset, curr->value);
2165 g_free (curr);
2168 if (mc_close (fp) == -1) {
2169 error = g_strdup (strerror (errno));
2170 message (D_ERROR, _(" Save file "),
2171 _(" Error while closing the file: \n %s \n"
2172 " Data may have been written or not. "), error);
2173 g_free (error);
2175 view_update (view);
2176 return TRUE;
2178 save_error:
2179 error = g_strdup (strerror (errno));
2180 text = g_strdup_printf (_(" Cannot save file: \n %s "), error);
2181 g_free (error);
2182 (void) mc_close (fp);
2184 answer = query_dialog (_(" Save file "), text, D_ERROR,
2185 2, _("&Retry"), _("&Cancel"));
2186 g_free (text);
2188 if (answer == 0)
2189 goto retry_save;
2190 return FALSE;
2193 /* {{{ Miscellaneous functions }}} */
2195 static gboolean
2196 view_ok_to_quit (WView *view)
2198 int r;
2200 if (view->change_list == NULL)
2201 return TRUE;
2203 r = query_dialog (_("Quit"),
2204 _(" File was modified, Save with exit? "), D_NORMAL, 3,
2205 _("&Cancel quit"), _("&Yes"), _("&No"));
2207 switch (r) {
2208 case 1:
2209 return view_hexedit_save_changes (view);
2210 case 2:
2211 view_hexedit_free_change_list (view);
2212 return TRUE;
2213 default:
2214 return FALSE;
2218 static inline void
2219 my_define (Dlg_head *h, int idx, const char *text, void (*fn) (WView *),
2220 WView *view)
2222 buttonbar_set_label_data (h, idx, text, (buttonbarfn) fn, view);
2225 /* {{{ Searching }}} */
2227 /* Case insensitive search of text in data */
2228 static int
2229 icase_search_p (WView *view, char *text, char *data, int nothing)
2231 const char *q;
2232 int lng;
2233 const int direction = view->direction;
2235 (void) nothing;
2237 /* If we are searching backwards, reverse the string */
2238 if (direction == -1) {
2239 g_strreverse (text);
2240 g_strreverse (data);
2243 q = _icase_search (text, data, &lng);
2245 if (direction == -1) {
2246 g_strreverse (text);
2247 g_strreverse (data);
2250 if (q != 0) {
2251 if (direction > 0)
2252 view->search_start = q - data - lng;
2253 else
2254 view->search_start = strlen (data) - (q - data);
2255 view->search_length = lng;
2256 return 1;
2258 return 0;
2261 static char *
2262 grow_string_buffer (char *text, gulong *size)
2264 char *new;
2266 /* The grow steps */
2267 *size += 160;
2268 new = g_realloc (text, *size);
2269 if (text == NULL) {
2270 *new = '\0';
2272 return new;
2275 static char *
2276 get_line_at (WView *view, offset_type *p, offset_type *skipped)
2278 char *buffer = NULL;
2279 gulong buffer_size = 0;
2280 offset_type usable_size = 0;
2281 int ch;
2282 const int direction = view->direction;
2283 offset_type pos = *p;
2284 offset_type i = 0;
2285 int prev = '\0';
2287 *skipped = 0;
2289 if (pos == 0 && direction == -1)
2290 return 0;
2292 /* skip over all the possible zeros in the file */
2293 while ((ch = get_byte (view, pos)) == 0) {
2294 if (pos == 0 && direction == -1)
2295 return 0;
2296 pos += direction;
2297 i++;
2299 *skipped = i;
2301 if (i == 0 && (pos != 0 || direction == -1)) {
2302 prev = get_byte (view, pos - direction);
2303 if ((prev == -1) || (prev == '\n'))
2304 prev = '\0';
2307 for (i = 1; ch != -1; ch = get_byte (view, pos)) {
2308 if (i >= usable_size) {
2309 buffer = grow_string_buffer (buffer, &buffer_size);
2310 usable_size = buffer_size - 2; /* prev & null terminator */
2313 buffer[i++] = ch;
2315 if (pos == 0 && direction == -1)
2316 break;
2318 pos += direction;
2320 if (ch == '\n' || ch == '\0') {
2321 i--; /* Strip newline/zero */
2322 break;
2326 if (buffer) {
2327 buffer[0] = prev;
2328 buffer[i] = '\0';
2330 /* If we are searching backwards, reverse the string */
2331 if (direction == -1) {
2332 g_strreverse (buffer + 1);
2336 *p = pos;
2337 return buffer;
2340 static void
2341 search_update_steps (WView *view)
2343 offset_type filesize = view_get_filesize (view);
2344 if (filesize != 0)
2345 view->update_steps = 40000;
2346 else /* viewing a data stream, not a file */
2347 view->update_steps = filesize / 100;
2349 /* Do not update the percent display but every 20 ks */
2350 if (view->update_steps < 20000)
2351 view->update_steps = 20000;
2354 static void
2355 search (WView *view, char *text,
2356 int (*search) (WView *, char *, char *, int))
2358 char *s = NULL; /* The line we read from the view buffer */
2359 offset_type p, beginning, search_start;
2360 int found_len;
2361 int search_status;
2362 Dlg_head *d = 0;
2364 /* Used to keep track of where the line starts, when looking forward
2365 * is the index before transfering the line; the reverse case uses
2366 * the position returned after the line has been read */
2367 offset_type forward_line_start;
2368 offset_type reverse_line_start;
2369 offset_type t;
2371 if (verbose) {
2372 d = create_message (D_NORMAL, _("Search"), _("Searching %s"), text);
2373 mc_refresh ();
2376 found_len = view->search_length;
2377 search_start = view->search_start;
2379 if (view->direction == 1) {
2380 p = search_start + ((found_len) ? 1 : 0);
2381 } else {
2382 p = search_start - ((found_len && search_start >= 1) ? 1 : 0);
2384 beginning = p;
2386 /* Compute the percent steps */
2387 search_update_steps (view);
2388 view->update_activate = 0;
2390 enable_interrupt_key ();
2391 for (;; g_free (s)) {
2392 if (p >= view->update_activate) {
2393 view->update_activate += view->update_steps;
2394 if (verbose) {
2395 view_percent (view, p);
2396 mc_refresh ();
2398 if (got_interrupt ())
2399 break;
2401 forward_line_start = p;
2402 s = get_line_at (view, &p, &t);
2403 reverse_line_start = p;
2405 if (!s)
2406 break;
2408 search_status = (*search) (view, text, s + 1, match_normal);
2409 if (search_status < 0) {
2410 g_free (s);
2411 break;
2414 if (search_status == 0)
2415 continue;
2417 /* We found the string */
2419 /* Handle ^ and $ when regexp search starts at the middle of the line */
2420 if (*s && !view->search_start && (search == regexp_view_search)) {
2421 if ((*text == '^' && view->direction == 1)
2422 || (view->direction == -1 && text[strlen (text) - 1] == '$')
2424 continue;
2427 /* Record the position used to continue the search */
2428 if (view->direction == 1)
2429 t += forward_line_start;
2430 else
2431 t = reverse_line_start ? reverse_line_start + 2 : 0;
2432 view->search_start += t;
2434 if (t != beginning) {
2435 view->dpy_start = t;
2438 g_free (s);
2439 break;
2441 disable_interrupt_key ();
2442 if (verbose) {
2443 dlg_run_done (d);
2444 destroy_dlg (d);
2446 if (!s) {
2447 message (D_NORMAL, _("Search"), _(" Search string not found "));
2448 view->search_length = 0;
2452 /* Search buffer (its size is len) in the complete buffer
2453 * returns the position where the block was found or INVALID_OFFSET
2454 * if not found */
2455 static offset_type
2456 block_search (WView *view, const char *buffer, int len)
2458 int direction = view->direction;
2459 const char *d = buffer;
2460 char b;
2461 offset_type e;
2463 enable_interrupt_key ();
2464 if (direction == 1)
2465 e = view->search_start + ((view->search_length) ? 1 : 0);
2466 else
2467 e = view->search_start
2468 - ((view->search_length && view->search_start >= 1) ? 1 : 0);
2470 search_update_steps (view);
2471 view->update_activate = 0;
2473 if (direction == -1) {
2474 for (d += len - 1;; e--) {
2475 if (e <= view->update_activate) {
2476 view->update_activate -= view->update_steps;
2477 if (verbose) {
2478 view_percent (view, e);
2479 mc_refresh ();
2481 if (got_interrupt ())
2482 break;
2484 b = get_byte (view, e);
2486 if (*d == b) {
2487 if (d == buffer) {
2488 disable_interrupt_key ();
2489 return e;
2491 d--;
2492 } else {
2493 e += buffer + len - 1 - d;
2494 d = buffer + len - 1;
2496 if (e == 0)
2497 break;
2499 } else {
2500 while (get_byte (view, e) != -1) {
2501 if (e >= view->update_activate) {
2502 view->update_activate += view->update_steps;
2503 if (verbose) {
2504 view_percent (view, e);
2505 mc_refresh ();
2507 if (got_interrupt ())
2508 break;
2510 b = get_byte (view, e++);
2512 if (*d == b) {
2513 d++;
2514 if (d - buffer == len) {
2515 disable_interrupt_key ();
2516 return e - len;
2518 } else {
2519 e -= d - buffer;
2520 d = buffer;
2524 disable_interrupt_key ();
2525 return INVALID_OFFSET;
2529 * Search in the hex mode. Supported input:
2530 * - numbers (oct, dec, hex). Each of them matches one byte.
2531 * - strings in double quotes. Matches exactly without quotes.
2533 static void
2534 hex_search (WView *view, const char *text)
2536 char *buffer; /* Parsed search string */
2537 char *cur; /* Current position in it */
2538 int block_len; /* Length of the search string */
2539 offset_type pos; /* Position of the string in the file */
2540 int parse_error = 0;
2542 if (!*text) {
2543 view->search_length = 0;
2544 return;
2547 /* buffer will never be longer that text */
2548 buffer = g_new (char, strlen (text));
2549 cur = buffer;
2551 /* First convert the string to a stream of bytes */
2552 while (*text) {
2553 int val;
2554 int ptr;
2556 /* Skip leading spaces */
2557 if (*text == ' ' || *text == '\t') {
2558 text++;
2559 continue;
2562 /* %i matches octal, decimal, and hexadecimal numbers */
2563 if (sscanf (text, "%i%n", &val, &ptr) > 0) {
2564 /* Allow signed and unsigned char in the user input */
2565 if (val < -128 || val > 255) {
2566 parse_error = 1;
2567 break;
2570 *cur++ = (char) val;
2571 text += ptr;
2572 continue;
2575 /* Try quoted string, strip quotes */
2576 if (*text == '"') {
2577 const char *next_quote;
2579 text++;
2580 next_quote = strchr (text, '"');
2581 if (next_quote) {
2582 memcpy (cur, text, next_quote - text);
2583 cur += next_quote - text;
2584 text = next_quote + 1;
2585 continue;
2587 /* fall through */
2590 parse_error = 1;
2591 break;
2594 block_len = cur - buffer;
2596 /* No valid bytes in the user input */
2597 if (block_len <= 0 || parse_error) {
2598 message (D_NORMAL, _("Search"), _("Invalid hex search expression"));
2599 g_free (buffer);
2600 view->search_length = 0;
2601 return;
2604 /* Then start the search */
2605 pos = block_search (view, buffer, block_len);
2607 g_free (buffer);
2609 if (pos == INVALID_OFFSET) {
2610 message (D_NORMAL, _("Search"), _(" Search string not found "));
2611 view->search_length = 0;
2612 return;
2615 view->search_start = pos;
2616 view->search_length = block_len;
2617 /* Set the edit cursor to the search position, left nibble */
2618 view->hex_cursor = view->search_start;
2619 view->hexedit_lownibble = FALSE;
2621 /* Adjust the file offset */
2622 view->dpy_start = pos - pos % view->bytes_per_line;
2625 static int
2626 regexp_view_search (WView *view, char *pattern, char *string,
2627 int match_type)
2629 static regex_t r;
2630 static char *old_pattern = NULL;
2631 static int old_type;
2632 regmatch_t pmatch[1];
2633 int i, flags = REG_ICASE;
2635 if (old_pattern == NULL || strcmp (old_pattern, pattern) != 0
2636 || old_type != match_type) {
2637 if (old_pattern != NULL) {
2638 regfree (&r);
2639 g_free (old_pattern);
2640 old_pattern = 0;
2642 for (i = 0; pattern[i] != '\0'; i++) {
2643 if (isupper ((unsigned char) pattern[i])) {
2644 flags = 0;
2645 break;
2648 flags |= REG_EXTENDED;
2649 if (regcomp (&r, pattern, flags)) {
2650 message (D_ERROR, MSG_ERROR, _(" Invalid regular expression "));
2651 return -1;
2653 old_pattern = g_strdup (pattern);
2654 old_type = match_type;
2656 if (regexec (&r, string, 1, pmatch, 0) != 0)
2657 return 0;
2658 view->search_length = pmatch[0].rm_eo - pmatch[0].rm_so;
2659 view->search_start = pmatch[0].rm_so;
2660 return 1;
2663 static void
2664 do_regexp_search (WView *view)
2666 search (view, view->search_exp, regexp_view_search);
2667 /* Had a refresh here */
2668 view->dirty++;
2669 view_update (view);
2672 static void
2673 do_normal_search (WView *view)
2675 if (view->hex_mode)
2676 hex_search (view, view->search_exp);
2677 else
2678 search (view, view->search_exp, icase_search_p);
2679 /* Had a refresh here */
2680 view->dirty++;
2681 view_update (view);
2684 /* {{{ User-definable commands }}} */
2687 The functions in this section can be bound to hotkeys. They are all
2688 of the same type (taking a pointer to WView as parameter and
2689 returning void). TODO: In the not-too-distant future, these commands
2690 will become fully configurable, like they already are in the
2691 internal editor. By convention, all the function names end in
2692 "_cmd".
2695 static void
2696 view_help_cmd (void)
2698 interactive_display (NULL, "[Internal File Viewer]");
2701 /* Toggle between hexview and hexedit mode */
2702 static void
2703 view_toggle_hexedit_mode_cmd (WView *view)
2705 view_toggle_hexedit_mode (view);
2706 view_update (view);
2709 /* Toggle between wrapped and unwrapped view */
2710 static void
2711 view_toggle_wrap_mode_cmd (WView *view)
2713 view_toggle_wrap_mode (view);
2714 view_update (view);
2717 /* Toggle between hex view and text view */
2718 static void
2719 view_toggle_hex_mode_cmd (WView *view)
2721 view_toggle_hex_mode (view);
2722 view_update (view);
2725 static void
2726 view_moveto_line_cmd (WView *view)
2728 char *answer, *answer_end, prompt[BUF_SMALL];
2729 offset_type line, col;
2731 view_offset_to_coord (view, &line, &col, view->dpy_start);
2733 g_snprintf (prompt, sizeof (prompt),
2734 _(" The current line number is %d.\n"
2735 " Enter the new line number:"), (int) (line + 1));
2736 answer = input_dialog (_(" Goto line "), prompt, MC_HISTORY_VIEW_GOTO_LINE, "");
2737 if (answer != NULL && answer[0] != '\0') {
2738 errno = 0;
2739 line = strtoul (answer, &answer_end, 10);
2740 if (*answer_end == '\0' && errno == 0 && line >= 1)
2741 view_moveto (view, line - 1, 0);
2743 g_free (answer);
2744 view->dirty++;
2745 view_update (view);
2748 static void
2749 view_moveto_addr_cmd (WView *view)
2751 char *line, *error, prompt[BUF_SMALL];
2752 offset_type addr;
2754 g_snprintf (prompt, sizeof (prompt),
2755 _(" The current address is 0x%lx.\n"
2756 " Enter the new address:"), view->hex_cursor);
2757 line = input_dialog (_(" Goto Address "), prompt, MC_HISTORY_VIEW_GOTO_ADDR, "");
2758 if (line != NULL) {
2759 if (*line != '\0') {
2760 addr = strtoul (line, &error, 0);
2761 if ((*error == '\0') && get_byte (view, addr) != -1) {
2762 view_moveto_offset (view, addr);
2763 } else {
2764 message (D_ERROR, _("Warning"), _(" Invalid address "));
2767 g_free (line);
2769 view->dirty++;
2770 view_update (view);
2773 static void
2774 view_hexedit_save_changes_cmd (WView *view)
2776 (void) view_hexedit_save_changes (view);
2779 /* {{{ Searching }}} */
2781 static void
2782 regexp_search (WView *view, int direction)
2784 const char *defval;
2785 char *regexp;
2786 static char *last_regexp;
2788 defval = (last_regexp != NULL ? last_regexp : "");
2790 regexp = input_dialog (_("Search"), _(" Enter regexp:"), MC_HISTORY_VIEW_SEARCH_REGEX, defval);
2791 if (regexp == NULL || regexp[0] == '\0') {
2792 g_free (regexp);
2793 return;
2796 g_free (last_regexp);
2797 view->search_exp = last_regexp = regexp;
2799 view->direction = direction;
2800 do_regexp_search (view);
2801 view->last_search = do_regexp_search;
2804 /* {{{ User-definable commands }}} */
2806 static void
2807 view_regexp_search_cmd (WView *view)
2809 regexp_search (view, 1);
2812 /* Both views */
2813 static void
2814 view_normal_search_cmd (WView *view)
2816 char *defval, *exp = NULL;
2817 static char *last_search_string;
2819 enum {
2820 SEARCH_DLG_HEIGHT = 8,
2821 SEARCH_DLG_WIDTH = 58
2824 static int replace_backwards;
2825 int treplace_backwards = replace_backwards;
2827 static QuickWidget quick_widgets[] = {
2828 {quick_button, 6, 10, 5, SEARCH_DLG_HEIGHT, N_("&Cancel"), 0,
2829 B_CANCEL,
2830 0, 0, NULL},
2831 {quick_button, 2, 10, 5, SEARCH_DLG_HEIGHT, N_("&OK"), 0, B_ENTER,
2832 0, 0, NULL},
2833 {quick_checkbox, 3, SEARCH_DLG_WIDTH, 4, SEARCH_DLG_HEIGHT,
2834 N_("&Backwards"), 0, 0,
2835 0, 0, NULL},
2836 {quick_input, 3, SEARCH_DLG_WIDTH, 3, SEARCH_DLG_HEIGHT, "", 52, 0,
2837 0, 0, N_("Search")},
2838 {quick_label, 2, SEARCH_DLG_WIDTH, 2, SEARCH_DLG_HEIGHT,
2839 N_(" Enter search string:"), 0, 0,
2840 0, 0, 0},
2841 NULL_QuickWidget
2843 static QuickDialog Quick_input = {
2844 SEARCH_DLG_WIDTH, SEARCH_DLG_HEIGHT, -1, 0, N_("Search"),
2845 "[Input Line Keys]", quick_widgets, 0
2848 defval = g_strdup (last_search_string != NULL ? last_search_string : "");
2849 convert_to_display (defval);
2851 quick_widgets[2].result = &treplace_backwards;
2852 quick_widgets[3].str_result = &exp;
2853 quick_widgets[3].text = defval;
2855 if (quick_dialog (&Quick_input) == B_CANCEL)
2856 goto cleanup;
2858 replace_backwards = treplace_backwards;
2860 if (exp == NULL || exp[0] == '\0')
2861 goto cleanup;
2863 convert_from_input (exp);
2865 g_free (last_search_string);
2866 view->search_exp = last_search_string = exp;
2867 exp = NULL;
2869 view->direction = replace_backwards ? -1 : 1;
2870 do_normal_search (view);
2871 view->last_search = do_normal_search;
2873 cleanup:
2874 g_free (exp);
2875 g_free (defval);
2878 static void
2879 view_toggle_magic_mode_cmd (WView *view)
2881 view_toggle_magic_mode (view);
2882 view_update (view);
2885 static void
2886 view_toggle_nroff_mode_cmd (WView *view)
2888 view_toggle_nroff_mode (view);
2889 view_update (view);
2892 static void
2893 view_quit_cmd (WView *view)
2895 if (view_ok_to_quit (view))
2896 dlg_stop (view->widget.parent);
2899 /* {{{ Miscellaneous functions }}} */
2901 /* Define labels and handlers for functional keys */
2902 static void
2903 view_labels (WView *view)
2905 Dlg_head *h = view->widget.parent;
2907 buttonbar_set_label (h, 1, Q_("ButtonBar|Help"), view_help_cmd);
2909 my_define (h, 10, Q_("ButtonBar|Quit"), view_quit_cmd, view);
2910 my_define (h, 4, view->hex_mode
2911 ? Q_("ButtonBar|Ascii")
2912 : Q_("ButtonBar|Hex"),
2913 view_toggle_hex_mode_cmd, view);
2914 my_define (h, 5, view->hex_mode
2915 ? Q_("ButtonBar|Goto")
2916 : Q_("ButtonBar|Line"),
2917 view->hex_mode ? view_moveto_addr_cmd : view_moveto_line_cmd, view);
2919 if (view->hex_mode) {
2920 if (view->hexedit_mode) {
2921 my_define (h, 2, Q_("ButtonBar|View"),
2922 view_toggle_hexedit_mode_cmd, view);
2923 } else if (view->datasource == DS_FILE) {
2924 my_define (h, 2, Q_("ButtonBar|Edit"),
2925 view_toggle_hexedit_mode_cmd, view);
2926 } else {
2927 buttonbar_clear_label (h, 2);
2929 my_define (h, 6, Q_("ButtonBar|Save"),
2930 view_hexedit_save_changes_cmd, view);
2931 } else {
2932 my_define (h, 2, view->text_wrap_mode
2933 ? Q_("ButtonBar|UnWrap")
2934 : Q_("ButtonBar|Wrap"),
2935 view_toggle_wrap_mode_cmd, view);
2936 my_define (h, 6, Q_("ButtonBar|RxSrch"),
2937 view_regexp_search_cmd, view);
2940 my_define (h, 7, view->hex_mode
2941 ? Q_("ButtonBar|HxSrch")
2942 : Q_("ButtonBar|Search"),
2943 view_normal_search_cmd, view);
2944 my_define (h, 8, view->magic_mode
2945 ? Q_("ButtonBar|Raw")
2946 : Q_("ButtonBar|Parse"),
2947 view_toggle_magic_mode_cmd, view);
2949 /* don't override the key to access the main menu */
2950 if (!view_is_in_panel (view)) {
2951 my_define (h, 9, view->text_nroff_mode
2952 ? Q_("ButtonBar|Unform")
2953 : Q_("ButtonBar|Format"),
2954 view_toggle_nroff_mode_cmd, view);
2955 my_define (h, 3, Q_("ButtonBar|Quit"), view_quit_cmd, view);
2959 /* {{{ Event handling }}} */
2961 /* Check for left and right arrows, possibly with modifiers */
2962 static cb_ret_t
2963 check_left_right_keys (WView *view, int c)
2965 if (c == KEY_LEFT) {
2966 view_move_left (view, 1);
2967 return MSG_HANDLED;
2970 if (c == KEY_RIGHT) {
2971 view_move_right (view, 1);
2972 return MSG_HANDLED;
2975 /* Ctrl with arrows moves by 10 postions in the unwrap mode */
2976 if (view->hex_mode || view->text_wrap_mode)
2977 return MSG_NOT_HANDLED;
2979 if (c == (KEY_M_CTRL | KEY_LEFT)) {
2980 if (view->dpy_text_column >= 10)
2981 view->dpy_text_column -= 10;
2982 else
2983 view->dpy_text_column = 0;
2984 view->dirty++;
2985 return MSG_HANDLED;
2988 if (c == (KEY_M_CTRL | KEY_RIGHT)) {
2989 if (view->dpy_text_column <= OFFSETTYPE_MAX - 10)
2990 view->dpy_text_column += 10;
2991 else
2992 view->dpy_text_column = OFFSETTYPE_MAX;
2993 view->dirty++;
2994 return MSG_HANDLED;
2997 return MSG_NOT_HANDLED;
3000 /* {{{ User-definable commands }}} */
3002 static void
3003 view_continue_search_cmd (WView *view)
3005 if (view->last_search) {
3006 view->last_search (view);
3007 } else {
3008 /* if not... then ask for an expression */
3009 view_normal_search_cmd (view);
3013 static void
3014 view_toggle_ruler_cmd (WView *view)
3016 static const enum ruler_type next[3] = {
3017 RULER_TOP,
3018 RULER_BOTTOM,
3019 RULER_NONE
3022 assert ((size_t) ruler < 3);
3023 ruler = next[(size_t) ruler];
3024 view->dirty++;
3027 /* {{{ Event handling }}} */
3029 static void view_cmk_move_up (void *w, int n) {
3030 view_move_up ((WView *) w, n);
3032 static void view_cmk_move_down (void *w, int n) {
3033 view_move_down ((WView *) w, n);
3035 static void view_cmk_moveto_top (void *w, int n) {
3036 (void) &n;
3037 view_moveto_top ((WView *) w);
3039 static void view_cmk_moveto_bottom (void *w, int n) {
3040 (void) &n;
3041 view_moveto_bottom ((WView *) w);
3044 /* Both views */
3045 static cb_ret_t
3046 view_handle_key (WView *view, int c)
3048 c = convert_from_input_c (c);
3050 if (view->hex_mode) {
3051 switch (c) {
3052 case '\t':
3053 view->hexview_in_text = !view->hexview_in_text;
3054 view->dirty++;
3055 return MSG_HANDLED;
3057 case XCTRL ('a'):
3058 view_moveto_bol (view);
3059 view->dirty++;
3060 return MSG_HANDLED;
3062 case XCTRL ('b'):
3063 view_move_left (view, 1);
3064 return MSG_HANDLED;
3066 case XCTRL ('e'):
3067 view_moveto_eol (view);
3068 return MSG_HANDLED;
3070 case XCTRL ('f'):
3071 view_move_right (view, 1);
3072 return MSG_HANDLED;
3075 if (view->hexedit_mode
3076 && view_handle_editkey (view, c) == MSG_HANDLED)
3077 return MSG_HANDLED;
3080 if (check_left_right_keys (view, c))
3081 return MSG_HANDLED;
3083 if (check_movement_keys (c, view->data_area.height + 1, view,
3084 view_cmk_move_up, view_cmk_move_down,
3085 view_cmk_moveto_top, view_cmk_moveto_bottom))
3086 return MSG_HANDLED;
3088 switch (c) {
3090 case '?':
3091 regexp_search (view, -1);
3092 return MSG_HANDLED;
3094 case '/':
3095 regexp_search (view, 1);
3096 return MSG_HANDLED;
3098 /* Continue search */
3099 case XCTRL ('r'):
3100 case XCTRL ('s'):
3101 case 'n':
3102 case KEY_F (17):
3103 view_continue_search_cmd (view);
3104 return MSG_HANDLED;
3106 /* toggle ruler */
3107 case ALT ('r'):
3108 view_toggle_ruler_cmd (view);
3109 return MSG_HANDLED;
3111 case 'h':
3112 view_move_left (view, 1);
3113 return MSG_HANDLED;
3115 case 'j':
3116 case '\n':
3117 case 'e':
3118 view_move_down (view, 1);
3119 return MSG_HANDLED;
3121 case 'd':
3122 view_move_down (view, (view->data_area.height + 1) / 2);
3123 return MSG_HANDLED;
3125 case 'u':
3126 view_move_up (view, (view->data_area.height + 1) / 2);
3127 return MSG_HANDLED;
3129 case 'k':
3130 case 'y':
3131 view_move_up (view, 1);
3132 return MSG_HANDLED;
3134 case 'l':
3135 view_move_right (view, 1);
3136 return MSG_HANDLED;
3138 case ' ':
3139 case 'f':
3140 view_move_down (view, view->data_area.height);
3141 return MSG_HANDLED;
3143 case XCTRL ('o'):
3144 view_other_cmd ();
3145 return MSG_HANDLED;
3147 /* Unlike Ctrl-O, run a new shell if the subshell is not running. */
3148 case '!':
3149 exec_shell ();
3150 return MSG_HANDLED;
3152 case 'b':
3153 view_move_up (view, view->data_area.height);
3154 return MSG_HANDLED;
3156 case KEY_IC:
3157 view_move_up (view, 2);
3158 return MSG_HANDLED;
3160 case KEY_DC:
3161 view_move_down (view, 2);
3162 return MSG_HANDLED;
3164 case 'm':
3165 view->marks[view->marker] = view->dpy_start;
3166 return MSG_HANDLED;
3168 case 'r':
3169 view->dpy_start = view->marks[view->marker];
3170 view->dirty++;
3171 return MSG_HANDLED;
3173 /* Use to indicate parent that we want to see the next/previous file */
3174 /* Does not work in panel mode */
3175 case XCTRL ('f'):
3176 case XCTRL ('b'):
3177 if (!view_is_in_panel (view))
3178 view->move_dir = c == XCTRL ('f') ? 1 : -1;
3179 /* FALLTHROUGH */
3180 case 'q':
3181 case XCTRL ('g'):
3182 case ESC_CHAR:
3183 if (view_ok_to_quit (view))
3184 view->want_to_quit = TRUE;
3185 return MSG_HANDLED;
3187 #ifdef HAVE_CHARSET
3188 case XCTRL ('t'):
3189 do_select_codepage ();
3190 view->dirty++;
3191 view_update (view);
3192 return MSG_HANDLED;
3193 #endif /* HAVE_CHARSET */
3195 #ifdef MC_ENABLE_DEBUGGING_CODE
3196 case 't': /* mnemonic: "test" */
3197 view_ccache_dump (view);
3198 return MSG_HANDLED;
3199 #endif
3201 if (c >= '0' && c <= '9')
3202 view->marker = c - '0';
3204 /* Key not used */
3205 return MSG_NOT_HANDLED;
3208 /* Both views */
3209 static int
3210 view_event (WView *view, Gpm_Event *event, int *result)
3212 screen_dimen y, x;
3214 *result = MOU_NORMAL;
3216 /* We are not interested in the release events */
3217 if (!(event->type & (GPM_DOWN | GPM_DRAG)))
3218 return 0;
3220 /* Wheel events */
3221 if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) {
3222 view_move_up (view, 2);
3223 return 1;
3225 if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
3226 view_move_down (view, 2);
3227 return 1;
3230 x = event->x;
3231 y = event->y;
3233 /* Scrolling left and right */
3234 if (!view->text_wrap_mode) {
3235 if (x < view->data_area.width * 1/4) {
3236 view_move_left (view, 1);
3237 goto processed;
3238 } else if (x < view->data_area.width * 3/4) {
3239 /* ignore the click */
3240 } else {
3241 view_move_right (view, 1);
3242 goto processed;
3246 /* Scrolling up and down */
3247 if (y < view->data_area.top + view->data_area.height * 1/3) {
3248 if (mouse_move_pages_viewer)
3249 view_move_up (view, view->data_area.height / 2);
3250 else
3251 view_move_up (view, 1);
3252 goto processed;
3253 } else if (y < view->data_area.top + view->data_area.height * 2/3) {
3254 /* ignore the click */
3255 } else {
3256 if (mouse_move_pages_viewer)
3257 view_move_down (view, view->data_area.height / 2);
3258 else
3259 view_move_down (view, 1);
3260 goto processed;
3263 return 0;
3265 processed:
3266 *result = MOU_REPEAT;
3267 return 1;
3270 /* Real view only */
3271 static int
3272 real_view_event (Gpm_Event *event, void *x)
3274 WView *view = (WView *) x;
3275 int result;
3277 if (view_event (view, event, &result))
3278 view_update (view);
3279 return result;
3282 static void
3283 view_adjust_size (Dlg_head *h)
3285 WView *view;
3286 WButtonBar *bar;
3288 /* Look up the viewer and the buttonbar, we assume only two widgets here */
3289 view = (WView *) find_widget_type (h, view_callback);
3290 bar = find_buttonbar (h);
3291 widget_set_size (&view->widget, 0, 0, LINES - 1, COLS);
3292 widget_set_size ((Widget *) bar, LINES - 1, 0, 1, COLS);
3294 view_compute_areas (view);
3295 view_update_bytes_per_line (view);
3298 /* Callback for the view dialog */
3299 static cb_ret_t
3300 view_dialog_callback (Dlg_head *h, dlg_msg_t msg, int parm)
3302 switch (msg) {
3303 case DLG_RESIZE:
3304 view_adjust_size (h);
3305 return MSG_HANDLED;
3307 default:
3308 return default_dlg_callback (h, msg, parm);
3312 /* {{{ External interface }}} */
3314 /* Real view only */
3316 mc_internal_viewer (const char *command, const char *file,
3317 int *move_dir_p, int start_line)
3319 gboolean succeeded;
3320 WView *wview;
3321 WButtonBar *bar;
3322 Dlg_head *view_dlg;
3324 /* Create dialog and widgets, put them on the dialog */
3325 view_dlg =
3326 create_dlg (0, 0, LINES, COLS, NULL, view_dialog_callback,
3327 "[Internal File Viewer]", NULL, DLG_WANT_TAB);
3329 wview = view_new (0, 0, COLS, LINES - 1, 0);
3331 bar = buttonbar_new (1);
3333 add_widget (view_dlg, bar);
3334 add_widget (view_dlg, wview);
3336 succeeded = view_load (wview, command, file, start_line);
3337 if (succeeded) {
3338 run_dlg (view_dlg);
3339 if (move_dir_p)
3340 *move_dir_p = wview->move_dir;
3341 } else {
3342 if (move_dir_p)
3343 *move_dir_p = 0;
3345 destroy_dlg (view_dlg);
3347 return succeeded;
3350 /* {{{ Miscellaneous functions }}} */
3352 static void
3353 view_hook (void *v)
3355 WView *view = (WView *) v;
3356 WPanel *panel;
3358 /* If the user is busy typing, wait until he finishes to update the
3359 screen */
3360 if (!is_idle ()) {
3361 if (!hook_present (idle_hook, view_hook))
3362 add_hook (&idle_hook, view_hook, v);
3363 return;
3366 delete_hook (&idle_hook, view_hook);
3368 if (get_current_type () == view_listing)
3369 panel = current_panel;
3370 else if (get_other_type () == view_listing)
3371 panel = other_panel;
3372 else
3373 return;
3375 view_load (view, 0, panel->dir.list[panel->selected].fname, 0);
3376 display (view);
3379 /* {{{ Event handling }}} */
3381 static cb_ret_t
3382 view_callback (Widget *w, widget_msg_t msg, int parm)
3384 WView *view = (WView *) w;
3385 cb_ret_t i;
3386 Dlg_head *h = view->widget.parent;
3388 view_compute_areas (view);
3389 view_update_bytes_per_line (view);
3391 switch (msg) {
3392 case WIDGET_INIT:
3393 if (view_is_in_panel (view))
3394 add_hook (&select_file_hook, view_hook, view);
3395 else
3396 view->dpy_bbar_dirty = TRUE;
3397 return MSG_HANDLED;
3399 case WIDGET_DRAW:
3400 display (view);
3401 return MSG_HANDLED;
3403 case WIDGET_CURSOR:
3404 if (view->hex_mode)
3405 view_place_cursor (view);
3406 return MSG_HANDLED;
3408 case WIDGET_KEY:
3409 i = view_handle_key ((WView *) view, parm);
3410 if (view->want_to_quit && !view_is_in_panel (view))
3411 dlg_stop (h);
3412 else {
3413 view_update (view);
3415 return i;
3417 case WIDGET_FOCUS:
3418 view->dpy_bbar_dirty = TRUE;
3419 view_update (view);
3420 return MSG_HANDLED;
3422 case WIDGET_DESTROY:
3423 view_done (view);
3424 if (view_is_in_panel (view))
3425 delete_hook (&select_file_hook, view_hook);
3426 return MSG_HANDLED;
3428 default:
3429 return default_proc (msg, parm);
3433 /* {{{ External interface }}} */
3435 WView *
3436 view_new (int y, int x, int cols, int lines, int is_panel)
3438 WView *view = g_new0 (WView, 1);
3439 size_t i;
3441 init_widget (&view->widget, y, x, lines, cols,
3442 view_callback,
3443 real_view_event);
3445 view->filename = NULL;
3446 view->command = NULL;
3448 view_set_datasource_none (view);
3450 view->growbuf_in_use = FALSE;
3451 /* leave the other growbuf fields uninitialized */
3453 view->hex_mode = FALSE;
3454 view->hexedit_mode = FALSE;
3455 view->hexview_in_text = FALSE;
3456 view->text_nroff_mode = FALSE;
3457 view->text_wrap_mode = FALSE;
3458 view->magic_mode = FALSE;
3460 view->hexedit_lownibble = FALSE;
3461 view->coord_cache = NULL;
3463 view->dpy_frame_size = is_panel ? 1 : 0;
3464 view->dpy_start = 0;
3465 view->dpy_text_column = 0;
3466 view->dpy_end= 0;
3467 view->hex_cursor = 0;
3468 view->cursor_col = 0;
3469 view->cursor_row = 0;
3470 view->change_list = NULL;
3472 /* {status,ruler,data}_area are left uninitialized */
3474 view->dirty = 0;
3475 view->dpy_bbar_dirty = TRUE;
3476 view->bytes_per_line = 1;
3478 view->search_start = 0;
3479 view->search_length = 0;
3480 view->search_exp = NULL;
3481 view->direction = 1; /* forward */
3482 view->last_search = 0; /* it's a function */
3484 view->want_to_quit = FALSE;
3485 view->marker = 0;
3486 for (i = 0; i < sizeof(view->marks) / sizeof(view->marks[0]); i++)
3487 view->marks[i] = 0;
3489 view->move_dir = 0;
3490 view->update_steps = 0;
3491 view->update_activate = 0;
3493 if (default_hex_mode)
3494 view_toggle_hex_mode (view);
3495 if (default_nroff_flag)
3496 view_toggle_nroff_mode (view);
3497 if (global_wrap_mode)
3498 view_toggle_wrap_mode (view);
3499 if (default_magic_flag)
3500 view_toggle_magic_mode (view);
3502 return view;