replaced gboolean by bool (from mhl/types.h)
[midnight-commander.git] / src / view.c
blob59031f66eadc215e07a02a23cb9efd391523225b
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>
48 #include "global.h"
49 #include "tty.h"
50 #include "cmd.h" /* For view_other_cmd */
51 #include "dialog.h" /* Needed by widget.h */
52 #include "widget.h" /* Needed for buttonbar_new */
53 #include "color.h"
54 #include "mouse.h"
55 #include "help.h"
56 #include "key.h" /* For mi_getch() */
57 #include "layout.h"
58 #include "setup.h"
59 #include "wtools.h" /* For query_set_sel() */
60 #include "dir.h"
61 #include "panel.h" /* Needed for current_panel and other_panel */
62 #include "win.h"
63 #include "execute.h"
64 #include "main.h" /* slow_terminal */
65 #include "view.h"
66 #include "history.h"
67 #include "charsets.h"
68 #include "selcodepage.h"
70 /* Block size for reading files in parts */
71 #define VIEW_PAGE_SIZE ((size_t) 8192)
72 #define VIEW_COORD_CACHE_GRANUL 1024
74 typedef unsigned char byte;
76 /* Offset in bytes into a file */
77 typedef unsigned long offset_type;
78 #define INVALID_OFFSET ((offset_type) -1)
79 #define OFFSETTYPE_MAX (~((offset_type) 0))
80 #define OFFSETTYPE_PRIX "lX"
81 #define OFFSETTYPE_PRId "lu"
83 /* A width or height on the screen */
84 typedef unsigned int screen_dimen;
86 /* A cache entry for mapping offsets into line/column pairs and vice versa.
87 * cc_offset, cc_line, and cc_column are the 0-based values of the offset,
88 * line and column of that cache entry. cc_nroff_column is the column
89 * corresponding to cc_offset in nroff mode.
91 struct coord_cache_entry {
92 offset_type cc_offset;
93 offset_type cc_line;
94 offset_type cc_column;
95 offset_type cc_nroff_column;
98 /* A node for building a change list on change_list */
99 struct hexedit_change_node {
100 struct hexedit_change_node *next;
101 offset_type offset;
102 byte value;
105 /* data sources of the view */
106 enum view_ds {
107 DS_NONE, /* No data available */
108 DS_STDIO_PIPE, /* Data comes from a pipe using popen/pclose */
109 DS_VFS_PIPE, /* Data comes from a piped-in VFS file */
110 DS_FILE, /* Data comes from a VFS file */
111 DS_STRING /* Data comes from a string in memory */
114 struct area {
115 screen_dimen top, left;
116 screen_dimen height, width;
119 struct WView {
120 Widget widget;
122 char *filename; /* Name of the file */
123 char *command; /* Command used to pipe data in */
125 enum view_ds datasource; /* Where the displayed data comes from */
127 /* stdio pipe data source */
128 FILE *ds_stdio_pipe; /* Output of a shell command */
130 /* vfs pipe data source */
131 int ds_vfs_pipe; /* Non-seekable vfs file descriptor */
133 /* vfs file data source */
134 int ds_file_fd; /* File with random access */
135 off_t ds_file_filesize; /* Size of the file */
136 off_t ds_file_offset; /* Offset of the currently loaded data */
137 byte *ds_file_data; /* Currently loaded data */
138 size_t ds_file_datalen; /* Number of valid bytes in file_data */
139 size_t ds_file_datasize; /* Number of allocated bytes in file_data */
141 /* string data source */
142 byte *ds_string_data; /* The characters of the string */
143 size_t ds_string_len; /* The length of the string */
145 /* Growing buffers information */
146 bool growbuf_in_use; /* Use the growing buffers? */
147 byte **growbuf_blockptr; /* Pointer to the block pointers */
148 size_t growbuf_blocks; /* The number of blocks in *block_ptr */
149 size_t growbuf_lastindex; /* Number of bytes in the last page of the
150 growing buffer */
151 bool growbuf_finished; /* TRUE when all data has been read. */
153 /* Editor modes */
154 bool hex_mode; /* Hexview or Hexedit */
155 bool hexedit_mode; /* Hexedit */
156 bool hexview_in_text; /* Is the hexview cursor in the text area? */
157 bool text_nroff_mode; /* Nroff-style highlighting */
158 bool text_wrap_mode; /* Wrap text lines to fit them on the screen */
159 bool magic_mode; /* Preprocess the file using external programs */
161 /* Additional editor state */
162 bool hexedit_lownibble; /* Are we editing the last significant nibble? */
163 GArray *coord_cache; /* Cache for mapping offsets to cursor positions */
165 /* Display information */
166 screen_dimen dpy_frame_size;/* Size of the frame surrounding the real viewer */
167 offset_type dpy_start; /* Offset of the displayed data */
168 offset_type dpy_end; /* Offset after the displayed data */
169 offset_type dpy_text_column;/* Number of skipped columns in non-wrap
170 * text mode */
171 offset_type hex_cursor; /* Hexview cursor position in file */
172 screen_dimen cursor_col; /* Cursor column */
173 screen_dimen cursor_row; /* Cursor row */
174 struct hexedit_change_node *change_list; /* Linked list of changes */
175 struct area status_area; /* Where the status line is displayed */
176 struct area ruler_area; /* Where the ruler is displayed */
177 struct area data_area; /* Where the data is displayed */
179 int dirty; /* Number of skipped updates */
180 bool dpy_bbar_dirty; /* Does the button bar need to be updated? */
182 /* Mode variables */
183 int bytes_per_line; /* Number of bytes per line in hex mode */
185 /* Search variables */
186 offset_type search_start; /* First character to start searching from */
187 offset_type search_length; /* Length of found string or 0 if none was found */
188 char *search_exp; /* The search expression */
189 int direction; /* 1= forward; -1 backward */
190 void (*last_search)(WView *);
191 /* Pointer to the last search command */
192 bool want_to_quit; /* Prepare for cleanup ... */
194 /* Markers */
195 int marker; /* mark to use */
196 offset_type marks [10]; /* 10 marks: 0..9 */
198 int move_dir; /* return value from widget:
199 * 0 do nothing
200 * -1 view previous file
201 * 1 view next file
204 offset_type update_steps; /* The number of bytes between percent
205 * increments */
206 offset_type update_activate;/* Last point where we updated the status */
210 /* {{{ Global Variables }}} */
212 /* Maxlimit for skipping updates */
213 int max_dirt_limit = 10;
215 /* If set, show a ruler */
216 static enum ruler_type {
217 RULER_NONE,
218 RULER_TOP,
219 RULER_BOTTOM
220 } ruler = RULER_NONE;
222 /* Scrolling is done in pages or line increments */
223 int mouse_move_pages_viewer = 1;
225 /* wrap mode default */
226 int global_wrap_mode = 1;
228 int default_hex_mode = 0;
229 int default_magic_flag = 1;
230 int default_nroff_flag = 1;
231 int altered_hex_mode = 0;
232 int altered_magic_flag = 0;
233 int altered_nroff_flag = 0;
235 static const char hex_char[] = "0123456789ABCDEF";
237 int mcview_remember_file_position = FALSE;
239 /* {{{ Function Prototypes }}} */
241 /* Our widget callback */
242 static cb_ret_t view_callback (Widget *, widget_msg_t, int);
244 static int regexp_view_search (WView * view, char *pattern, char *string,
245 int match_type);
246 static void view_labels (WView * view);
248 static void view_init_growbuf (WView *);
249 static void view_place_cursor (WView *view);
250 static void display (WView *);
251 static void view_done (WView *);
253 /* {{{ Helper Functions }}} */
255 /* difference or zero */
256 static inline screen_dimen
257 dimen_doz (screen_dimen a, screen_dimen b)
259 return (a >= b) ? a - b : 0;
262 static inline screen_dimen
263 dimen_min (screen_dimen a, screen_dimen b)
265 return (a < b) ? a : b;
268 static inline offset_type
269 offset_doz (offset_type a, offset_type b)
271 return (a >= b) ? a - b : 0;
274 static inline offset_type
275 offset_rounddown (offset_type a, offset_type b)
277 assert (b != 0);
278 return a - a % b;
281 /* {{{ Simple Primitive Functions for WView }}} */
283 static inline bool
284 view_is_in_panel (WView *view)
286 return (view->dpy_frame_size != 0);
289 static void
290 view_compute_areas (WView *view)
292 struct area view_area;
293 screen_dimen height, rest, y;
295 /* The viewer is surrounded by a frame of size view->dpy_frame_size.
296 * Inside that frame, there are: The status line (at the top),
297 * the data area and an optional ruler, which is shown above or
298 * below the data area. */
300 view_area.top = view->dpy_frame_size;
301 view_area.left = view->dpy_frame_size;
302 view_area.height = dimen_doz(view->widget.lines, 2 * view->dpy_frame_size);
303 view_area.width = dimen_doz(view->widget.cols, 2 * view->dpy_frame_size);
305 /* Most coordinates of the areas equal those of the whole viewer */
306 view->status_area = view_area;
307 view->ruler_area = view_area;
308 view->data_area = view_area;
310 /* Compute the heights of the areas */
311 rest = view_area.height;
313 height = dimen_min(rest, 1);
314 view->status_area.height = height;
315 rest -= height;
317 height = dimen_min(rest, (ruler == RULER_NONE || view->hex_mode) ? 0 : 2);
318 view->ruler_area.height = height;
319 rest -= height;
321 view->data_area.height = rest;
323 /* Compute the position of the areas */
324 y = view_area.top;
326 view->status_area.top = y;
327 y += view->status_area.height;
329 if (ruler == RULER_TOP) {
330 view->ruler_area.top = y;
331 y += view->ruler_area.height;
334 view->data_area.top = y;
335 y += view->data_area.height;
337 if (ruler == RULER_BOTTOM) {
338 view->ruler_area.top = y;
339 y += view->ruler_area.height;
343 static void
344 view_hexedit_free_change_list (WView *view)
346 struct hexedit_change_node *curr, *next;
348 for (curr = view->change_list; curr != NULL; curr = next) {
349 next = curr->next;
350 g_free (curr);
352 view->change_list = NULL;
353 view->dirty++;
356 /* {{{ Growing buffer }}} */
358 static void
359 view_init_growbuf (WView *view)
361 view->growbuf_in_use = TRUE;
362 view->growbuf_blockptr = NULL;
363 view->growbuf_blocks = 0;
364 view->growbuf_lastindex = VIEW_PAGE_SIZE;
365 view->growbuf_finished = FALSE;
368 static void
369 view_growbuf_free (WView *view)
371 size_t i;
373 assert (view->growbuf_in_use);
375 for (i = 0; i < view->growbuf_blocks; i++)
376 g_free (view->growbuf_blockptr[i]);
377 g_free (view->growbuf_blockptr);
378 view->growbuf_blockptr = NULL;
379 view->growbuf_in_use = FALSE;
382 static offset_type
383 view_growbuf_filesize (WView *view)
385 assert(view->growbuf_in_use);
387 if (view->growbuf_blocks == 0)
388 return 0;
389 else
390 return ((offset_type) view->growbuf_blocks - 1) * VIEW_PAGE_SIZE
391 + view->growbuf_lastindex;
394 /* Copies the output from the pipe to the growing buffer, until either
395 * the end-of-pipe is reached or the interval [0..ofs) of the growing
396 * buffer is completely filled. */
397 static void
398 view_growbuf_read_until (WView *view, offset_type ofs)
400 ssize_t nread;
401 byte *p;
402 size_t bytesfree;
403 bool short_read;
405 assert (view->growbuf_in_use);
407 if (view->growbuf_finished)
408 return;
410 short_read = FALSE;
411 while (view_growbuf_filesize (view) < ofs || short_read) {
412 if (view->growbuf_lastindex == VIEW_PAGE_SIZE) {
413 /* Append a new block to the growing buffer */
414 byte *newblock = g_try_malloc (VIEW_PAGE_SIZE);
415 byte **newblocks = g_try_malloc (sizeof (*newblocks) * (view->growbuf_blocks + 1));
416 if (!newblock || !newblocks) {
417 g_free (newblock);
418 g_free (newblocks);
419 return;
421 memcpy (newblocks, view->growbuf_blockptr, sizeof (*newblocks) * view->growbuf_blocks);
422 g_free (view->growbuf_blockptr);
423 view->growbuf_blockptr = newblocks;
424 view->growbuf_blockptr[view->growbuf_blocks++] = newblock;
425 view->growbuf_lastindex = 0;
427 p = view->growbuf_blockptr[view->growbuf_blocks - 1] + view->growbuf_lastindex;
428 bytesfree = VIEW_PAGE_SIZE - view->growbuf_lastindex;
430 if (view->datasource == DS_STDIO_PIPE) {
431 nread = fread (p, 1, bytesfree, view->ds_stdio_pipe);
432 if (nread == 0) {
433 view->growbuf_finished = TRUE;
434 (void) pclose (view->ds_stdio_pipe);
435 display (view);
436 close_error_pipe (D_NORMAL, NULL);
437 view->ds_stdio_pipe = NULL;
438 return;
440 } else {
441 assert (view->datasource == DS_VFS_PIPE);
442 do {
443 nread = mc_read (view->ds_vfs_pipe, p, bytesfree);
444 } while (nread == -1 && errno == EINTR);
445 if (nread == -1 || nread == 0) {
446 view->growbuf_finished = TRUE;
447 (void) mc_close (view->ds_vfs_pipe);
448 view->ds_vfs_pipe = -1;
449 return;
452 short_read = ((size_t)nread < bytesfree);
453 view->growbuf_lastindex += nread;
457 static int
458 get_byte_growing_buffer (WView *view, offset_type byte_index)
460 offset_type pageno = byte_index / VIEW_PAGE_SIZE;
461 offset_type pageindex = byte_index % VIEW_PAGE_SIZE;
463 assert (view->growbuf_in_use);
465 if ((size_t) pageno != pageno)
466 return -1;
468 view_growbuf_read_until (view, byte_index + 1);
469 if (view->growbuf_blocks == 0)
470 return -1;
471 if (pageno < view->growbuf_blocks - 1)
472 return view->growbuf_blockptr[pageno][pageindex];
473 if (pageno == view->growbuf_blocks - 1 && pageindex < view->growbuf_lastindex)
474 return view->growbuf_blockptr[pageno][pageindex];
475 return -1;
478 /* {{{ Data sources }}} */
481 The data source provides the viewer with data from either a file, a
482 string or the output of a command. The get_byte() function can be
483 used to get the value of a byte at a specific offset. If the offset
484 is out of range, -1 is returned. The function get_byte_indexed(a,b)
485 returns the byte at the offset a+b, or -1 if a+b is out of range.
487 The view_set_byte() function has the effect that later calls to
488 get_byte() will return the specified byte for this offset. This
489 function is designed only for use by the hexedit component after
490 saving its changes. Inspect the source before you want to use it for
491 other purposes.
493 The view_get_filesize() function returns the current size of the
494 data source. If the growing buffer is used, this size may increase
495 later on. Use the view_may_still_grow() function when you want to
496 know if the size can change later.
499 static offset_type
500 view_get_filesize (WView *view)
502 switch (view->datasource) {
503 case DS_NONE:
504 return 0;
505 case DS_STDIO_PIPE:
506 case DS_VFS_PIPE:
507 return view_growbuf_filesize (view);
508 case DS_FILE:
509 return view->ds_file_filesize;
510 case DS_STRING:
511 return view->ds_string_len;
512 default:
513 assert(!"Unknown datasource type");
514 return 0;
518 static inline bool
519 view_may_still_grow (WView *view)
521 return (view->growbuf_in_use && !view->growbuf_finished);
524 /* returns TRUE if the idx lies in the half-open interval
525 * [offset; offset + size), FALSE otherwise.
527 static inline bool
528 already_loaded (offset_type offset, offset_type idx, size_t size)
530 return (offset <= idx && idx - offset < size);
533 static inline void
534 view_file_load_data (WView *view, offset_type byte_index)
536 offset_type blockoffset;
537 ssize_t res;
538 size_t bytes_read;
540 assert (view->datasource == DS_FILE);
542 if (already_loaded (view->ds_file_offset, byte_index, view->ds_file_datalen))
543 return;
545 if (byte_index >= view->ds_file_filesize)
546 return;
548 blockoffset = offset_rounddown (byte_index, view->ds_file_datasize);
549 if (mc_lseek (view->ds_file_fd, blockoffset, SEEK_SET) == -1)
550 goto error;
552 bytes_read = 0;
553 while (bytes_read < view->ds_file_datasize) {
554 res = mc_read (view->ds_file_fd, view->ds_file_data + bytes_read, view->ds_file_datasize - bytes_read);
555 if (res == -1)
556 goto error;
557 if (res == 0)
558 break;
559 bytes_read += (size_t) res;
561 view->ds_file_offset = blockoffset;
562 if (bytes_read > view->ds_file_filesize - view->ds_file_offset) {
563 /* the file has grown in the meantime -- stick to the old size */
564 view->ds_file_datalen = view->ds_file_filesize - view->ds_file_offset;
565 } else {
566 view->ds_file_datalen = bytes_read;
568 return;
570 error:
571 view->ds_file_datalen = 0;
574 static int
575 get_byte_none (WView *view, offset_type byte_index)
577 assert (view->datasource == DS_NONE);
578 (void) &view;
579 (void) byte_index;
580 return -1;
583 static inline int
584 get_byte_file (WView *view, offset_type byte_index)
586 assert (view->datasource == DS_FILE);
588 view_file_load_data (view, byte_index);
589 if (already_loaded(view->ds_file_offset, byte_index, view->ds_file_datalen))
590 return view->ds_file_data[byte_index - view->ds_file_offset];
591 return -1;
594 static int
595 get_byte_string (WView *view, offset_type byte_index)
597 assert (view->datasource == DS_STRING);
598 if (byte_index < view->ds_string_len)
599 return view->ds_string_data[byte_index];
600 return -1;
603 static inline int
604 get_byte (WView *view, offset_type offset)
606 switch (view->datasource) {
607 case DS_STDIO_PIPE:
608 case DS_VFS_PIPE:
609 return get_byte_growing_buffer (view, offset);
610 case DS_FILE:
611 return get_byte_file (view, offset);
612 case DS_STRING:
613 return get_byte_string (view, offset);
614 case DS_NONE:
615 return get_byte_none (view, offset);
617 assert(!"Unknown datasource type");
618 return -1;
621 static inline int
622 get_byte_indexed (WView *view, offset_type base, offset_type ofs)
624 if (base <= OFFSETTYPE_MAX - ofs)
625 return get_byte (view, base + ofs);
626 return -1;
629 static void
630 view_set_byte (WView *view, offset_type offset, byte b)
632 (void) &b;
633 assert (offset < view_get_filesize (view));
634 assert (view->datasource == DS_FILE);
635 view->ds_file_datalen = 0; /* just force reloading */
638 static void
639 view_set_datasource_none (WView *view)
641 view->datasource = DS_NONE;
644 static void
645 view_set_datasource_vfs_pipe (WView *view, int fd)
647 assert (fd != -1);
648 view->datasource = DS_VFS_PIPE;
649 view->ds_vfs_pipe = fd;
651 view_init_growbuf (view);
654 static void
655 view_set_datasource_stdio_pipe (WView *view, FILE *fp)
657 assert (fp != NULL);
658 view->datasource = DS_STDIO_PIPE;
659 view->ds_stdio_pipe = fp;
661 view_init_growbuf (view);
664 static void
665 view_set_datasource_string (WView *view, const char *s)
667 view->datasource = DS_STRING;
668 view->ds_string_data = (byte *) g_strdup (s);
669 view->ds_string_len = strlen (s);
672 static void
673 view_set_datasource_file (WView *view, int fd, const struct stat *st)
675 view->datasource = DS_FILE;
676 view->ds_file_fd = fd;
677 view->ds_file_filesize = st->st_size;
678 view->ds_file_offset = 0;
679 view->ds_file_data = g_malloc (4096);
680 view->ds_file_datalen = 0;
681 view->ds_file_datasize = 4096;
684 static void
685 view_close_datasource (WView *view)
687 switch (view->datasource) {
688 case DS_NONE:
689 break;
690 case DS_STDIO_PIPE:
691 if (view->ds_stdio_pipe != NULL) {
692 (void) pclose (view->ds_stdio_pipe);
693 display (view);
694 close_error_pipe (D_NORMAL, NULL);
695 view->ds_stdio_pipe = NULL;
697 view_growbuf_free (view);
698 break;
699 case DS_VFS_PIPE:
700 if (view->ds_vfs_pipe != -1) {
701 (void) mc_close (view->ds_vfs_pipe);
702 view->ds_vfs_pipe = -1;
704 view_growbuf_free (view);
705 break;
706 case DS_FILE:
707 (void) mc_close (view->ds_file_fd);
708 view->ds_file_fd = -1;
709 g_free (view->ds_file_data);
710 view->ds_file_data = NULL;
711 break;
712 case DS_STRING:
713 g_free (view->ds_string_data);
714 view->ds_string_data = NULL;
715 break;
716 default:
717 assert (!"Unknown datasource type");
719 view->datasource = DS_NONE;
722 /* {{{ The Coordinate Cache }}} */
725 This cache provides you with a fast lookup to map file offsets into
726 line/column pairs and vice versa. The interface to the mapping is
727 provided by the functions view_coord_to_offset() and
728 view_offset_to_coord().
730 The cache is implemented as a simple sorted array holding entries
731 that map some of the offsets to their line/column pair. Entries that
732 are not cached themselves are interpolated (exactly) from their
733 neighbor entries. The algorithm used for determining the line/column
734 for a specific offset needs to be kept synchronized with the one used
735 in display().
738 enum ccache_type {
739 CCACHE_OFFSET,
740 CCACHE_LINECOL
743 static inline bool
744 coord_cache_entry_less (const struct coord_cache_entry *a,
745 const struct coord_cache_entry *b, enum ccache_type crit,
746 bool nroff_mode)
748 if (crit == CCACHE_OFFSET)
749 return (a->cc_offset < b->cc_offset);
751 if (a->cc_line < b->cc_line)
752 return TRUE;
754 if (a->cc_line == b->cc_line) {
755 if (nroff_mode) {
756 return (a->cc_nroff_column < b->cc_nroff_column);
757 } else {
758 return (a->cc_column < b->cc_column);
761 return FALSE;
764 #ifdef MC_ENABLE_DEBUGGING_CODE
765 static void view_coord_to_offset (WView *, offset_type *, offset_type, offset_type);
766 static void view_offset_to_coord (WView *, offset_type *, offset_type *, offset_type);
768 static void
769 view_ccache_dump (WView *view)
771 FILE *f;
772 offset_type offset, line, column, nextline_offset, filesize;
773 guint i;
774 const struct coord_cache_entry *cache;
776 assert (view->coord_cache != NULL);
778 filesize = view_get_filesize (view);
779 cache = &(g_array_index (view->coord_cache, struct coord_cache_entry, 0));
781 f = fopen("mcview-ccache.out", "w");
782 if (f == NULL)
783 return;
784 (void)setvbuf(f, NULL, _IONBF, 0);
786 /* cache entries */
787 for (i = 0; i < view->coord_cache->len; i++) {
788 (void) fprintf (f,
789 "entry %8u "
790 "offset %8"OFFSETTYPE_PRId" "
791 "line %8"OFFSETTYPE_PRId" "
792 "column %8"OFFSETTYPE_PRId" "
793 "nroff_column %8"OFFSETTYPE_PRId"\n",
794 (unsigned int) i, cache[i].cc_offset, cache[i].cc_line,
795 cache[i].cc_column, cache[i].cc_nroff_column);
797 (void)fprintf (f, "\n");
799 /* offset -> line/column translation */
800 for (offset = 0; offset < filesize; offset++) {
801 view_offset_to_coord (view, &line, &column, offset);
802 (void)fprintf (f,
803 "offset %8"OFFSETTYPE_PRId" "
804 "line %8"OFFSETTYPE_PRId" "
805 "column %8"OFFSETTYPE_PRId"\n",
806 offset, line, column);
809 /* line/column -> offset translation */
810 for (line = 0; TRUE; line++) {
811 view_coord_to_offset (view, &nextline_offset, line + 1, 0);
812 (void)fprintf (f, "nextline_offset %8"OFFSETTYPE_PRId"\n",
813 nextline_offset);
815 for (column = 0; TRUE; column++) {
816 view_coord_to_offset (view, &offset, line, column);
817 if (offset >= nextline_offset)
818 break;
820 (void)fprintf (f, "line %8"OFFSETTYPE_PRId" column %8"OFFSETTYPE_PRId" offset %8"OFFSETTYPE_PRId"\n",
821 line, column, offset);
824 if (nextline_offset >= filesize - 1)
825 break;
828 (void)fclose (f);
830 #endif
832 static inline bool
833 is_nroff_sequence (WView *view, offset_type offset)
835 int c0, c1, c2;
837 /* The following commands are ordered to speed up the calculation. */
839 c1 = get_byte_indexed (view, offset, 1);
840 if (c1 == -1 || c1 != '\b')
841 return FALSE;
843 c0 = get_byte_indexed (view, offset, 0);
844 if (c0 == -1 || !is_printable(c0))
845 return FALSE;
847 c2 = get_byte_indexed (view, offset, 2);
848 if (c2 == -1 || !is_printable(c2))
849 return FALSE;
851 return (c0 == c2 || c0 == '_' || (c0 == '+' && c2 == 'o'));
854 /* Find and return the index of the last cache entry that is
855 * smaller than ''coord'', according to the criterion ''sort_by''. */
856 static inline guint
857 view_ccache_find (WView *view, const struct coord_cache_entry *cache,
858 const struct coord_cache_entry *coord, enum ccache_type sort_by)
860 guint base, i, limit;
862 limit = view->coord_cache->len;
863 assert (limit != 0);
865 base = 0;
866 while (limit > 1) {
867 i = base + limit / 2;
868 if (coord_cache_entry_less (coord, &cache[i], sort_by, view->text_nroff_mode)) {
869 /* continue the search in the lower half of the cache */
870 } else {
871 /* continue the search in the upper half of the cache */
872 base = i;
874 limit = (limit + 1) / 2;
876 return base;
879 /* Look up the missing components of ''coord'', which are given by
880 * ''lookup_what''. The function returns the smallest value that
881 * matches the existing components of ''coord''.
883 static void
884 view_ccache_lookup (WView *view, struct coord_cache_entry *coord,
885 enum ccache_type lookup_what)
887 guint i;
888 struct coord_cache_entry *cache, current, next, entry;
889 enum ccache_type sorter;
890 offset_type limit;
891 enum {
892 NROFF_START,
893 NROFF_BACKSPACE,
894 NROFF_CONTINUATION
895 } nroff_state;
897 if (!view->coord_cache) {
898 view->coord_cache = g_array_new (FALSE, FALSE, sizeof(struct coord_cache_entry));
899 current.cc_offset = 0;
900 current.cc_line = 0;
901 current.cc_column = 0;
902 current.cc_nroff_column = 0;
903 g_array_append_val (view->coord_cache, current);
906 sorter = (lookup_what == CCACHE_OFFSET) ? CCACHE_LINECOL : CCACHE_OFFSET;
908 retry:
909 /* find the two neighbor entries in the cache */
910 cache = &(g_array_index (view->coord_cache, struct coord_cache_entry, 0));
911 i = view_ccache_find (view, cache, coord, sorter);
912 /* now i points to the lower neighbor in the cache */
914 current = cache[i];
915 if (i + 1 < view->coord_cache->len)
916 limit = cache[i + 1].cc_offset;
917 else
918 limit = current.cc_offset + VIEW_COORD_CACHE_GRANUL;
920 entry = current;
921 nroff_state = NROFF_START;
922 for (; current.cc_offset < limit; current = next) {
923 int c, nextc;
925 if ((c = get_byte (view, current.cc_offset)) == -1)
926 break;
928 if (!coord_cache_entry_less (&current, coord, sorter, view->text_nroff_mode)) {
929 if (lookup_what == CCACHE_OFFSET
930 && view->text_nroff_mode
931 && nroff_state != NROFF_START) {
932 /* don't break here */
933 } else {
934 break;
938 /* Provide useful default values for ''next'' */
939 next.cc_offset = current.cc_offset + 1;
940 next.cc_line = current.cc_line;
941 next.cc_column = current.cc_column + 1;
942 next.cc_nroff_column = current.cc_nroff_column + 1;
944 /* and override some of them as necessary. */
945 if (c == '\r') {
946 nextc = get_byte_indexed(view, current.cc_offset, 1);
948 /* Ignore '\r' if it is followed by '\r' or '\n'. If it is
949 * followed by anything else, it is a Mac line ending and
950 * produces a line break.
952 if (nextc == '\r' || nextc == '\n') {
953 next.cc_column = current.cc_column;
954 next.cc_nroff_column = current.cc_nroff_column;
955 } else {
956 next.cc_line = current.cc_line + 1;
957 next.cc_column = 0;
958 next.cc_nroff_column = 0;
961 } else if (nroff_state == NROFF_BACKSPACE) {
962 next.cc_nroff_column = current.cc_nroff_column - 1;
964 } else if (c == '\t') {
965 next.cc_column = offset_rounddown (current.cc_column, 8) + 8;
966 next.cc_nroff_column =
967 offset_rounddown (current.cc_nroff_column, 8) + 8;
969 } else if (c == '\n') {
970 next.cc_line = current.cc_line + 1;
971 next.cc_column = 0;
972 next.cc_nroff_column = 0;
974 } else {
975 /* Use all default values from above */
978 switch (nroff_state) {
979 case NROFF_START:
980 case NROFF_CONTINUATION:
981 if (is_nroff_sequence (view, current.cc_offset))
982 nroff_state = NROFF_BACKSPACE;
983 else
984 nroff_state = NROFF_START;
985 break;
986 case NROFF_BACKSPACE:
987 nroff_state = NROFF_CONTINUATION;
988 break;
991 /* Cache entries must guarantee that for each i < j,
992 * line[i] <= line[j] and column[i] < column[j]. In the case of
993 * nroff sequences and '\r' characters, this is not guaranteed,
994 * so we cannot save them. */
995 if (nroff_state == NROFF_START && c != '\r')
996 entry = next;
999 if (i + 1 == view->coord_cache->len && entry.cc_offset != cache[i].cc_offset) {
1000 g_array_append_val (view->coord_cache, entry);
1001 goto retry;
1004 if (lookup_what == CCACHE_OFFSET) {
1005 coord->cc_offset = current.cc_offset;
1006 } else {
1007 coord->cc_line = current.cc_line;
1008 coord->cc_column = current.cc_column;
1009 coord->cc_nroff_column = current.cc_nroff_column;
1013 static void
1014 view_coord_to_offset (WView *view, offset_type *ret_offset,
1015 offset_type line, offset_type column)
1017 struct coord_cache_entry coord;
1019 coord.cc_line = line;
1020 coord.cc_column = column;
1021 coord.cc_nroff_column = column;
1022 view_ccache_lookup (view, &coord, CCACHE_OFFSET);
1023 *ret_offset = coord.cc_offset;
1026 static void
1027 view_offset_to_coord (WView *view, offset_type *ret_line,
1028 offset_type *ret_column, offset_type offset)
1030 struct coord_cache_entry coord;
1032 coord.cc_offset = offset;
1033 view_ccache_lookup (view, &coord, CCACHE_LINECOL);
1034 *ret_line = coord.cc_line;
1035 *ret_column = (view->text_nroff_mode)
1036 ? coord.cc_nroff_column
1037 : coord.cc_column;
1040 /* {{{ Cursor Movement }}} */
1043 The following variables have to do with the current position and are
1044 updated by the cursor movement functions.
1046 In hex view and wrapped text view mode, dpy_start marks the offset of
1047 the top-left corner on the screen, in non-wrapping text mode it is
1048 the beginning of the current line. In hex mode, hex_cursor is the
1049 offset of the cursor. In non-wrapping text mode, dpy_text_column is
1050 the number of columns that are hidden on the left side on the screen.
1052 In hex mode, dpy_start is updated by the view_fix_cursor_position()
1053 function in order to keep the other functions simple. In
1054 non-wrapping text mode dpy_start and dpy_text_column are normalized
1055 such that dpy_text_column < view_get_datacolumns().
1058 /* prototypes for functions used by view_moveto_bottom() */
1059 static void view_move_up (WView *, offset_type);
1060 static void view_moveto_bol (WView *);
1062 static void
1063 view_scroll_to_cursor (WView *view)
1065 if (view->hex_mode) {
1066 const offset_type bytes = view->bytes_per_line;
1067 const offset_type displaysize = view->data_area.height * bytes;
1068 const offset_type cursor = view->hex_cursor;
1069 offset_type topleft = view->dpy_start;
1071 if (topleft + displaysize <= cursor)
1072 topleft = offset_rounddown (cursor, bytes)
1073 - (displaysize - bytes);
1074 if (cursor < topleft)
1075 topleft = offset_rounddown (cursor, bytes);
1076 view->dpy_start = topleft;
1077 } else if (view->text_wrap_mode) {
1078 offset_type line, col, columns;
1080 columns = view->data_area.width;
1081 view_offset_to_coord (view, &line, &col, view->dpy_start + view->dpy_text_column);
1082 if (columns != 0)
1083 col = offset_rounddown (col, columns);
1084 view_coord_to_offset (view, &(view->dpy_start), line, col);
1085 view->dpy_text_column = 0;
1086 } else {
1087 /* nothing to do */
1091 static void
1092 view_movement_fixups (WView *view, bool reset_search)
1094 view_scroll_to_cursor (view);
1095 if (reset_search) {
1096 view->search_start = view->dpy_start;
1097 view->search_length = 0;
1099 view->dirty++;
1102 static void
1103 view_moveto_top (WView *view)
1105 view->dpy_start = 0;
1106 view->hex_cursor = 0;
1107 view->dpy_text_column = 0;
1108 view_movement_fixups (view, TRUE);
1111 static void
1112 view_moveto_bottom (WView *view)
1114 offset_type datalines, lines_up, filesize, last_offset;
1116 if (view->growbuf_in_use)
1117 view_growbuf_read_until (view, OFFSETTYPE_MAX);
1119 filesize = view_get_filesize (view);
1120 last_offset = offset_doz(filesize, 1);
1121 datalines = view->data_area.height;
1122 lines_up = offset_doz(datalines, 1);
1124 if (view->hex_mode) {
1125 view->hex_cursor = filesize;
1126 view_move_up (view, lines_up);
1127 view->hex_cursor = last_offset;
1128 } else {
1129 view->dpy_start = last_offset;
1130 view_moveto_bol (view);
1131 view_move_up (view, lines_up);
1133 view_movement_fixups (view, TRUE);
1136 static void
1137 view_moveto_bol (WView *view)
1139 if (view->hex_mode) {
1140 view->hex_cursor -= view->hex_cursor % view->bytes_per_line;
1141 } else if (view->text_wrap_mode) {
1142 /* do nothing */
1143 } else {
1144 offset_type line, column;
1145 view_offset_to_coord (view, &line, &column, view->dpy_start);
1146 view_coord_to_offset (view, &(view->dpy_start), line, 0);
1147 view->dpy_text_column = 0;
1149 view_movement_fixups (view, TRUE);
1152 static void
1153 view_moveto_eol (WView *view)
1155 if (view->hex_mode) {
1156 offset_type filesize, bol;
1158 bol = offset_rounddown (view->hex_cursor, view->bytes_per_line);
1159 if (get_byte_indexed (view, bol, view->bytes_per_line - 1) != -1) {
1160 view->hex_cursor = bol + view->bytes_per_line - 1;
1161 } else {
1162 filesize = view_get_filesize (view);
1163 view->hex_cursor = offset_doz(filesize, 1);
1165 } else if (view->text_wrap_mode) {
1166 /* nothing to do */
1167 } else {
1168 offset_type line, col;
1170 view_offset_to_coord (view, &line, &col, view->dpy_start);
1171 view_coord_to_offset (view, &(view->dpy_start), line, OFFSETTYPE_MAX);
1173 view_movement_fixups (view, FALSE);
1176 static void
1177 view_moveto_offset (WView *view, offset_type offset)
1179 if (view->hex_mode) {
1180 view->hex_cursor = offset;
1181 view->dpy_start = offset - offset % view->bytes_per_line;
1182 } else {
1183 view->dpy_start = offset;
1185 view_movement_fixups (view, TRUE);
1188 static void
1189 view_moveto (WView *view, offset_type line, offset_type col)
1191 offset_type offset;
1193 view_coord_to_offset (view, &offset, line, col);
1194 view_moveto_offset (view, offset);
1197 static void
1198 view_move_up (WView *view, offset_type lines)
1200 if (view->hex_mode) {
1201 offset_type bytes = lines * view->bytes_per_line;
1202 if (view->hex_cursor >= bytes) {
1203 view->hex_cursor -= bytes;
1204 if (view->hex_cursor < view->dpy_start)
1205 view->dpy_start = offset_doz (view->dpy_start, bytes);
1206 } else {
1207 view->hex_cursor %= view->bytes_per_line;
1209 } else if (view->text_wrap_mode) {
1210 const screen_dimen width = view->data_area.width;
1211 offset_type i, col, line, linestart;
1213 for (i = 0; i < lines; i++) {
1214 view_offset_to_coord (view, &line, &col, view->dpy_start);
1215 if (col >= width) {
1216 col -= width;
1217 } else if (line >= 1) {
1218 view_coord_to_offset (view, &linestart, line, 0);
1219 view_offset_to_coord (view, &line, &col, linestart - 1);
1221 /* if the only thing that would be displayed were a
1222 * single newline character, advance to the previous
1223 * part of the line. */
1224 if (col > 0 && col % width == 0)
1225 col -= width;
1226 else
1227 col -= col % width;
1228 } else {
1229 /* nothing to do */
1231 view_coord_to_offset (view, &(view->dpy_start), line, col);
1233 } else {
1234 offset_type line, column;
1236 view_offset_to_coord (view, &line, &column, view->dpy_start);
1237 line = offset_doz(line, lines);
1238 view_coord_to_offset (view, &(view->dpy_start), line, column);
1240 view_movement_fixups (view, (lines != 1));
1243 static void
1244 view_move_down (WView *view, offset_type lines)
1246 if (view->hex_mode) {
1247 offset_type i, limit, last_byte;
1249 last_byte = view_get_filesize (view);
1250 if (last_byte >= (offset_type) view->bytes_per_line)
1251 limit = last_byte - view->bytes_per_line;
1252 else
1253 limit = 0;
1254 for (i = 0; i < lines && view->hex_cursor < limit; i++) {
1255 view->hex_cursor += view->bytes_per_line;
1256 if (lines != 1)
1257 view->dpy_start += view->bytes_per_line;
1260 } else if (view->dpy_end == view_get_filesize (view)) {
1261 /* don't move further down. There's nothing more to see. */
1263 } else if (view->text_wrap_mode) {
1264 offset_type line, col, i;
1266 for (i = 0; i < lines; i++) {
1267 offset_type new_offset, chk_line, chk_col;
1269 view_offset_to_coord (view, &line, &col, view->dpy_start);
1270 col += view->data_area.width;
1271 view_coord_to_offset (view, &new_offset, line, col);
1273 /* skip to the next line if the only thing that would be
1274 * displayed is the newline character. */
1275 view_offset_to_coord (view, &chk_line, &chk_col, new_offset);
1276 if (chk_line == line && chk_col == col
1277 && get_byte (view, new_offset) == '\n')
1278 new_offset++;
1280 view->dpy_start = new_offset;
1283 } else {
1284 offset_type line, col;
1286 view_offset_to_coord (view, &line, &col, view->dpy_start);
1287 line += lines;
1288 view_coord_to_offset (view, &(view->dpy_start), line, col);
1290 view_movement_fixups (view, (lines != 1));
1293 static void
1294 view_move_left (WView *view, offset_type columns)
1296 if (view->hex_mode) {
1297 assert (columns == 1);
1298 if (view->hexview_in_text || !view->hexedit_lownibble) {
1299 if (view->hex_cursor > 0)
1300 view->hex_cursor--;
1302 if (!view->hexview_in_text)
1303 view->hexedit_lownibble = !view->hexedit_lownibble;
1304 } else if (view->text_wrap_mode) {
1305 /* nothing to do */
1306 } else {
1307 if (view->dpy_text_column >= columns)
1308 view->dpy_text_column -= columns;
1309 else
1310 view->dpy_text_column = 0;
1312 view_movement_fixups (view, FALSE);
1315 static void
1316 view_move_right (WView *view, offset_type columns)
1318 if (view->hex_mode) {
1319 assert (columns == 1);
1320 if (view->hexview_in_text || view->hexedit_lownibble) {
1321 if (get_byte_indexed (view, view->hex_cursor, 1) != -1)
1322 view->hex_cursor++;
1324 if (!view->hexview_in_text)
1325 view->hexedit_lownibble = !view->hexedit_lownibble;
1326 } else if (view->text_wrap_mode) {
1327 /* nothing to do */
1328 } else {
1329 view->dpy_text_column += columns;
1331 view_movement_fixups (view, FALSE);
1334 /* {{{ Toggling of viewer modes }}} */
1336 static void
1337 view_toggle_hex_mode (WView *view)
1339 view->hex_mode = !view->hex_mode;
1341 if (view->hex_mode) {
1342 view->hex_cursor = view->dpy_start;
1343 view->dpy_start =
1344 offset_rounddown (view->dpy_start, view->bytes_per_line);
1345 view->widget.options |= W_WANT_CURSOR;
1346 } else {
1347 view->dpy_start = view->hex_cursor;
1348 view_moveto_bol (view);
1349 view->widget.options &= ~W_WANT_CURSOR;
1351 altered_hex_mode = 1;
1352 view->dpy_bbar_dirty = TRUE;
1353 view->dirty++;
1356 static void
1357 view_toggle_hexedit_mode (WView *view)
1359 view->hexedit_mode = !view->hexedit_mode;
1360 view->dpy_bbar_dirty = TRUE;
1361 view->dirty++;
1364 static void
1365 view_toggle_wrap_mode (WView *view)
1367 view->text_wrap_mode = !view->text_wrap_mode;
1368 if (view->text_wrap_mode) {
1369 view_scroll_to_cursor (view);
1370 } else {
1371 offset_type line;
1373 view_offset_to_coord (view, &line, &(view->dpy_text_column), view->dpy_start);
1374 view_coord_to_offset (view, &(view->dpy_start), line, 0);
1376 view->dpy_bbar_dirty = TRUE;
1377 view->dirty++;
1380 static void
1381 view_toggle_nroff_mode (WView *view)
1383 view->text_nroff_mode = !view->text_nroff_mode;
1384 altered_nroff_flag = 1;
1385 view->dpy_bbar_dirty = TRUE;
1386 view->dirty++;
1389 static void
1390 view_toggle_magic_mode (WView *view)
1392 char *filename, *command;
1394 altered_magic_flag = 1;
1395 view->magic_mode = !view->magic_mode;
1396 filename = g_strdup (view->filename);
1397 command = g_strdup (view->command);
1399 view_done (view);
1400 view_load (view, command, filename, 0);
1401 g_free (filename);
1402 g_free (command);
1403 view->dpy_bbar_dirty = TRUE;
1404 view->dirty++;
1407 /* {{{ Miscellaneous functions }}} */
1409 static void
1410 view_done (WView *view)
1412 /* Save current file position */
1413 if (mcview_remember_file_position && view->filename != NULL) {
1414 char *canon_fname;
1415 offset_type line, col;
1417 canon_fname = vfs_canon (view->filename);
1418 view_offset_to_coord (view, &line, &col, view->dpy_start);
1419 save_file_position (canon_fname, line + 1, col);
1420 g_free (canon_fname);
1423 /* Write back the global viewer mode */
1424 default_hex_mode = view->hex_mode;
1425 default_nroff_flag = view->text_nroff_mode;
1426 default_magic_flag = view->magic_mode;
1427 global_wrap_mode = view->text_wrap_mode;
1429 /* Free memory used by the viewer */
1431 /* view->widget needs no destructor */
1433 g_free (view->filename), view->filename = NULL;
1434 g_free (view->command), view->command = NULL;
1436 view_close_datasource (view);
1437 /* the growing buffer is freed with the datasource */
1439 if (view->coord_cache) {
1440 g_array_free (view->coord_cache, TRUE), view->coord_cache = NULL;
1443 view_hexedit_free_change_list (view);
1444 /* FIXME: what about view->search_exp? */
1447 static void
1448 view_show_error (WView *view, const char *msg)
1450 view_close_datasource (view);
1451 if (view_is_in_panel (view)) {
1452 view_set_datasource_string (view, msg);
1453 } else {
1454 message (D_ERROR, MSG_ERROR, "%s", msg);
1458 static bool
1459 view_load_command_output (WView *view, const char *command)
1461 FILE *fp;
1463 view_close_datasource (view);
1465 open_error_pipe ();
1466 if ((fp = popen (command, "r")) == NULL) {
1467 /* Avoid two messages. Message from stderr has priority. */
1468 display (view);
1469 if (!close_error_pipe (view_is_in_panel (view) ? -1 : D_ERROR, NULL))
1470 view_show_error (view, _(" Cannot spawn child process "));
1471 return FALSE;
1474 /* First, check if filter produced any output */
1475 view_set_datasource_stdio_pipe (view, fp);
1476 if (get_byte (view, 0) == -1) {
1477 view_close_datasource (view);
1479 /* Avoid two messages. Message from stderr has priority. */
1480 display (view);
1481 if (!close_error_pipe (view_is_in_panel (view) ? -1 : D_ERROR, NULL))
1482 view_show_error (view, _("Empty output from child filter"));
1483 return FALSE;
1485 return TRUE;
1488 bool
1489 view_load (WView *view, const char *command, const char *file,
1490 int start_line)
1492 int i, type;
1493 int fd = -1;
1494 char tmp[BUF_MEDIUM];
1495 struct stat st;
1496 bool retval = FALSE;
1498 assert (view->bytes_per_line != 0);
1499 view_done (view);
1501 /* Set up the state */
1502 view_set_datasource_none (view);
1503 view->filename = g_strdup (file);
1504 view->command = 0;
1506 /* Clear the markers */
1507 view->marker = 0;
1508 for (i = 0; i < 10; i++)
1509 view->marks[i] = 0;
1511 if (!view_is_in_panel (view)) {
1512 view->dpy_text_column = 0;
1515 if (command && (view->magic_mode || file == NULL || file[0] == '\0')) {
1516 retval = view_load_command_output (view, command);
1517 } else if (file != NULL && file[0] != '\0') {
1518 /* Open the file */
1519 if ((fd = mc_open (file, O_RDONLY | O_NONBLOCK)) == -1) {
1520 g_snprintf (tmp, sizeof (tmp), _(" Cannot open \"%s\"\n %s "),
1521 file, unix_error_string (errno));
1522 view_show_error (view, tmp);
1523 goto finish;
1526 /* Make sure we are working with a regular file */
1527 if (mc_fstat (fd, &st) == -1) {
1528 mc_close (fd);
1529 g_snprintf (tmp, sizeof (tmp), _(" Cannot stat \"%s\"\n %s "),
1530 file, unix_error_string (errno));
1531 view_show_error (view, tmp);
1532 goto finish;
1535 if (!S_ISREG (st.st_mode)) {
1536 mc_close (fd);
1537 view_show_error (view, _(" Cannot view: not a regular file "));
1538 goto finish;
1541 if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1) {
1542 /* Must be one of those nice files that grow (/proc) */
1543 view_set_datasource_vfs_pipe (view, fd);
1544 } else {
1545 type = get_compression_type (fd);
1547 if (view->magic_mode && (type != COMPRESSION_NONE)) {
1548 g_free (view->filename);
1549 view->filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
1551 view_set_datasource_file (view, fd, &st);
1553 retval = TRUE;
1556 finish:
1557 view->command = g_strdup (command);
1558 view->dpy_start = 0;
1559 view->search_start = 0;
1560 view->search_length = 0;
1561 view->dpy_text_column = 0;
1562 view->last_search = 0; /* Start a new search */
1564 assert (view->bytes_per_line != 0);
1565 if (mcview_remember_file_position && file != NULL && start_line == 0) {
1566 long line, col;
1567 char *canon_fname;
1569 canon_fname = vfs_canon (file);
1570 load_file_position (file, &line, &col);
1571 g_free (canon_fname);
1572 view_moveto (view, offset_doz(line, 1), col);
1573 } else if (start_line > 0) {
1574 view_moveto (view, start_line - 1, 0);
1577 view->hexedit_lownibble = FALSE;
1578 view->hexview_in_text = FALSE;
1579 view->change_list = NULL;
1581 return retval;
1584 /* {{{ Display management }}} */
1586 static void
1587 view_update_bytes_per_line (WView *view)
1589 const screen_dimen cols = view->data_area.width;
1590 int bytes;
1592 if (cols < 8 + 17)
1593 bytes = 4;
1594 else
1595 bytes = 4 * ((cols - 8) / ((cols < 80) ? 17 : 18));
1596 assert(bytes != 0);
1598 view->bytes_per_line = bytes;
1599 view->dirty = max_dirt_limit + 1; /* To force refresh */
1602 static void
1603 view_percent (WView *view, offset_type p)
1605 const screen_dimen top = view->status_area.top;
1606 const screen_dimen right = view->status_area.left + view->status_area.width;
1607 const screen_dimen height = view->status_area.height;
1608 int percent;
1609 offset_type filesize;
1611 if (height < 1 || right < 4)
1612 return;
1613 if (view_may_still_grow (view))
1614 return;
1615 filesize = view_get_filesize (view);
1617 if (filesize == 0 || view->dpy_end == filesize)
1618 percent = 100;
1619 else if (p > (INT_MAX / 100))
1620 percent = p / (filesize / 100);
1621 else
1622 percent = p * 100 / filesize;
1624 widget_move (view, top, right - 4);
1625 tty_printf ("%3d%%", percent);
1628 static void
1629 view_display_status (WView *view)
1631 const screen_dimen top = view->status_area.top;
1632 const screen_dimen left = view->status_area.left;
1633 const screen_dimen width = view->status_area.width;
1634 const screen_dimen height = view->status_area.height;
1635 const char *file_label, *file_name;
1636 screen_dimen file_label_width;
1637 int i;
1639 if (height < 1)
1640 return;
1642 tty_setcolor (SELECTED_COLOR);
1643 widget_move (view, top, left);
1644 hline (' ', width);
1646 file_label = _("File: %s");
1647 file_label_width = strlen (file_label) - 2;
1648 file_name = view->filename ? view->filename
1649 : view->command ? view->command
1650 : "";
1652 if (width < file_label_width + 6)
1653 addstr ((char *) name_trunc (file_name, width));
1654 else {
1655 i = (width > 22 ? 22 : width) - file_label_width;
1656 tty_printf (file_label, name_trunc (file_name, i));
1657 if (width > 46) {
1658 widget_move (view, top, left + 24);
1659 /* FIXME: the format strings need to be changed when offset_type changes */
1660 if (view->hex_mode)
1661 tty_printf (_("Offset 0x%08lx"), (unsigned long) view->hex_cursor);
1662 else {
1663 offset_type line, col;
1664 view_offset_to_coord (view, &line, &col, view->dpy_start);
1665 tty_printf (_("Line %lu Col %lu"),
1666 (unsigned long) line + 1,
1667 (unsigned long) (view->text_wrap_mode ? col : view->dpy_text_column));
1670 if (width > 62) {
1671 offset_type filesize;
1672 filesize = view_get_filesize (view);
1673 widget_move (view, top, left + 43);
1674 if (!view_may_still_grow (view)) {
1675 tty_printf (_("%s bytes"), size_trunc (filesize));
1676 } else {
1677 tty_printf (_(">= %s bytes"), size_trunc (filesize));
1680 if (width > 26) {
1681 view_percent (view, view->hex_mode
1682 ? view->hex_cursor
1683 : view->dpy_end);
1686 tty_setcolor (SELECTED_COLOR);
1689 static inline void
1690 view_display_clean (WView *view)
1692 tty_setcolor (NORMAL_COLOR);
1693 widget_erase ((Widget *) view);
1694 if (view->dpy_frame_size != 0) {
1695 draw_double_box (view->widget.parent, view->widget.y,
1696 view->widget.x, view->widget.lines,
1697 view->widget.cols);
1701 typedef enum {
1702 MARK_NORMAL,
1703 MARK_SELECTED,
1704 MARK_CURSOR,
1705 MARK_CHANGED
1706 } mark_t;
1708 static inline int
1709 view_count_backspaces (WView *view, off_t offset)
1711 int backspaces = 0;
1712 while (offset >= 2 * backspaces
1713 && get_byte (view, offset - 2 * backspaces) == '\b')
1714 backspaces++;
1715 return backspaces;
1718 static void
1719 view_display_ruler (WView *view)
1721 static const char ruler_chars[] = "|----*----";
1722 const screen_dimen top = view->ruler_area.top;
1723 const screen_dimen left = view->ruler_area.left;
1724 const screen_dimen width = view->ruler_area.width;
1725 const screen_dimen height = view->ruler_area.height;
1726 const screen_dimen line_row = (ruler == RULER_TOP) ? 0 : 1;
1727 const screen_dimen nums_row = (ruler == RULER_TOP) ? 1 : 0;
1729 char r_buff[10];
1730 offset_type cl;
1731 screen_dimen c;
1733 if (ruler == RULER_NONE || height < 1)
1734 return;
1736 tty_setcolor (MARKED_COLOR);
1737 for (c = 0; c < width; c++) {
1738 cl = view->dpy_text_column + c;
1739 if (line_row < height) {
1740 widget_move (view, top + line_row, left + c);
1741 tty_print_char (ruler_chars[cl % 10]);
1744 if ((cl != 0) && (cl % 10) == 0) {
1745 g_snprintf (r_buff, sizeof (r_buff), "%"OFFSETTYPE_PRId, cl);
1746 if (nums_row < height) {
1747 widget_move (view, top + nums_row, left + c - 1);
1748 tty_print_string (r_buff);
1752 attrset (NORMAL_COLOR);
1755 static void
1756 view_display_hex (WView *view)
1758 const screen_dimen top = view->data_area.top;
1759 const screen_dimen left = view->data_area.left;
1760 const screen_dimen height = view->data_area.height;
1761 const screen_dimen width = view->data_area.width;
1762 const int ngroups = view->bytes_per_line / 4;
1763 const screen_dimen text_start =
1764 8 + 13 * ngroups + ((width < 80) ? 0 : (ngroups - 1 + 1));
1765 /* 8 characters are used for the file offset, and every hex group
1766 * takes 13 characters. On ``big'' screens, the groups are separated
1767 * by an extra vertical line, and there is an extra space before the
1768 * text column.
1771 screen_dimen row, col;
1772 offset_type from;
1773 int c;
1774 mark_t boldflag = MARK_NORMAL;
1775 struct hexedit_change_node *curr = view->change_list;
1776 size_t i;
1778 char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */
1779 int bytes; /* Number of bytes already printed on the line */
1781 view_display_clean (view);
1783 /* Find the first displayable changed byte */
1784 from = view->dpy_start;
1785 while (curr && (curr->offset < from)) {
1786 curr = curr->next;
1789 for (row = 0; get_byte (view, from) != -1 && row < height; row++) {
1790 col = 0;
1792 /* Print the hex offset */
1793 g_snprintf (hex_buff, sizeof (hex_buff), "%08"OFFSETTYPE_PRIX" ", from);
1794 widget_move (view, top + row, left);
1795 tty_setcolor (MARKED_COLOR);
1796 for (i = 0; col < width && hex_buff[i] != '\0'; i++) {
1797 tty_print_char(hex_buff[i]);
1798 col += 1;
1800 tty_setcolor (NORMAL_COLOR);
1802 for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++) {
1804 if ((c = get_byte (view, from)) == -1)
1805 break;
1807 /* Save the cursor position for view_place_cursor() */
1808 if (from == view->hex_cursor && !view->hexview_in_text) {
1809 view->cursor_row = row;
1810 view->cursor_col = col;
1813 /* Determine the state of the current byte */
1814 boldflag =
1815 (from == view->hex_cursor) ? MARK_CURSOR
1816 : (curr != NULL && from == curr->offset) ? MARK_CHANGED
1817 : (view->search_start <= from &&
1818 from < view->search_start + view->search_length
1819 ) ? MARK_SELECTED
1820 : MARK_NORMAL;
1822 /* Determine the value of the current byte */
1823 if (curr != NULL && from == curr->offset) {
1824 c = curr->value;
1825 curr = curr->next;
1828 /* Select the color for the hex number */
1829 tty_setcolor (
1830 boldflag == MARK_NORMAL ? NORMAL_COLOR :
1831 boldflag == MARK_SELECTED ? MARKED_COLOR :
1832 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
1833 /* boldflag == MARK_CURSOR */
1834 view->hexview_in_text ? MARKED_SELECTED_COLOR :
1835 VIEW_UNDERLINED_COLOR);
1837 /* Print the hex number */
1838 widget_move (view, top + row, left + col);
1839 if (col < width) {
1840 tty_print_char (hex_char[c / 16]);
1841 col += 1;
1843 if (col < width) {
1844 tty_print_char (hex_char[c % 16]);
1845 col += 1;
1848 /* Print the separator */
1849 tty_setcolor (NORMAL_COLOR);
1850 if (bytes != view->bytes_per_line - 1) {
1851 if (col < width) {
1852 tty_print_char (' ');
1853 col += 1;
1856 /* After every four bytes, print a group separator */
1857 if (bytes % 4 == 3) {
1858 if (view->data_area.width >= 80 && col < width) {
1859 tty_print_one_vline ();
1860 col += 1;
1862 if (col < width) {
1863 tty_print_char (' ');
1864 col += 1;
1869 /* Select the color for the character; this differs from the
1870 * hex color when boldflag == MARK_CURSOR */
1871 tty_setcolor (
1872 boldflag == MARK_NORMAL ? NORMAL_COLOR :
1873 boldflag == MARK_SELECTED ? MARKED_COLOR :
1874 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
1875 /* boldflag == MARK_CURSOR */
1876 view->hexview_in_text ? VIEW_UNDERLINED_COLOR :
1877 MARKED_SELECTED_COLOR);
1879 c = convert_to_display_c (c);
1880 if (!is_printable (c))
1881 c = '.';
1883 /* Print corresponding character on the text side */
1884 if (text_start + bytes < width) {
1885 widget_move (view, top + row, left + text_start + bytes);
1886 tty_print_char (c);
1889 /* Save the cursor position for view_place_cursor() */
1890 if (from == view->hex_cursor && view->hexview_in_text) {
1891 view->cursor_row = row;
1892 view->cursor_col = text_start + bytes;
1897 /* Be polite to the other functions */
1898 tty_setcolor (NORMAL_COLOR);
1900 view_place_cursor (view);
1901 view->dpy_end = from;
1904 static void
1905 view_display_text (WView * view)
1907 const screen_dimen left = view->data_area.left;
1908 const screen_dimen top = view->data_area.top;
1909 const screen_dimen width = view->data_area.width;
1910 const screen_dimen height = view->data_area.height;
1911 screen_dimen row, col;
1912 offset_type from;
1913 int c;
1914 struct hexedit_change_node *curr = view->change_list;
1916 view_display_clean (view);
1917 view_display_ruler (view);
1919 /* Find the first displayable changed byte */
1920 from = view->dpy_start;
1921 while (curr && (curr->offset < from)) {
1922 curr = curr->next;
1925 tty_setcolor (NORMAL_COLOR);
1926 for (row = 0, col = 0; row < height && (c = get_byte (view, from)) != -1; from++) {
1928 if (view->text_nroff_mode && c == '\b') {
1929 int c_prev;
1930 int c_next;
1932 if ((c_next = get_byte_indexed (view, from, 1)) != -1
1933 && is_printable (c_next)
1934 && from >= 1
1935 && (c_prev = get_byte (view, from - 1)) != -1
1936 && is_printable (c_prev)
1937 && (c_prev == c_next || c_prev == '_'
1938 || (c_prev == '+' && c_next == 'o'))) {
1939 if (col == 0) {
1940 if (row == 0) {
1941 /* We're inside an nroff character sequence at the
1942 * beginning of the screen -- just skip the
1943 * backspace and continue with the next character. */
1944 continue;
1946 row--;
1947 col = width;
1949 col--;
1950 if (c_prev == '_' && (c_next != '_' || view_count_backspaces (view, from) == 1))
1951 tty_setcolor (VIEW_UNDERLINED_COLOR);
1952 else
1953 tty_setcolor (MARKED_COLOR);
1954 continue;
1958 if ((c == '\n') || (col >= width && view->text_wrap_mode)) {
1959 col = 0;
1960 row++;
1961 if (c == '\n' || row >= height)
1962 continue;
1965 if (c == '\r') {
1966 c = get_byte_indexed(view, from, 1);
1967 if (c == '\r' || c == '\n')
1968 continue;
1969 col = 0;
1970 row++;
1971 continue;
1974 if (c == '\t') {
1975 offset_type line, column;
1976 view_offset_to_coord (view, &line, &column, from);
1977 col += (8 - column % 8);
1978 if (view->text_wrap_mode && col >= width && width != 0) {
1979 row += col / width;
1980 col %= width;
1982 continue;
1985 if (view->search_start <= from
1986 && from < view->search_start + view->search_length) {
1987 tty_setcolor (SELECTED_COLOR);
1990 if (col >= view->dpy_text_column
1991 && col - view->dpy_text_column < width) {
1992 widget_move (view, top + row, left + (col - view->dpy_text_column));
1993 c = convert_to_display_c (c);
1994 if (!is_printable (c))
1995 c = '.';
1996 tty_print_char (c);
1998 col++;
1999 tty_setcolor (NORMAL_COLOR);
2001 view->dpy_end = from;
2004 /* Displays as much data from view->dpy_start as fits on the screen */
2005 static void
2006 display (WView *view)
2008 view_compute_areas (view);
2009 if (view->hex_mode) {
2010 view_display_hex (view);
2011 } else {
2012 view_display_text (view);
2014 view_display_status (view);
2017 static void
2018 view_place_cursor (WView *view)
2020 const screen_dimen top = view->data_area.top;
2021 const screen_dimen left = view->data_area.left;
2022 screen_dimen col;
2024 col = view->cursor_col;
2025 if (!view->hexview_in_text && view->hexedit_lownibble)
2026 col++;
2027 widget_move (&view->widget, top + view->cursor_row, left + col);
2030 static void
2031 view_update (WView *view)
2033 static int dirt_limit = 1;
2035 if (view->dpy_bbar_dirty) {
2036 view->dpy_bbar_dirty = FALSE;
2037 view_labels (view);
2038 buttonbar_redraw (view->widget.parent);
2041 if (view->dirty > dirt_limit) {
2042 /* Too many updates skipped -> force a update */
2043 display (view);
2044 view->dirty = 0;
2045 /* Raise the update skipping limit */
2046 dirt_limit++;
2047 if (dirt_limit > max_dirt_limit)
2048 dirt_limit = max_dirt_limit;
2050 if (view->dirty) {
2051 if (is_idle ()) {
2052 /* We have time to update the screen properly */
2053 display (view);
2054 view->dirty = 0;
2055 if (dirt_limit > 1)
2056 dirt_limit--;
2057 } else {
2058 /* We are busy -> skipping full update,
2059 only the status line is updated */
2060 view_display_status (view);
2062 /* Here we had a refresh, if fast scrolling does not work
2063 restore the refresh, although this should not happen */
2067 /* {{{ Hex editor }}} */
2069 static void
2070 enqueue_change (struct hexedit_change_node **head,
2071 struct hexedit_change_node *node)
2073 /* chnode always either points to the head of the list or
2074 * to one of the ->next fields in the list. The value at
2075 * this location will be overwritten with the new node. */
2076 struct hexedit_change_node **chnode = head;
2078 while (*chnode != NULL && (*chnode)->offset < node->offset)
2079 chnode = &((*chnode)->next);
2081 node->next = *chnode;
2082 *chnode = node;
2085 static cb_ret_t
2086 view_handle_editkey (WView *view, int key)
2088 struct hexedit_change_node *node;
2089 byte byte_val;
2091 /* Has there been a change at this position? */
2092 node = view->change_list;
2093 while (node && (node->offset != view->hex_cursor))
2094 node = node->next;
2096 if (!view->hexview_in_text) {
2097 /* Hex editing */
2098 unsigned int hexvalue = 0;
2100 if (key >= '0' && key <= '9')
2101 hexvalue = 0 + (key - '0');
2102 else if (key >= 'A' && key <= 'F')
2103 hexvalue = 10 + (key - 'A');
2104 else if (key >= 'a' && key <= 'f')
2105 hexvalue = 10 + (key - 'a');
2106 else
2107 return MSG_NOT_HANDLED;
2109 if (node)
2110 byte_val = node->value;
2111 else
2112 byte_val = get_byte (view, view->hex_cursor);
2114 if (view->hexedit_lownibble) {
2115 byte_val = (byte_val & 0xf0) | (hexvalue);
2116 } else {
2117 byte_val = (byte_val & 0x0f) | (hexvalue << 4);
2119 } else {
2120 /* Text editing */
2121 if (key < 256 && (is_printable (key) || (key == '\n')))
2122 byte_val = key;
2123 else
2124 return MSG_NOT_HANDLED;
2126 if (!node) {
2127 node = g_new (struct hexedit_change_node, 1);
2128 node->offset = view->hex_cursor;
2129 node->value = byte_val;
2130 enqueue_change (&view->change_list, node);
2131 } else {
2132 node->value = byte_val;
2134 view->dirty++;
2135 view_update (view);
2136 view_move_right (view, 1);
2137 return MSG_HANDLED;
2140 static bool
2141 view_hexedit_save_changes (WView *view)
2143 struct hexedit_change_node *curr, *next;
2144 int fp, answer;
2145 char *text, *error;
2147 if (view->change_list == NULL)
2148 return TRUE;
2150 retry_save:
2151 assert (view->filename != NULL);
2152 fp = mc_open (view->filename, O_WRONLY);
2153 if (fp == -1)
2154 goto save_error;
2156 for (curr = view->change_list; curr != NULL; curr = next) {
2157 next = curr->next;
2159 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
2160 || mc_write (fp, &(curr->value), 1) != 1)
2161 goto save_error;
2163 /* delete the saved item from the change list */
2164 view->change_list = next;
2165 view->dirty++;
2166 view_set_byte (view, curr->offset, curr->value);
2167 g_free (curr);
2170 if (mc_close (fp) == -1) {
2171 error = g_strdup (strerror (errno));
2172 message (D_ERROR, _(" Save file "),
2173 _(" Error while closing the file: \n %s \n"
2174 " Data may have been written or not. "), error);
2175 g_free (error);
2177 view_update (view);
2178 return TRUE;
2180 save_error:
2181 error = g_strdup (strerror (errno));
2182 text = g_strdup_printf (_(" Cannot save file: \n %s "), error);
2183 g_free (error);
2184 (void) mc_close (fp);
2186 answer = query_dialog (_(" Save file "), text, D_ERROR,
2187 2, _("&Retry"), _("&Cancel"));
2188 g_free (text);
2190 if (answer == 0)
2191 goto retry_save;
2192 return FALSE;
2195 /* {{{ Miscellaneous functions }}} */
2197 static bool
2198 view_ok_to_quit (WView *view)
2200 int r;
2202 if (view->change_list == NULL)
2203 return TRUE;
2205 r = query_dialog (_("Quit"),
2206 _(" File was modified, Save with exit? "), D_NORMAL, 3,
2207 _("&Cancel quit"), _("&Yes"), _("&No"));
2209 switch (r) {
2210 case 1:
2211 return view_hexedit_save_changes (view);
2212 case 2:
2213 view_hexedit_free_change_list (view);
2214 return TRUE;
2215 default:
2216 return FALSE;
2220 static inline void
2221 my_define (Dlg_head *h, int idx, const char *text, void (*fn) (WView *),
2222 WView *view)
2224 buttonbar_set_label_data (h, idx, text, (buttonbarfn) fn, view);
2227 /* {{{ Searching }}} */
2229 /* Case insensitive search of text in data */
2230 static int
2231 icase_search_p (WView *view, char *text, char *data, int nothing)
2233 const char *q;
2234 int lng;
2235 const int direction = view->direction;
2237 (void) nothing;
2239 /* If we are searching backwards, reverse the string */
2240 if (direction == -1) {
2241 g_strreverse (text);
2242 g_strreverse (data);
2245 q = _icase_search (text, data, &lng);
2247 if (direction == -1) {
2248 g_strreverse (text);
2249 g_strreverse (data);
2252 if (q != 0) {
2253 if (direction > 0)
2254 view->search_start = q - data - lng;
2255 else
2256 view->search_start = strlen (data) - (q - data);
2257 view->search_length = lng;
2258 return 1;
2260 return 0;
2263 static char *
2264 grow_string_buffer (char *text, gulong *size)
2266 char *new;
2268 /* The grow steps */
2269 *size += 160;
2270 new = g_realloc (text, *size);
2271 if (text == NULL) {
2272 *new = '\0';
2274 return new;
2277 static char *
2278 get_line_at (WView *view, offset_type *p, offset_type *skipped)
2280 char *buffer = NULL;
2281 gulong buffer_size = 0;
2282 offset_type usable_size = 0;
2283 int ch;
2284 const int direction = view->direction;
2285 offset_type pos = *p;
2286 offset_type i = 0;
2287 int prev = '\0';
2289 *skipped = 0;
2291 if (pos == 0 && direction == -1)
2292 return 0;
2294 /* skip over all the possible zeros in the file */
2295 while ((ch = get_byte (view, pos)) == 0) {
2296 if (pos == 0 && direction == -1)
2297 return 0;
2298 pos += direction;
2299 i++;
2301 *skipped = i;
2303 if (i == 0 && (pos != 0 || direction == -1)) {
2304 prev = get_byte (view, pos - direction);
2305 if ((prev == -1) || (prev == '\n'))
2306 prev = '\0';
2309 for (i = 1; ch != -1; ch = get_byte (view, pos)) {
2310 if (i >= usable_size) {
2311 buffer = grow_string_buffer (buffer, &buffer_size);
2312 usable_size = buffer_size - 2; /* prev & null terminator */
2315 buffer[i++] = ch;
2317 if (pos == 0 && direction == -1)
2318 break;
2320 pos += direction;
2322 if (ch == '\n' || ch == '\0') {
2323 i--; /* Strip newline/zero */
2324 break;
2328 if (buffer) {
2329 buffer[0] = prev;
2330 buffer[i] = '\0';
2332 /* If we are searching backwards, reverse the string */
2333 if (direction == -1) {
2334 g_strreverse (buffer + 1);
2338 *p = pos;
2339 return buffer;
2342 static void
2343 search_update_steps (WView *view)
2345 offset_type filesize = view_get_filesize (view);
2346 if (filesize != 0)
2347 view->update_steps = 40000;
2348 else /* viewing a data stream, not a file */
2349 view->update_steps = filesize / 100;
2351 /* Do not update the percent display but every 20 ks */
2352 if (view->update_steps < 20000)
2353 view->update_steps = 20000;
2356 static void
2357 search (WView *view, char *text,
2358 int (*search) (WView *, char *, char *, int))
2360 char *s = NULL; /* The line we read from the view buffer */
2361 offset_type p, beginning, search_start;
2362 int found_len;
2363 int search_status;
2364 Dlg_head *d = 0;
2366 /* Used to keep track of where the line starts, when looking forward
2367 * is the index before transfering the line; the reverse case uses
2368 * the position returned after the line has been read */
2369 offset_type forward_line_start;
2370 offset_type reverse_line_start;
2371 offset_type t;
2373 if (verbose) {
2374 d = create_message (D_NORMAL, _("Search"), _("Searching %s"), text);
2375 mc_refresh ();
2378 found_len = view->search_length;
2379 search_start = view->search_start;
2381 if (view->direction == 1) {
2382 p = search_start + ((found_len) ? 1 : 0);
2383 } else {
2384 p = search_start - ((found_len && search_start >= 1) ? 1 : 0);
2386 beginning = p;
2388 /* Compute the percent steps */
2389 search_update_steps (view);
2390 view->update_activate = 0;
2392 enable_interrupt_key ();
2393 for (;; g_free (s)) {
2394 if (p >= view->update_activate) {
2395 view->update_activate += view->update_steps;
2396 if (verbose) {
2397 view_percent (view, p);
2398 mc_refresh ();
2400 if (got_interrupt ())
2401 break;
2403 forward_line_start = p;
2404 s = get_line_at (view, &p, &t);
2405 reverse_line_start = p;
2407 if (!s)
2408 break;
2410 search_status = (*search) (view, text, s + 1, match_normal);
2411 if (search_status < 0) {
2412 g_free (s);
2413 break;
2416 if (search_status == 0)
2417 continue;
2419 /* We found the string */
2421 /* Handle ^ and $ when regexp search starts at the middle of the line */
2422 if (*s && !view->search_start && (search == regexp_view_search)) {
2423 if ((*text == '^' && view->direction == 1)
2424 || (view->direction == -1 && text[strlen (text) - 1] == '$')
2426 continue;
2429 /* Record the position used to continue the search */
2430 if (view->direction == 1)
2431 t += forward_line_start;
2432 else
2433 t = reverse_line_start ? reverse_line_start + 2 : 0;
2434 view->search_start += t;
2436 if (t != beginning) {
2437 view->dpy_start = t;
2440 g_free (s);
2441 break;
2443 disable_interrupt_key ();
2444 if (verbose) {
2445 dlg_run_done (d);
2446 destroy_dlg (d);
2448 if (!s) {
2449 message (D_NORMAL, _("Search"), _(" Search string not found "));
2450 view->search_length = 0;
2454 /* Search buffer (its size is len) in the complete buffer
2455 * returns the position where the block was found or INVALID_OFFSET
2456 * if not found */
2457 static offset_type
2458 block_search (WView *view, const char *buffer, int len)
2460 int direction = view->direction;
2461 const char *d = buffer;
2462 char b;
2463 offset_type e;
2465 enable_interrupt_key ();
2466 if (direction == 1)
2467 e = view->search_start + ((view->search_length) ? 1 : 0);
2468 else
2469 e = view->search_start
2470 - ((view->search_length && view->search_start >= 1) ? 1 : 0);
2472 search_update_steps (view);
2473 view->update_activate = 0;
2475 if (direction == -1) {
2476 for (d += len - 1;; e--) {
2477 if (e <= view->update_activate) {
2478 view->update_activate -= view->update_steps;
2479 if (verbose) {
2480 view_percent (view, e);
2481 mc_refresh ();
2483 if (got_interrupt ())
2484 break;
2486 b = get_byte (view, e);
2488 if (*d == b) {
2489 if (d == buffer) {
2490 disable_interrupt_key ();
2491 return e;
2493 d--;
2494 } else {
2495 e += buffer + len - 1 - d;
2496 d = buffer + len - 1;
2498 if (e == 0)
2499 break;
2501 } else {
2502 while (get_byte (view, e) != -1) {
2503 if (e >= view->update_activate) {
2504 view->update_activate += view->update_steps;
2505 if (verbose) {
2506 view_percent (view, e);
2507 mc_refresh ();
2509 if (got_interrupt ())
2510 break;
2512 b = get_byte (view, e++);
2514 if (*d == b) {
2515 d++;
2516 if (d - buffer == len) {
2517 disable_interrupt_key ();
2518 return e - len;
2520 } else {
2521 e -= d - buffer;
2522 d = buffer;
2526 disable_interrupt_key ();
2527 return INVALID_OFFSET;
2531 * Search in the hex mode. Supported input:
2532 * - numbers (oct, dec, hex). Each of them matches one byte.
2533 * - strings in double quotes. Matches exactly without quotes.
2535 static void
2536 hex_search (WView *view, const char *text)
2538 char *buffer; /* Parsed search string */
2539 char *cur; /* Current position in it */
2540 int block_len; /* Length of the search string */
2541 offset_type pos; /* Position of the string in the file */
2542 int parse_error = 0;
2544 if (!*text) {
2545 view->search_length = 0;
2546 return;
2549 /* buffer will never be longer that text */
2550 buffer = g_new (char, strlen (text));
2551 cur = buffer;
2553 /* First convert the string to a stream of bytes */
2554 while (*text) {
2555 int val;
2556 int ptr;
2558 /* Skip leading spaces */
2559 if (*text == ' ' || *text == '\t') {
2560 text++;
2561 continue;
2564 /* %i matches octal, decimal, and hexadecimal numbers */
2565 if (sscanf (text, "%i%n", &val, &ptr) > 0) {
2566 /* Allow signed and unsigned char in the user input */
2567 if (val < -128 || val > 255) {
2568 parse_error = 1;
2569 break;
2572 *cur++ = (char) val;
2573 text += ptr;
2574 continue;
2577 /* Try quoted string, strip quotes */
2578 if (*text == '"') {
2579 const char *next_quote;
2581 text++;
2582 next_quote = strchr (text, '"');
2583 if (next_quote) {
2584 memcpy (cur, text, next_quote - text);
2585 cur += next_quote - text;
2586 text = next_quote + 1;
2587 continue;
2589 /* fall through */
2592 parse_error = 1;
2593 break;
2596 block_len = cur - buffer;
2598 /* No valid bytes in the user input */
2599 if (block_len <= 0 || parse_error) {
2600 message (D_NORMAL, _("Search"), _("Invalid hex search expression"));
2601 g_free (buffer);
2602 view->search_length = 0;
2603 return;
2606 /* Then start the search */
2607 pos = block_search (view, buffer, block_len);
2609 g_free (buffer);
2611 if (pos == INVALID_OFFSET) {
2612 message (D_NORMAL, _("Search"), _(" Search string not found "));
2613 view->search_length = 0;
2614 return;
2617 view->search_start = pos;
2618 view->search_length = block_len;
2619 /* Set the edit cursor to the search position, left nibble */
2620 view->hex_cursor = view->search_start;
2621 view->hexedit_lownibble = FALSE;
2623 /* Adjust the file offset */
2624 view->dpy_start = pos - pos % view->bytes_per_line;
2627 static int
2628 regexp_view_search (WView *view, char *pattern, char *string,
2629 int match_type)
2631 static regex_t r;
2632 static char *old_pattern = NULL;
2633 static int old_type;
2634 regmatch_t pmatch[1];
2635 int i, flags = REG_ICASE;
2637 if (old_pattern == NULL || strcmp (old_pattern, pattern) != 0
2638 || old_type != match_type) {
2639 if (old_pattern != NULL) {
2640 regfree (&r);
2641 g_free (old_pattern);
2642 old_pattern = 0;
2644 for (i = 0; pattern[i] != '\0'; i++) {
2645 if (isupper ((unsigned char) pattern[i])) {
2646 flags = 0;
2647 break;
2650 flags |= REG_EXTENDED;
2651 if (regcomp (&r, pattern, flags)) {
2652 message (D_ERROR, MSG_ERROR, _(" Invalid regular expression "));
2653 return -1;
2655 old_pattern = g_strdup (pattern);
2656 old_type = match_type;
2658 if (regexec (&r, string, 1, pmatch, 0) != 0)
2659 return 0;
2660 view->search_length = pmatch[0].rm_eo - pmatch[0].rm_so;
2661 view->search_start = pmatch[0].rm_so;
2662 return 1;
2665 static void
2666 do_regexp_search (WView *view)
2668 search (view, view->search_exp, regexp_view_search);
2669 /* Had a refresh here */
2670 view->dirty++;
2671 view_update (view);
2674 static void
2675 do_normal_search (WView *view)
2677 if (view->hex_mode)
2678 hex_search (view, view->search_exp);
2679 else
2680 search (view, view->search_exp, icase_search_p);
2681 /* Had a refresh here */
2682 view->dirty++;
2683 view_update (view);
2686 /* {{{ User-definable commands }}} */
2689 The functions in this section can be bound to hotkeys. They are all
2690 of the same type (taking a pointer to WView as parameter and
2691 returning void). TODO: In the not-too-distant future, these commands
2692 will become fully configurable, like they already are in the
2693 internal editor. By convention, all the function names end in
2694 "_cmd".
2697 static void
2698 view_help_cmd (void)
2700 interactive_display (NULL, "[Internal File Viewer]");
2703 /* Toggle between hexview and hexedit mode */
2704 static void
2705 view_toggle_hexedit_mode_cmd (WView *view)
2707 view_toggle_hexedit_mode (view);
2708 view_update (view);
2711 /* Toggle between wrapped and unwrapped view */
2712 static void
2713 view_toggle_wrap_mode_cmd (WView *view)
2715 view_toggle_wrap_mode (view);
2716 view_update (view);
2719 /* Toggle between hex view and text view */
2720 static void
2721 view_toggle_hex_mode_cmd (WView *view)
2723 view_toggle_hex_mode (view);
2724 view_update (view);
2727 static void
2728 view_moveto_line_cmd (WView *view)
2730 char *answer, *answer_end, prompt[BUF_SMALL];
2731 offset_type line, col;
2733 view_offset_to_coord (view, &line, &col, view->dpy_start);
2735 g_snprintf (prompt, sizeof (prompt),
2736 _(" The current line number is %d.\n"
2737 " Enter the new line number:"), (int) (line + 1));
2738 answer = input_dialog (_(" Goto line "), prompt, MC_HISTORY_VIEW_GOTO_LINE, "");
2739 if (answer != NULL && answer[0] != '\0') {
2740 errno = 0;
2741 line = strtoul (answer, &answer_end, 10);
2742 if (*answer_end == '\0' && errno == 0 && line >= 1)
2743 view_moveto (view, line - 1, 0);
2745 g_free (answer);
2746 view->dirty++;
2747 view_update (view);
2750 static void
2751 view_moveto_addr_cmd (WView *view)
2753 char *line, *error, prompt[BUF_SMALL];
2754 offset_type addr;
2756 g_snprintf (prompt, sizeof (prompt),
2757 _(" The current address is 0x%lx.\n"
2758 " Enter the new address:"), view->hex_cursor);
2759 line = input_dialog (_(" Goto Address "), prompt, MC_HISTORY_VIEW_GOTO_ADDR, "");
2760 if (line != NULL) {
2761 if (*line != '\0') {
2762 addr = strtoul (line, &error, 0);
2763 if ((*error == '\0') && get_byte (view, addr) != -1) {
2764 view_moveto_offset (view, addr);
2765 } else {
2766 message (D_ERROR, _("Warning"), _(" Invalid address "));
2769 g_free (line);
2771 view->dirty++;
2772 view_update (view);
2775 static void
2776 view_hexedit_save_changes_cmd (WView *view)
2778 (void) view_hexedit_save_changes (view);
2781 /* {{{ Searching }}} */
2783 static void
2784 regexp_search (WView *view, int direction)
2786 const char *defval;
2787 char *regexp;
2788 static char *last_regexp;
2790 defval = (last_regexp != NULL ? last_regexp : "");
2792 regexp = input_dialog (_("Search"), _(" Enter regexp:"), MC_HISTORY_VIEW_SEARCH_REGEX, defval);
2793 if (regexp == NULL || regexp[0] == '\0') {
2794 g_free (regexp);
2795 return;
2798 g_free (last_regexp);
2799 view->search_exp = last_regexp = regexp;
2801 view->direction = direction;
2802 do_regexp_search (view);
2803 view->last_search = do_regexp_search;
2806 /* {{{ User-definable commands }}} */
2808 static void
2809 view_regexp_search_cmd (WView *view)
2811 regexp_search (view, 1);
2814 /* Both views */
2815 static void
2816 view_normal_search_cmd (WView *view)
2818 char *defval, *exp = NULL;
2819 static char *last_search_string;
2821 enum {
2822 SEARCH_DLG_HEIGHT = 8,
2823 SEARCH_DLG_WIDTH = 58
2826 static int replace_backwards;
2827 int treplace_backwards = replace_backwards;
2829 static QuickWidget quick_widgets[] = {
2830 {quick_button, 6, 10, 5, SEARCH_DLG_HEIGHT, N_("&Cancel"), 0,
2831 B_CANCEL,
2832 0, 0, NULL},
2833 {quick_button, 2, 10, 5, SEARCH_DLG_HEIGHT, N_("&OK"), 0, B_ENTER,
2834 0, 0, NULL},
2835 {quick_checkbox, 3, SEARCH_DLG_WIDTH, 4, SEARCH_DLG_HEIGHT,
2836 N_("&Backwards"), 0, 0,
2837 0, 0, NULL},
2838 {quick_input, 3, SEARCH_DLG_WIDTH, 3, SEARCH_DLG_HEIGHT, "", 52, 0,
2839 0, 0, N_("Search")},
2840 {quick_label, 2, SEARCH_DLG_WIDTH, 2, SEARCH_DLG_HEIGHT,
2841 N_(" Enter search string:"), 0, 0,
2842 0, 0, 0},
2843 NULL_QuickWidget
2845 static QuickDialog Quick_input = {
2846 SEARCH_DLG_WIDTH, SEARCH_DLG_HEIGHT, -1, 0, N_("Search"),
2847 "[Input Line Keys]", quick_widgets, 0
2850 defval = g_strdup (last_search_string != NULL ? last_search_string : "");
2851 convert_to_display (defval);
2853 quick_widgets[2].result = &treplace_backwards;
2854 quick_widgets[3].str_result = &exp;
2855 quick_widgets[3].text = defval;
2857 if (quick_dialog (&Quick_input) == B_CANCEL)
2858 goto cleanup;
2860 replace_backwards = treplace_backwards;
2862 if (exp == NULL || exp[0] == '\0')
2863 goto cleanup;
2865 convert_from_input (exp);
2867 g_free (last_search_string);
2868 view->search_exp = last_search_string = exp;
2869 exp = NULL;
2871 view->direction = replace_backwards ? -1 : 1;
2872 do_normal_search (view);
2873 view->last_search = do_normal_search;
2875 cleanup:
2876 g_free (exp);
2877 g_free (defval);
2880 static void
2881 view_toggle_magic_mode_cmd (WView *view)
2883 view_toggle_magic_mode (view);
2884 view_update (view);
2887 static void
2888 view_toggle_nroff_mode_cmd (WView *view)
2890 view_toggle_nroff_mode (view);
2891 view_update (view);
2894 static void
2895 view_quit_cmd (WView *view)
2897 if (view_ok_to_quit (view))
2898 dlg_stop (view->widget.parent);
2901 /* {{{ Miscellaneous functions }}} */
2903 /* Define labels and handlers for functional keys */
2904 static void
2905 view_labels (WView *view)
2907 Dlg_head *h = view->widget.parent;
2909 buttonbar_set_label (h, 1, Q_("ButtonBar|Help"), view_help_cmd);
2911 my_define (h, 10, Q_("ButtonBar|Quit"), view_quit_cmd, view);
2912 my_define (h, 4, view->hex_mode
2913 ? Q_("ButtonBar|Ascii")
2914 : Q_("ButtonBar|Hex"),
2915 view_toggle_hex_mode_cmd, view);
2916 my_define (h, 5, view->hex_mode
2917 ? Q_("ButtonBar|Goto")
2918 : Q_("ButtonBar|Line"),
2919 view->hex_mode ? view_moveto_addr_cmd : view_moveto_line_cmd, view);
2921 if (view->hex_mode) {
2922 if (view->hexedit_mode) {
2923 my_define (h, 2, Q_("ButtonBar|View"),
2924 view_toggle_hexedit_mode_cmd, view);
2925 } else if (view->datasource == DS_FILE) {
2926 my_define (h, 2, Q_("ButtonBar|Edit"),
2927 view_toggle_hexedit_mode_cmd, view);
2928 } else {
2929 buttonbar_clear_label (h, 2);
2931 my_define (h, 6, Q_("ButtonBar|Save"),
2932 view_hexedit_save_changes_cmd, view);
2933 } else {
2934 my_define (h, 2, view->text_wrap_mode
2935 ? Q_("ButtonBar|UnWrap")
2936 : Q_("ButtonBar|Wrap"),
2937 view_toggle_wrap_mode_cmd, view);
2938 my_define (h, 6, Q_("ButtonBar|RxSrch"),
2939 view_regexp_search_cmd, view);
2942 my_define (h, 7, view->hex_mode
2943 ? Q_("ButtonBar|HxSrch")
2944 : Q_("ButtonBar|Search"),
2945 view_normal_search_cmd, view);
2946 my_define (h, 8, view->magic_mode
2947 ? Q_("ButtonBar|Raw")
2948 : Q_("ButtonBar|Parse"),
2949 view_toggle_magic_mode_cmd, view);
2951 /* don't override the key to access the main menu */
2952 if (!view_is_in_panel (view)) {
2953 my_define (h, 9, view->text_nroff_mode
2954 ? Q_("ButtonBar|Unform")
2955 : Q_("ButtonBar|Format"),
2956 view_toggle_nroff_mode_cmd, view);
2957 my_define (h, 3, Q_("ButtonBar|Quit"), view_quit_cmd, view);
2961 /* {{{ Event handling }}} */
2963 /* Check for left and right arrows, possibly with modifiers */
2964 static cb_ret_t
2965 check_left_right_keys (WView *view, int c)
2967 if (c == KEY_LEFT) {
2968 view_move_left (view, 1);
2969 return MSG_HANDLED;
2972 if (c == KEY_RIGHT) {
2973 view_move_right (view, 1);
2974 return MSG_HANDLED;
2977 /* Ctrl with arrows moves by 10 postions in the unwrap mode */
2978 if (view->hex_mode || view->text_wrap_mode)
2979 return MSG_NOT_HANDLED;
2981 if (c == (KEY_M_CTRL | KEY_LEFT)) {
2982 if (view->dpy_text_column >= 10)
2983 view->dpy_text_column -= 10;
2984 else
2985 view->dpy_text_column = 0;
2986 view->dirty++;
2987 return MSG_HANDLED;
2990 if (c == (KEY_M_CTRL | KEY_RIGHT)) {
2991 if (view->dpy_text_column <= OFFSETTYPE_MAX - 10)
2992 view->dpy_text_column += 10;
2993 else
2994 view->dpy_text_column = OFFSETTYPE_MAX;
2995 view->dirty++;
2996 return MSG_HANDLED;
2999 return MSG_NOT_HANDLED;
3002 /* {{{ User-definable commands }}} */
3004 static void
3005 view_continue_search_cmd (WView *view)
3007 if (view->last_search) {
3008 view->last_search (view);
3009 } else {
3010 /* if not... then ask for an expression */
3011 view_normal_search_cmd (view);
3015 static void
3016 view_toggle_ruler_cmd (WView *view)
3018 static const enum ruler_type next[3] = {
3019 RULER_TOP,
3020 RULER_BOTTOM,
3021 RULER_NONE
3024 assert ((size_t) ruler < 3);
3025 ruler = next[(size_t) ruler];
3026 view->dirty++;
3029 /* {{{ Event handling }}} */
3031 static void view_cmk_move_up (void *w, int n) {
3032 view_move_up ((WView *) w, n);
3034 static void view_cmk_move_down (void *w, int n) {
3035 view_move_down ((WView *) w, n);
3037 static void view_cmk_moveto_top (void *w, int n) {
3038 (void) &n;
3039 view_moveto_top ((WView *) w);
3041 static void view_cmk_moveto_bottom (void *w, int n) {
3042 (void) &n;
3043 view_moveto_bottom ((WView *) w);
3046 /* Both views */
3047 static cb_ret_t
3048 view_handle_key (WView *view, int c)
3050 c = convert_from_input_c (c);
3052 if (view->hex_mode) {
3053 switch (c) {
3054 case '\t':
3055 view->hexview_in_text = !view->hexview_in_text;
3056 view->dirty++;
3057 return MSG_HANDLED;
3059 case XCTRL ('a'):
3060 view_moveto_bol (view);
3061 view->dirty++;
3062 return MSG_HANDLED;
3064 case XCTRL ('b'):
3065 view_move_left (view, 1);
3066 return MSG_HANDLED;
3068 case XCTRL ('e'):
3069 view_moveto_eol (view);
3070 return MSG_HANDLED;
3072 case XCTRL ('f'):
3073 view_move_right (view, 1);
3074 return MSG_HANDLED;
3077 if (view->hexedit_mode
3078 && view_handle_editkey (view, c) == MSG_HANDLED)
3079 return MSG_HANDLED;
3082 if (check_left_right_keys (view, c))
3083 return MSG_HANDLED;
3085 if (check_movement_keys (c, view->data_area.height + 1, view,
3086 view_cmk_move_up, view_cmk_move_down,
3087 view_cmk_moveto_top, view_cmk_moveto_bottom))
3088 return MSG_HANDLED;
3090 switch (c) {
3092 case '?':
3093 regexp_search (view, -1);
3094 return MSG_HANDLED;
3096 case '/':
3097 regexp_search (view, 1);
3098 return MSG_HANDLED;
3100 /* Continue search */
3101 case XCTRL ('r'):
3102 case XCTRL ('s'):
3103 case 'n':
3104 case KEY_F (17):
3105 view_continue_search_cmd (view);
3106 return MSG_HANDLED;
3108 /* toggle ruler */
3109 case ALT ('r'):
3110 view_toggle_ruler_cmd (view);
3111 return MSG_HANDLED;
3113 case 'h':
3114 view_move_left (view, 1);
3115 return MSG_HANDLED;
3117 case 'j':
3118 case '\n':
3119 case 'e':
3120 view_move_down (view, 1);
3121 return MSG_HANDLED;
3123 case 'd':
3124 view_move_down (view, (view->data_area.height + 1) / 2);
3125 return MSG_HANDLED;
3127 case 'u':
3128 view_move_up (view, (view->data_area.height + 1) / 2);
3129 return MSG_HANDLED;
3131 case 'k':
3132 case 'y':
3133 view_move_up (view, 1);
3134 return MSG_HANDLED;
3136 case 'l':
3137 view_move_right (view, 1);
3138 return MSG_HANDLED;
3140 case ' ':
3141 case 'f':
3142 view_move_down (view, view->data_area.height);
3143 return MSG_HANDLED;
3145 case XCTRL ('o'):
3146 view_other_cmd ();
3147 return MSG_HANDLED;
3149 /* Unlike Ctrl-O, run a new shell if the subshell is not running. */
3150 case '!':
3151 exec_shell ();
3152 return MSG_HANDLED;
3154 case 'b':
3155 view_move_up (view, view->data_area.height);
3156 return MSG_HANDLED;
3158 case KEY_IC:
3159 view_move_up (view, 2);
3160 return MSG_HANDLED;
3162 case KEY_DC:
3163 view_move_down (view, 2);
3164 return MSG_HANDLED;
3166 case 'm':
3167 view->marks[view->marker] = view->dpy_start;
3168 return MSG_HANDLED;
3170 case 'r':
3171 view->dpy_start = view->marks[view->marker];
3172 view->dirty++;
3173 return MSG_HANDLED;
3175 /* Use to indicate parent that we want to see the next/previous file */
3176 /* Does not work in panel mode */
3177 case XCTRL ('f'):
3178 case XCTRL ('b'):
3179 if (!view_is_in_panel (view))
3180 view->move_dir = c == XCTRL ('f') ? 1 : -1;
3181 /* FALLTHROUGH */
3182 case 'q':
3183 case XCTRL ('g'):
3184 case ESC_CHAR:
3185 if (view_ok_to_quit (view))
3186 view->want_to_quit = TRUE;
3187 return MSG_HANDLED;
3189 #ifdef HAVE_CHARSET
3190 case XCTRL ('t'):
3191 do_select_codepage ();
3192 view->dirty++;
3193 view_update (view);
3194 return MSG_HANDLED;
3195 #endif /* HAVE_CHARSET */
3197 #ifdef MC_ENABLE_DEBUGGING_CODE
3198 case 't': /* mnemonic: "test" */
3199 view_ccache_dump (view);
3200 return MSG_HANDLED;
3201 #endif
3203 if (c >= '0' && c <= '9')
3204 view->marker = c - '0';
3206 /* Key not used */
3207 return MSG_NOT_HANDLED;
3210 /* Both views */
3211 static int
3212 view_event (WView *view, Gpm_Event *event, int *result)
3214 screen_dimen y, x;
3216 *result = MOU_NORMAL;
3218 /* We are not interested in the release events */
3219 if (!(event->type & (GPM_DOWN | GPM_DRAG)))
3220 return 0;
3222 /* Wheel events */
3223 if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) {
3224 view_move_up (view, 2);
3225 return 1;
3227 if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
3228 view_move_down (view, 2);
3229 return 1;
3232 x = event->x;
3233 y = event->y;
3235 /* Scrolling left and right */
3236 if (!view->text_wrap_mode) {
3237 if (x < view->data_area.width * 1/4) {
3238 view_move_left (view, 1);
3239 goto processed;
3240 } else if (x < view->data_area.width * 3/4) {
3241 /* ignore the click */
3242 } else {
3243 view_move_right (view, 1);
3244 goto processed;
3248 /* Scrolling up and down */
3249 if (y < view->data_area.top + view->data_area.height * 1/3) {
3250 if (mouse_move_pages_viewer)
3251 view_move_up (view, view->data_area.height / 2);
3252 else
3253 view_move_up (view, 1);
3254 goto processed;
3255 } else if (y < view->data_area.top + view->data_area.height * 2/3) {
3256 /* ignore the click */
3257 } else {
3258 if (mouse_move_pages_viewer)
3259 view_move_down (view, view->data_area.height / 2);
3260 else
3261 view_move_down (view, 1);
3262 goto processed;
3265 return 0;
3267 processed:
3268 *result = MOU_REPEAT;
3269 return 1;
3272 /* Real view only */
3273 static int
3274 real_view_event (Gpm_Event *event, void *x)
3276 WView *view = (WView *) x;
3277 int result;
3279 if (view_event (view, event, &result))
3280 view_update (view);
3281 return result;
3284 static void
3285 view_adjust_size (Dlg_head *h)
3287 WView *view;
3288 WButtonBar *bar;
3290 /* Look up the viewer and the buttonbar, we assume only two widgets here */
3291 view = (WView *) find_widget_type (h, view_callback);
3292 bar = find_buttonbar (h);
3293 widget_set_size (&view->widget, 0, 0, LINES - 1, COLS);
3294 widget_set_size ((Widget *) bar, LINES - 1, 0, 1, COLS);
3296 view_compute_areas (view);
3297 view_update_bytes_per_line (view);
3300 /* Callback for the view dialog */
3301 static cb_ret_t
3302 view_dialog_callback (Dlg_head *h, dlg_msg_t msg, int parm)
3304 switch (msg) {
3305 case DLG_RESIZE:
3306 view_adjust_size (h);
3307 return MSG_HANDLED;
3309 default:
3310 return default_dlg_callback (h, msg, parm);
3314 /* {{{ External interface }}} */
3316 /* Real view only */
3318 mc_internal_viewer (const char *command, const char *file,
3319 int *move_dir_p, int start_line)
3321 bool succeeded;
3322 WView *wview;
3323 WButtonBar *bar;
3324 Dlg_head *view_dlg;
3326 /* Create dialog and widgets, put them on the dialog */
3327 view_dlg =
3328 create_dlg (0, 0, LINES, COLS, NULL, view_dialog_callback,
3329 "[Internal File Viewer]", NULL, DLG_WANT_TAB);
3331 wview = view_new (0, 0, COLS, LINES - 1, 0);
3333 bar = buttonbar_new (1);
3335 add_widget (view_dlg, bar);
3336 add_widget (view_dlg, wview);
3338 succeeded = view_load (wview, command, file, start_line);
3339 if (succeeded) {
3340 run_dlg (view_dlg);
3341 if (move_dir_p)
3342 *move_dir_p = wview->move_dir;
3343 } else {
3344 if (move_dir_p)
3345 *move_dir_p = 0;
3347 destroy_dlg (view_dlg);
3349 return succeeded;
3352 /* {{{ Miscellaneous functions }}} */
3354 static void
3355 view_hook (void *v)
3357 WView *view = (WView *) v;
3358 WPanel *panel;
3360 /* If the user is busy typing, wait until he finishes to update the
3361 screen */
3362 if (!is_idle ()) {
3363 if (!hook_present (idle_hook, view_hook))
3364 add_hook (&idle_hook, view_hook, v);
3365 return;
3368 delete_hook (&idle_hook, view_hook);
3370 if (get_current_type () == view_listing)
3371 panel = current_panel;
3372 else if (get_other_type () == view_listing)
3373 panel = other_panel;
3374 else
3375 return;
3377 view_load (view, 0, panel->dir.list[panel->selected].fname, 0);
3378 display (view);
3381 /* {{{ Event handling }}} */
3383 static cb_ret_t
3384 view_callback (Widget *w, widget_msg_t msg, int parm)
3386 WView *view = (WView *) w;
3387 cb_ret_t i;
3388 Dlg_head *h = view->widget.parent;
3390 view_compute_areas (view);
3391 view_update_bytes_per_line (view);
3393 switch (msg) {
3394 case WIDGET_INIT:
3395 if (view_is_in_panel (view))
3396 add_hook (&select_file_hook, view_hook, view);
3397 else
3398 view->dpy_bbar_dirty = TRUE;
3399 return MSG_HANDLED;
3401 case WIDGET_DRAW:
3402 display (view);
3403 return MSG_HANDLED;
3405 case WIDGET_CURSOR:
3406 if (view->hex_mode)
3407 view_place_cursor (view);
3408 return MSG_HANDLED;
3410 case WIDGET_KEY:
3411 i = view_handle_key ((WView *) view, parm);
3412 if (view->want_to_quit && !view_is_in_panel (view))
3413 dlg_stop (h);
3414 else {
3415 view_update (view);
3417 return i;
3419 case WIDGET_FOCUS:
3420 view->dpy_bbar_dirty = TRUE;
3421 view_update (view);
3422 return MSG_HANDLED;
3424 case WIDGET_DESTROY:
3425 view_done (view);
3426 if (view_is_in_panel (view))
3427 delete_hook (&select_file_hook, view_hook);
3428 return MSG_HANDLED;
3430 default:
3431 return default_proc (msg, parm);
3435 /* {{{ External interface }}} */
3437 WView *
3438 view_new (int y, int x, int cols, int lines, int is_panel)
3440 WView *view = g_new0 (WView, 1);
3441 size_t i;
3443 init_widget (&view->widget, y, x, lines, cols,
3444 view_callback,
3445 real_view_event);
3447 view->filename = NULL;
3448 view->command = NULL;
3450 view_set_datasource_none (view);
3452 view->growbuf_in_use = FALSE;
3453 /* leave the other growbuf fields uninitialized */
3455 view->hex_mode = FALSE;
3456 view->hexedit_mode = FALSE;
3457 view->hexview_in_text = FALSE;
3458 view->text_nroff_mode = FALSE;
3459 view->text_wrap_mode = FALSE;
3460 view->magic_mode = FALSE;
3462 view->hexedit_lownibble = FALSE;
3463 view->coord_cache = NULL;
3465 view->dpy_frame_size = is_panel ? 1 : 0;
3466 view->dpy_start = 0;
3467 view->dpy_text_column = 0;
3468 view->dpy_end= 0;
3469 view->hex_cursor = 0;
3470 view->cursor_col = 0;
3471 view->cursor_row = 0;
3472 view->change_list = NULL;
3474 /* {status,ruler,data}_area are left uninitialized */
3476 view->dirty = 0;
3477 view->dpy_bbar_dirty = TRUE;
3478 view->bytes_per_line = 1;
3480 view->search_start = 0;
3481 view->search_length = 0;
3482 view->search_exp = NULL;
3483 view->direction = 1; /* forward */
3484 view->last_search = 0; /* it's a function */
3486 view->want_to_quit = FALSE;
3487 view->marker = 0;
3488 for (i = 0; i < sizeof(view->marks) / sizeof(view->marks[0]); i++)
3489 view->marks[i] = 0;
3491 view->move_dir = 0;
3492 view->update_steps = 0;
3493 view->update_activate = 0;
3495 if (default_hex_mode)
3496 view_toggle_hex_mode (view);
3497 if (default_nroff_flag)
3498 view_toggle_nroff_mode (view);
3499 if (global_wrap_mode)
3500 view_toggle_wrap_mode (view);
3501 if (default_magic_flag)
3502 view_toggle_magic_mode (view);
3504 return view;