added a new parameter for completion flags to input_new
[midnight-commander.git] / src / view.c
blob8465301614840302a8983519bac82da669af28b6
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>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <unistd.h>
47 #include "global.h"
48 #include "tty.h"
49 #include "cmd.h" /* For view_other_cmd */
50 #include "dialog.h" /* Needed by widget.h */
51 #include "widget.h" /* Needed for buttonbar_new */
52 #include "color.h"
53 #include "mouse.h"
54 #include "help.h"
55 #include "key.h" /* For mi_getch() */
56 #include "layout.h"
57 #include "setup.h"
58 #include "wtools.h" /* For query_set_sel() */
59 #include "dir.h"
60 #include "panel.h" /* Needed for current_panel and other_panel */
61 #include "win.h"
62 #include "execute.h"
63 #include "main.h" /* slow_terminal */
64 #include "view.h"
65 #include "history.h"
66 #include "charsets.h"
67 #include "selcodepage.h"
69 /* Block size for reading files in parts */
70 #define VIEW_PAGE_SIZE ((size_t) 8192)
71 #define VIEW_COORD_CACHE_GRANUL 1024
73 typedef unsigned char byte;
75 /* Offset in bytes into a file */
76 typedef unsigned long offset_type;
77 #define INVALID_OFFSET ((offset_type) -1)
78 #define OFFSETTYPE_MAX (~((offset_type) 0))
79 #define OFFSETTYPE_PRIX "lX"
80 #define OFFSETTYPE_PRId "lu"
82 /* A width or height on the screen */
83 typedef unsigned int screen_dimen;
85 /* A cache entry for mapping offsets into line/column pairs and vice versa.
86 * cc_offset, cc_line, and cc_column are the 0-based values of the offset,
87 * line and column of that cache entry. cc_nroff_column is the column
88 * corresponding to cc_offset in nroff mode.
90 struct coord_cache_entry {
91 offset_type cc_offset;
92 offset_type cc_line;
93 offset_type cc_column;
94 offset_type cc_nroff_column;
97 /* A node for building a change list on change_list */
98 struct hexedit_change_node {
99 struct hexedit_change_node *next;
100 offset_type offset;
101 byte value;
104 /* data sources of the view */
105 enum view_ds {
106 DS_NONE, /* No data available */
107 DS_STDIO_PIPE, /* Data comes from a pipe using popen/pclose */
108 DS_VFS_PIPE, /* Data comes from a piped-in VFS file */
109 DS_FILE, /* Data comes from a VFS file */
110 DS_STRING /* Data comes from a string in memory */
113 struct area {
114 screen_dimen top, left;
115 screen_dimen height, width;
118 struct WView {
119 Widget widget;
121 char *filename; /* Name of the file */
122 char *command; /* Command used to pipe data in */
124 enum view_ds datasource; /* Where the displayed data comes from */
126 /* stdio pipe data source */
127 FILE *ds_stdio_pipe; /* Output of a shell command */
129 /* vfs pipe data source */
130 int ds_vfs_pipe; /* Non-seekable vfs file descriptor */
132 /* vfs file data source */
133 int ds_file_fd; /* File with random access */
134 off_t ds_file_filesize; /* Size of the file */
135 off_t ds_file_offset; /* Offset of the currently loaded data */
136 byte *ds_file_data; /* Currently loaded data */
137 size_t ds_file_datalen; /* Number of valid bytes in file_data */
138 size_t ds_file_datasize; /* Number of allocated bytes in file_data */
140 /* string data source */
141 byte *ds_string_data; /* The characters of the string */
142 size_t ds_string_len; /* The length of the string */
144 /* Growing buffers information */
145 gboolean growbuf_in_use; /* Use the growing buffers? */
146 byte **growbuf_blockptr; /* Pointer to the block pointers */
147 size_t growbuf_blocks; /* The number of blocks in *block_ptr */
148 size_t growbuf_lastindex; /* Number of bytes in the last page of the
149 growing buffer */
150 gboolean growbuf_finished; /* TRUE when all data has been read. */
152 /* Editor modes */
153 gboolean hex_mode; /* Hexview or Hexedit */
154 gboolean hexedit_mode; /* Hexedit */
155 gboolean hexview_in_text; /* Is the hexview cursor in the text area? */
156 gboolean text_nroff_mode; /* Nroff-style highlighting */
157 gboolean text_wrap_mode; /* Wrap text lines to fit them on the screen */
158 gboolean magic_mode; /* Preprocess the file using external programs */
160 /* Additional editor state */
161 gboolean hexedit_lownibble; /* Are we editing the last significant nibble? */
162 GArray *coord_cache; /* Cache for mapping offsets to cursor positions */
164 /* Display information */
165 screen_dimen dpy_frame_size;/* Size of the frame surrounding the real viewer */
166 offset_type dpy_start; /* Offset of the displayed data */
167 offset_type dpy_end; /* Offset after the displayed data */
168 offset_type dpy_text_column;/* Number of skipped columns in non-wrap
169 * text mode */
170 offset_type hex_cursor; /* Hexview cursor position in file */
171 screen_dimen cursor_col; /* Cursor column */
172 screen_dimen cursor_row; /* Cursor row */
173 struct hexedit_change_node *change_list; /* Linked list of changes */
174 struct area status_area; /* Where the status line is displayed */
175 struct area ruler_area; /* Where the ruler is displayed */
176 struct area data_area; /* Where the data is displayed */
178 int dirty; /* Number of skipped updates */
179 gboolean dpy_bbar_dirty; /* Does the button bar need to be updated? */
181 /* Mode variables */
182 int bytes_per_line; /* Number of bytes per line in hex mode */
184 /* Search variables */
185 offset_type search_start; /* First character to start searching from */
186 offset_type search_length; /* Length of found string or 0 if none was found */
187 char *search_exp; /* The search expression */
188 int direction; /* 1= forward; -1 backward */
189 void (*last_search)(WView *);
190 /* Pointer to the last search command */
191 gboolean want_to_quit; /* Prepare for cleanup ... */
193 /* Markers */
194 int marker; /* mark to use */
195 offset_type marks [10]; /* 10 marks: 0..9 */
197 int move_dir; /* return value from widget:
198 * 0 do nothing
199 * -1 view previous file
200 * 1 view next file
203 offset_type update_steps; /* The number of bytes between percent
204 * increments */
205 offset_type update_activate;/* Last point where we updated the status */
209 /* {{{ Global Variables }}} */
211 /* Maxlimit for skipping updates */
212 int max_dirt_limit = 10;
214 /* If set, show a ruler */
215 static enum ruler_type {
216 RULER_NONE,
217 RULER_TOP,
218 RULER_BOTTOM
219 } ruler = RULER_NONE;
221 /* Scrolling is done in pages or line increments */
222 int mouse_move_pages_viewer = 1;
224 /* wrap mode default */
225 int global_wrap_mode = 1;
227 int default_hex_mode = 0;
228 int default_magic_flag = 1;
229 int default_nroff_flag = 1;
230 int altered_hex_mode = 0;
231 int altered_magic_flag = 0;
232 int altered_nroff_flag = 0;
234 static const char hex_char[] = "0123456789ABCDEF";
236 int mcview_remember_file_position = FALSE;
238 /* {{{ Function Prototypes }}} */
240 /* Our widget callback */
241 static cb_ret_t view_callback (Widget *, widget_msg_t, int);
243 static int regexp_view_search (WView * view, char *pattern, char *string,
244 int match_type);
245 static void view_labels (WView * view);
247 static void view_init_growbuf (WView *);
248 static void view_place_cursor (WView *view);
249 static void display (WView *);
250 static void view_done (WView *);
252 /* {{{ Helper Functions }}} */
254 /* difference or zero */
255 static inline screen_dimen
256 dimen_doz (screen_dimen a, screen_dimen b)
258 return (a >= b) ? a - b : 0;
261 static inline screen_dimen
262 dimen_min (screen_dimen a, screen_dimen b)
264 return (a < b) ? a : b;
267 static inline offset_type
268 offset_doz (offset_type a, offset_type b)
270 return (a >= b) ? a - b : 0;
273 static inline offset_type
274 offset_rounddown (offset_type a, offset_type b)
276 assert (b != 0);
277 return a - a % b;
280 /* {{{ Simple Primitive Functions for WView }}} */
282 static inline gboolean
283 view_is_in_panel (WView *view)
285 return (view->dpy_frame_size != 0);
288 static void
289 view_compute_areas (WView *view)
291 struct area view_area;
292 screen_dimen height, rest, y;
294 /* The viewer is surrounded by a frame of size view->dpy_frame_size.
295 * Inside that frame, there are: The status line (at the top),
296 * the data area and an optional ruler, which is shown above or
297 * below the data area. */
299 view_area.top = view->dpy_frame_size;
300 view_area.left = view->dpy_frame_size;
301 view_area.height = dimen_doz(view->widget.lines, 2 * view->dpy_frame_size);
302 view_area.width = dimen_doz(view->widget.cols, 2 * view->dpy_frame_size);
304 /* Most coordinates of the areas equal those of the whole viewer */
305 view->status_area = view_area;
306 view->ruler_area = view_area;
307 view->data_area = view_area;
309 /* Compute the heights of the areas */
310 rest = view_area.height;
312 height = dimen_min(rest, 1);
313 view->status_area.height = height;
314 rest -= height;
316 height = dimen_min(rest, (ruler == RULER_NONE || view->hex_mode) ? 0 : 2);
317 view->ruler_area.height = height;
318 rest -= height;
320 view->data_area.height = rest;
322 /* Compute the position of the areas */
323 y = view_area.top;
325 view->status_area.top = y;
326 y += view->status_area.height;
328 if (ruler == RULER_TOP) {
329 view->ruler_area.top = y;
330 y += view->ruler_area.height;
333 view->data_area.top = y;
334 y += view->data_area.height;
336 if (ruler == RULER_BOTTOM) {
337 view->ruler_area.top = y;
338 y += view->ruler_area.height;
342 static void
343 view_hexedit_free_change_list (WView *view)
345 struct hexedit_change_node *curr, *next;
347 for (curr = view->change_list; curr != NULL; curr = next) {
348 next = curr->next;
349 g_free (curr);
351 view->change_list = NULL;
352 view->dirty++;
355 /* {{{ Growing buffer }}} */
357 static void
358 view_init_growbuf (WView *view)
360 view->growbuf_in_use = TRUE;
361 view->growbuf_blockptr = NULL;
362 view->growbuf_blocks = 0;
363 view->growbuf_lastindex = VIEW_PAGE_SIZE;
364 view->growbuf_finished = FALSE;
367 static void
368 view_growbuf_free (WView *view)
370 size_t i;
372 assert (view->growbuf_in_use);
374 for (i = 0; i < view->growbuf_blocks; i++)
375 g_free (view->growbuf_blockptr[i]);
376 g_free (view->growbuf_blockptr);
377 view->growbuf_blockptr = NULL;
378 view->growbuf_in_use = FALSE;
381 static offset_type
382 view_growbuf_filesize (WView *view)
384 assert(view->growbuf_in_use);
386 if (view->growbuf_blocks == 0)
387 return 0;
388 else
389 return ((offset_type) view->growbuf_blocks - 1) * VIEW_PAGE_SIZE
390 + view->growbuf_lastindex;
393 /* Copies the output from the pipe to the growing buffer, until either
394 * the end-of-pipe is reached or the interval [0..ofs) of the growing
395 * buffer is completely filled. */
396 static void
397 view_growbuf_read_until (WView *view, offset_type ofs)
399 ssize_t nread;
400 byte *p;
401 size_t bytesfree;
402 gboolean short_read;
404 assert (view->growbuf_in_use);
406 if (view->growbuf_finished)
407 return;
409 short_read = FALSE;
410 while (view_growbuf_filesize (view) < ofs || short_read) {
411 if (view->growbuf_lastindex == VIEW_PAGE_SIZE) {
412 /* Append a new block to the growing buffer */
413 byte *newblock = g_try_malloc (VIEW_PAGE_SIZE);
414 byte **newblocks = g_try_malloc (sizeof (*newblocks) * (view->growbuf_blocks + 1));
415 if (!newblock || !newblocks) {
416 g_free (newblock);
417 g_free (newblocks);
418 return;
420 memcpy (newblocks, view->growbuf_blockptr, sizeof (*newblocks) * view->growbuf_blocks);
421 g_free (view->growbuf_blockptr);
422 view->growbuf_blockptr = newblocks;
423 view->growbuf_blockptr[view->growbuf_blocks++] = newblock;
424 view->growbuf_lastindex = 0;
426 p = view->growbuf_blockptr[view->growbuf_blocks - 1] + view->growbuf_lastindex;
427 bytesfree = VIEW_PAGE_SIZE - view->growbuf_lastindex;
429 if (view->datasource == DS_STDIO_PIPE) {
430 nread = fread (p, 1, bytesfree, view->ds_stdio_pipe);
431 if (nread == 0) {
432 view->growbuf_finished = TRUE;
433 (void) pclose (view->ds_stdio_pipe);
434 display (view);
435 close_error_pipe (0, NULL);
436 view->ds_stdio_pipe = NULL;
437 return;
439 } else {
440 assert (view->datasource == DS_VFS_PIPE);
441 do {
442 nread = mc_read (view->ds_vfs_pipe, p, bytesfree);
443 } while (nread == -1 && errno == EINTR);
444 if (nread == -1 || nread == 0) {
445 view->growbuf_finished = TRUE;
446 (void) mc_close (view->ds_vfs_pipe);
447 view->ds_vfs_pipe = -1;
448 return;
451 short_read = ((size_t)nread < bytesfree);
452 view->growbuf_lastindex += nread;
456 static int
457 get_byte_growing_buffer (WView *view, offset_type byte_index)
459 offset_type pageno = byte_index / VIEW_PAGE_SIZE;
460 offset_type pageindex = byte_index % VIEW_PAGE_SIZE;
462 assert (view->growbuf_in_use);
464 if ((size_t) pageno != pageno)
465 return -1;
467 view_growbuf_read_until (view, byte_index + 1);
468 if (view->growbuf_blocks == 0)
469 return -1;
470 if (pageno < view->growbuf_blocks - 1)
471 return view->growbuf_blockptr[pageno][pageindex];
472 if (pageno == view->growbuf_blocks - 1 && pageindex < view->growbuf_lastindex)
473 return view->growbuf_blockptr[pageno][pageindex];
474 return -1;
477 /* {{{ Data sources }}} */
480 The data source provides the viewer with data from either a file, a
481 string or the output of a command. The get_byte() function can be
482 used to get the value of a byte at a specific offset. If the offset
483 is out of range, -1 is returned. The function get_byte_indexed(a,b)
484 returns the byte at the offset a+b, or -1 if a+b is out of range.
486 The view_set_byte() function has the effect that later calls to
487 get_byte() will return the specified byte for this offset. This
488 function is designed only for use by the hexedit component after
489 saving its changes. Inspect the source before you want to use it for
490 other purposes.
492 The view_get_filesize() function returns the current size of the
493 data source. If the growing buffer is used, this size may increase
494 later on. Use the view_may_still_grow() function when you want to
495 know if the size can change later.
498 static offset_type
499 view_get_filesize (WView *view)
501 switch (view->datasource) {
502 case DS_NONE:
503 return 0;
504 case DS_STDIO_PIPE:
505 case DS_VFS_PIPE:
506 return view_growbuf_filesize (view);
507 case DS_FILE:
508 return view->ds_file_filesize;
509 case DS_STRING:
510 return view->ds_string_len;
511 default:
512 assert(!"Unknown datasource type");
513 return 0;
517 static inline gboolean
518 view_may_still_grow (WView *view)
520 return (view->growbuf_in_use && !view->growbuf_finished);
523 /* returns TRUE if the idx lies in the half-open interval
524 * [offset; offset + size), FALSE otherwise.
526 static inline gboolean
527 already_loaded (offset_type offset, offset_type idx, size_t size)
529 return (offset <= idx && idx - offset < size);
532 static inline void
533 view_file_load_data (WView *view, offset_type byte_index)
535 offset_type blockoffset;
536 ssize_t res;
537 size_t bytes_read;
539 assert (view->datasource == DS_FILE);
541 if (already_loaded (view->ds_file_offset, byte_index, view->ds_file_datalen))
542 return;
544 if (byte_index >= view->ds_file_filesize)
545 return;
547 blockoffset = offset_rounddown (byte_index, view->ds_file_datasize);
548 if (mc_lseek (view->ds_file_fd, blockoffset, SEEK_SET) == -1)
549 goto error;
551 bytes_read = 0;
552 while (bytes_read < view->ds_file_datasize) {
553 res = mc_read (view->ds_file_fd, view->ds_file_data + bytes_read, view->ds_file_datasize - bytes_read);
554 if (res == -1)
555 goto error;
556 if (res == 0)
557 break;
558 bytes_read += (size_t) res;
560 view->ds_file_offset = blockoffset;
561 if (bytes_read > view->ds_file_filesize - view->ds_file_offset) {
562 /* the file has grown in the meantime -- stick to the old size */
563 view->ds_file_datalen = view->ds_file_filesize - view->ds_file_offset;
564 } else {
565 view->ds_file_datalen = bytes_read;
567 return;
569 error:
570 view->ds_file_datalen = 0;
573 static int
574 get_byte_none (WView *view, offset_type byte_index)
576 assert (view->datasource == DS_NONE);
577 (void) &view;
578 (void) byte_index;
579 return -1;
582 static inline int
583 get_byte_file (WView *view, offset_type byte_index)
585 assert (view->datasource == DS_FILE);
587 view_file_load_data (view, byte_index);
588 if (already_loaded(view->ds_file_offset, byte_index, view->ds_file_datalen))
589 return view->ds_file_data[byte_index - view->ds_file_offset];
590 return -1;
593 static int
594 get_byte_string (WView *view, offset_type byte_index)
596 assert (view->datasource == DS_STRING);
597 if (byte_index < view->ds_string_len)
598 return view->ds_string_data[byte_index];
599 return -1;
602 static inline int
603 get_byte (WView *view, offset_type offset)
605 switch (view->datasource) {
606 case DS_STDIO_PIPE:
607 case DS_VFS_PIPE:
608 return get_byte_growing_buffer (view, offset);
609 case DS_FILE:
610 return get_byte_file (view, offset);
611 case DS_STRING:
612 return get_byte_string (view, offset);
613 case DS_NONE:
614 return get_byte_none (view, offset);
616 assert(!"Unknown datasource type");
617 return -1;
620 static inline int
621 get_byte_indexed (WView *view, offset_type base, offset_type ofs)
623 if (base <= OFFSETTYPE_MAX - ofs)
624 return get_byte (view, base + ofs);
625 return -1;
628 static void
629 view_set_byte (WView *view, offset_type offset, byte b)
631 (void) &b;
632 assert (offset < view_get_filesize (view));
633 assert (view->datasource == DS_FILE);
634 view->ds_file_datalen = 0; /* just force reloading */
637 static void
638 view_set_datasource_none (WView *view)
640 view->datasource = DS_NONE;
643 static void
644 view_set_datasource_vfs_pipe (WView *view, int fd)
646 assert (fd != -1);
647 view->datasource = DS_VFS_PIPE;
648 view->ds_vfs_pipe = fd;
650 view_init_growbuf (view);
653 static void
654 view_set_datasource_stdio_pipe (WView *view, FILE *fp)
656 assert (fp != NULL);
657 view->datasource = DS_STDIO_PIPE;
658 view->ds_stdio_pipe = fp;
660 view_init_growbuf (view);
663 static void
664 view_set_datasource_string (WView *view, const char *s)
666 view->datasource = DS_STRING;
667 view->ds_string_data = (byte *) g_strdup (s);
668 view->ds_string_len = strlen (s);
671 static void
672 view_set_datasource_file (WView *view, int fd, const struct stat *st)
674 view->datasource = DS_FILE;
675 view->ds_file_fd = fd;
676 view->ds_file_filesize = st->st_size;
677 view->ds_file_offset = 0;
678 view->ds_file_data = g_malloc (4096);
679 view->ds_file_datalen = 0;
680 view->ds_file_datasize = 4096;
683 static void
684 view_close_datasource (WView *view)
686 switch (view->datasource) {
687 case DS_NONE:
688 break;
689 case DS_STDIO_PIPE:
690 if (view->ds_stdio_pipe != NULL) {
691 (void) pclose (view->ds_stdio_pipe);
692 display (view);
693 close_error_pipe (0, NULL);
694 view->ds_stdio_pipe = NULL;
696 view_growbuf_free (view);
697 break;
698 case DS_VFS_PIPE:
699 if (view->ds_vfs_pipe != -1) {
700 (void) mc_close (view->ds_vfs_pipe);
701 view->ds_vfs_pipe = -1;
703 view_growbuf_free (view);
704 break;
705 case DS_FILE:
706 (void) mc_close (view->ds_file_fd);
707 view->ds_file_fd = -1;
708 g_free (view->ds_file_data);
709 view->ds_file_data = NULL;
710 break;
711 case DS_STRING:
712 g_free (view->ds_string_data);
713 view->ds_string_data = NULL;
714 break;
715 default:
716 assert (!"Unknown datasource type");
718 view->datasource = DS_NONE;
721 /* {{{ The Coordinate Cache }}} */
724 This cache provides you with a fast lookup to map file offsets into
725 line/column pairs and vice versa. The interface to the mapping is
726 provided by the functions view_coord_to_offset() and
727 view_offset_to_coord().
729 The cache is implemented as a simple sorted array holding entries
730 that map some of the offsets to their line/column pair. Entries that
731 are not cached themselves are interpolated (exactly) from their
732 neighbor entries. The algorithm used for determining the line/column
733 for a specific offset needs to be kept synchronized with the one used
734 in display().
737 enum ccache_type {
738 CCACHE_OFFSET,
739 CCACHE_LINECOL
742 static inline gboolean
743 coord_cache_entry_less (const struct coord_cache_entry *a,
744 const struct coord_cache_entry *b, enum ccache_type crit,
745 gboolean nroff_mode)
747 if (crit == CCACHE_OFFSET)
748 return (a->cc_offset < b->cc_offset);
750 if (a->cc_line < b->cc_line)
751 return TRUE;
753 if (a->cc_line == b->cc_line) {
754 if (nroff_mode) {
755 return (a->cc_nroff_column < b->cc_nroff_column);
756 } else {
757 return (a->cc_column < b->cc_column);
760 return FALSE;
763 #ifdef MC_ENABLE_DEBUGGING_CODE
764 static void view_coord_to_offset (WView *, offset_type *, offset_type, offset_type);
765 static void view_offset_to_coord (WView *, offset_type *, offset_type *, offset_type);
767 static void
768 view_ccache_dump (WView *view)
770 FILE *f;
771 offset_type offset, line, column, nextline_offset, filesize;
772 guint i;
773 const struct coord_cache_entry *cache;
775 assert (view->coord_cache != NULL);
777 filesize = view_get_filesize (view);
778 cache = &(g_array_index (view->coord_cache, struct coord_cache_entry, 0));
780 f = fopen("mcview-ccache.out", "w");
781 if (f == NULL)
782 return;
783 (void)setvbuf(f, NULL, _IONBF, 0);
785 /* cache entries */
786 for (i = 0; i < view->coord_cache->len; i++) {
787 (void) fprintf (f,
788 "entry %8u "
789 "offset %8"OFFSETTYPE_PRId" "
790 "line %8"OFFSETTYPE_PRId" "
791 "column %8"OFFSETTYPE_PRId" "
792 "nroff_column %8"OFFSETTYPE_PRId"\n",
793 (unsigned int) i, cache[i].cc_offset, cache[i].cc_line,
794 cache[i].cc_column, cache[i].cc_nroff_column);
796 (void)fprintf (f, "\n");
798 /* offset -> line/column translation */
799 for (offset = 0; offset < filesize; offset++) {
800 view_offset_to_coord (view, &line, &column, offset);
801 (void)fprintf (f,
802 "offset %8"OFFSETTYPE_PRId" "
803 "line %8"OFFSETTYPE_PRId" "
804 "column %8"OFFSETTYPE_PRId"\n",
805 offset, line, column);
808 /* line/column -> offset translation */
809 for (line = 0; TRUE; line++) {
810 view_coord_to_offset (view, &nextline_offset, line + 1, 0);
811 (void)fprintf (f, "nextline_offset %8"OFFSETTYPE_PRId"\n",
812 nextline_offset);
814 for (column = 0; TRUE; column++) {
815 view_coord_to_offset (view, &offset, line, column);
816 if (offset >= nextline_offset)
817 break;
819 (void)fprintf (f, "line %8"OFFSETTYPE_PRId" column %8"OFFSETTYPE_PRId" offset %8"OFFSETTYPE_PRId"\n",
820 line, column, offset);
823 if (nextline_offset >= filesize - 1)
824 break;
827 (void)fclose (f);
829 #endif
831 static inline gboolean
832 is_nroff_sequence (WView *view, offset_type offset)
834 int c0, c1, c2;
836 /* The following commands are ordered to speed up the calculation. */
838 c1 = get_byte_indexed (view, offset, 1);
839 if (c1 == -1 || c1 != '\b')
840 return FALSE;
842 c0 = get_byte_indexed (view, offset, 0);
843 if (c0 == -1 || !is_printable(c0))
844 return FALSE;
846 c2 = get_byte_indexed (view, offset, 2);
847 if (c2 == -1 || !is_printable(c2))
848 return FALSE;
850 return (c0 == c2 || c0 == '_' || (c0 == '+' && c2 == 'o'));
853 /* Find and return the index of the last cache entry that is
854 * smaller than ''coord'', according to the criterion ''sort_by''. */
855 static inline guint
856 view_ccache_find (WView *view, const struct coord_cache_entry *cache,
857 const struct coord_cache_entry *coord, enum ccache_type sort_by)
859 guint base, i, limit;
861 limit = view->coord_cache->len;
862 assert (limit != 0);
864 base = 0;
865 while (limit > 1) {
866 i = base + limit / 2;
867 if (coord_cache_entry_less (coord, &cache[i], sort_by, view->text_nroff_mode)) {
868 /* continue the search in the lower half of the cache */
869 } else {
870 /* continue the search in the upper half of the cache */
871 base = i;
873 limit = (limit + 1) / 2;
875 return base;
878 /* Look up the missing components of ''coord'', which are given by
879 * ''lookup_what''. The function returns the smallest value that
880 * matches the existing components of ''coord''.
882 static void
883 view_ccache_lookup (WView *view, struct coord_cache_entry *coord,
884 enum ccache_type lookup_what)
886 guint i;
887 struct coord_cache_entry *cache, current, next, entry;
888 enum ccache_type sorter;
889 offset_type limit;
890 enum {
891 NROFF_START,
892 NROFF_BACKSPACE,
893 NROFF_CONTINUATION
894 } nroff_state;
896 if (!view->coord_cache) {
897 view->coord_cache = g_array_new (FALSE, FALSE, sizeof(struct coord_cache_entry));
898 current.cc_offset = 0;
899 current.cc_line = 0;
900 current.cc_column = 0;
901 current.cc_nroff_column = 0;
902 g_array_append_val (view->coord_cache, current);
905 sorter = (lookup_what == CCACHE_OFFSET) ? CCACHE_LINECOL : CCACHE_OFFSET;
907 retry:
908 /* find the two neighbor entries in the cache */
909 cache = &(g_array_index (view->coord_cache, struct coord_cache_entry, 0));
910 i = view_ccache_find (view, cache, coord, sorter);
911 /* now i points to the lower neighbor in the cache */
913 current = cache[i];
914 if (i + 1 < view->coord_cache->len)
915 limit = cache[i + 1].cc_offset;
916 else
917 limit = current.cc_offset + VIEW_COORD_CACHE_GRANUL;
919 entry = current;
920 nroff_state = NROFF_START;
921 for (; current.cc_offset < limit; current = next) {
922 int c, nextc;
924 if ((c = get_byte (view, current.cc_offset)) == -1)
925 break;
927 if (!coord_cache_entry_less (&current, coord, sorter, view->text_nroff_mode)) {
928 if (lookup_what == CCACHE_OFFSET
929 && view->text_nroff_mode
930 && nroff_state != NROFF_START) {
931 /* don't break here */
932 } else {
933 break;
937 /* Provide useful default values for ''next'' */
938 next.cc_offset = current.cc_offset + 1;
939 next.cc_line = current.cc_line;
940 next.cc_column = current.cc_column + 1;
941 next.cc_nroff_column = current.cc_nroff_column + 1;
943 /* and override some of them as necessary. */
944 if (c == '\r') {
945 nextc = get_byte_indexed(view, current.cc_offset, 1);
947 /* Ignore '\r' if it is followed by '\r' or '\n'. If it is
948 * followed by anything else, it is a Mac line ending and
949 * produces a line break.
951 if (nextc == '\r' || nextc == '\n') {
952 next.cc_column = current.cc_column;
953 next.cc_nroff_column = current.cc_nroff_column;
954 } else {
955 next.cc_line = current.cc_line + 1;
956 next.cc_column = 0;
957 next.cc_nroff_column = 0;
960 } else if (nroff_state == NROFF_BACKSPACE) {
961 next.cc_nroff_column = current.cc_nroff_column - 1;
963 } else if (c == '\t') {
964 next.cc_column = offset_rounddown (current.cc_column, 8) + 8;
965 next.cc_nroff_column =
966 offset_rounddown (current.cc_nroff_column, 8) + 8;
968 } else if (c == '\n') {
969 next.cc_line = current.cc_line + 1;
970 next.cc_column = 0;
971 next.cc_nroff_column = 0;
973 } else {
974 /* Use all default values from above */
977 switch (nroff_state) {
978 case NROFF_START:
979 case NROFF_CONTINUATION:
980 if (is_nroff_sequence (view, current.cc_offset))
981 nroff_state = NROFF_BACKSPACE;
982 else
983 nroff_state = NROFF_START;
984 break;
985 case NROFF_BACKSPACE:
986 nroff_state = NROFF_CONTINUATION;
987 break;
990 /* Cache entries must guarantee that for each i < j,
991 * line[i] <= line[j] and column[i] < column[j]. In the case of
992 * nroff sequences and '\r' characters, this is not guaranteed,
993 * so we cannot save them. */
994 if (nroff_state == NROFF_START && c != '\r')
995 entry = next;
998 if (i + 1 == view->coord_cache->len && entry.cc_offset != cache[i].cc_offset) {
999 g_array_append_val (view->coord_cache, entry);
1000 goto retry;
1003 if (lookup_what == CCACHE_OFFSET) {
1004 coord->cc_offset = current.cc_offset;
1005 } else {
1006 coord->cc_line = current.cc_line;
1007 coord->cc_column = current.cc_column;
1008 coord->cc_nroff_column = current.cc_nroff_column;
1012 static void
1013 view_coord_to_offset (WView *view, offset_type *ret_offset,
1014 offset_type line, offset_type column)
1016 struct coord_cache_entry coord;
1018 coord.cc_line = line;
1019 coord.cc_column = column;
1020 coord.cc_nroff_column = column;
1021 view_ccache_lookup (view, &coord, CCACHE_OFFSET);
1022 *ret_offset = coord.cc_offset;
1025 static void
1026 view_offset_to_coord (WView *view, offset_type *ret_line,
1027 offset_type *ret_column, offset_type offset)
1029 struct coord_cache_entry coord;
1031 coord.cc_offset = offset;
1032 view_ccache_lookup (view, &coord, CCACHE_LINECOL);
1033 *ret_line = coord.cc_line;
1034 *ret_column = (view->text_nroff_mode)
1035 ? coord.cc_nroff_column
1036 : coord.cc_column;
1039 /* {{{ Cursor Movement }}} */
1042 The following variables have to do with the current position and are
1043 updated by the cursor movement functions.
1045 In hex view and wrapped text view mode, dpy_start marks the offset of
1046 the top-left corner on the screen, in non-wrapping text mode it is
1047 the beginning of the current line. In hex mode, hex_cursor is the
1048 offset of the cursor. In non-wrapping text mode, dpy_text_column is
1049 the number of columns that are hidden on the left side on the screen.
1051 In hex mode, dpy_start is updated by the view_fix_cursor_position()
1052 function in order to keep the other functions simple. In
1053 non-wrapping text mode dpy_start and dpy_text_column are normalized
1054 such that dpy_text_column < view_get_datacolumns().
1057 /* prototypes for functions used by view_moveto_bottom() */
1058 static void view_move_up (WView *, offset_type);
1059 static void view_moveto_bol (WView *);
1061 static void
1062 view_scroll_to_cursor (WView *view)
1064 if (view->hex_mode) {
1065 const offset_type bytes = view->bytes_per_line;
1066 const offset_type displaysize = view->data_area.height * bytes;
1067 const offset_type cursor = view->hex_cursor;
1068 offset_type topleft = view->dpy_start;
1070 if (topleft + displaysize <= cursor)
1071 topleft = offset_rounddown (cursor, bytes)
1072 - (displaysize - bytes);
1073 if (cursor < topleft)
1074 topleft = offset_rounddown (cursor, bytes);
1075 view->dpy_start = topleft;
1076 } else if (view->text_wrap_mode) {
1077 offset_type line, col, columns;
1079 columns = view->data_area.width;
1080 view_offset_to_coord (view, &line, &col, view->dpy_start + view->dpy_text_column);
1081 if (columns != 0)
1082 col = offset_rounddown (col, columns);
1083 view_coord_to_offset (view, &(view->dpy_start), line, col);
1084 view->dpy_text_column = 0;
1085 } else {
1086 /* nothing to do */
1090 static void
1091 view_movement_fixups (WView *view, gboolean reset_search)
1093 view_scroll_to_cursor (view);
1094 if (reset_search) {
1095 view->search_start = view->dpy_start;
1096 view->search_length = 0;
1098 view->dirty++;
1101 static void
1102 view_moveto_top (WView *view)
1104 view->dpy_start = 0;
1105 view->hex_cursor = 0;
1106 view->dpy_text_column = 0;
1107 view_movement_fixups (view, TRUE);
1110 static void
1111 view_moveto_bottom (WView *view)
1113 offset_type datalines, lines_up, filesize, last_offset;
1115 if (view->growbuf_in_use)
1116 view_growbuf_read_until (view, OFFSETTYPE_MAX);
1118 filesize = view_get_filesize (view);
1119 last_offset = offset_doz(filesize, 1);
1120 datalines = view->data_area.height;
1121 lines_up = offset_doz(datalines, 1);
1123 if (view->hex_mode) {
1124 view->hex_cursor = filesize;
1125 view_move_up (view, lines_up);
1126 view->hex_cursor = last_offset;
1127 } else {
1128 view->dpy_start = last_offset;
1129 view_moveto_bol (view);
1130 view_move_up (view, lines_up);
1132 view_movement_fixups (view, TRUE);
1135 static void
1136 view_moveto_bol (WView *view)
1138 if (view->hex_mode) {
1139 view->hex_cursor -= view->hex_cursor % view->bytes_per_line;
1140 } else if (view->text_wrap_mode) {
1141 /* do nothing */
1142 } else {
1143 offset_type line, column;
1144 view_offset_to_coord (view, &line, &column, view->dpy_start);
1145 view_coord_to_offset (view, &(view->dpy_start), line, 0);
1146 view->dpy_text_column = 0;
1148 view_movement_fixups (view, TRUE);
1151 static void
1152 view_moveto_eol (WView *view)
1154 if (view->hex_mode) {
1155 offset_type filesize, bol;
1157 bol = offset_rounddown (view->hex_cursor, view->bytes_per_line);
1158 if (get_byte_indexed (view, bol, view->bytes_per_line - 1) != -1) {
1159 view->hex_cursor = bol + view->bytes_per_line - 1;
1160 } else {
1161 filesize = view_get_filesize (view);
1162 view->hex_cursor = offset_doz(filesize, 1);
1164 } else if (view->text_wrap_mode) {
1165 /* nothing to do */
1166 } else {
1167 offset_type line, col;
1169 view_offset_to_coord (view, &line, &col, view->dpy_start);
1170 view_coord_to_offset (view, &(view->dpy_start), line, OFFSETTYPE_MAX);
1172 view_movement_fixups (view, FALSE);
1175 static void
1176 view_moveto_offset (WView *view, offset_type offset)
1178 if (view->hex_mode) {
1179 view->hex_cursor = offset;
1180 view->dpy_start = offset - offset % view->bytes_per_line;
1181 } else {
1182 view->dpy_start = offset;
1184 view_movement_fixups (view, TRUE);
1187 static void
1188 view_moveto (WView *view, offset_type line, offset_type col)
1190 offset_type offset;
1192 view_coord_to_offset (view, &offset, line, col);
1193 view_moveto_offset (view, offset);
1196 static void
1197 view_move_up (WView *view, offset_type lines)
1199 if (view->hex_mode) {
1200 offset_type bytes = lines * view->bytes_per_line;
1201 if (view->hex_cursor >= bytes) {
1202 view->hex_cursor -= bytes;
1203 if (view->hex_cursor < view->dpy_start)
1204 view->dpy_start = offset_doz (view->dpy_start, bytes);
1205 } else {
1206 view->hex_cursor %= view->bytes_per_line;
1208 } else if (view->text_wrap_mode) {
1209 const screen_dimen width = view->data_area.width;
1210 offset_type i, col, line, linestart;
1212 for (i = 0; i < lines; i++) {
1213 view_offset_to_coord (view, &line, &col, view->dpy_start);
1214 if (col >= width) {
1215 col -= width;
1216 } else if (line >= 1) {
1217 view_coord_to_offset (view, &linestart, line, 0);
1218 view_offset_to_coord (view, &line, &col, linestart - 1);
1220 /* if the only thing that would be displayed were a
1221 * single newline character, advance to the previous
1222 * part of the line. */
1223 if (col > 0 && col % width == 0)
1224 col -= width;
1225 else
1226 col -= col % width;
1227 } else {
1228 /* nothing to do */
1230 view_coord_to_offset (view, &(view->dpy_start), line, col);
1232 } else {
1233 offset_type line, column;
1235 view_offset_to_coord (view, &line, &column, view->dpy_start);
1236 line = offset_doz(line, lines);
1237 view_coord_to_offset (view, &(view->dpy_start), line, column);
1239 view_movement_fixups (view, (lines != 1));
1242 static void
1243 view_move_down (WView *view, offset_type lines)
1245 if (view->hex_mode) {
1246 offset_type i, limit, last_byte;
1248 last_byte = view_get_filesize (view);
1249 if (last_byte >= (offset_type) view->bytes_per_line)
1250 limit = last_byte - view->bytes_per_line;
1251 else
1252 limit = 0;
1253 for (i = 0; i < lines && view->hex_cursor < limit; i++) {
1254 view->hex_cursor += view->bytes_per_line;
1255 if (lines != 1)
1256 view->dpy_start += view->bytes_per_line;
1259 } else if (view->dpy_end == view_get_filesize (view)) {
1260 /* don't move further down. There's nothing more to see. */
1262 } else if (view->text_wrap_mode) {
1263 offset_type line, col, i;
1265 for (i = 0; i < lines; i++) {
1266 offset_type new_offset, chk_line, chk_col;
1268 view_offset_to_coord (view, &line, &col, view->dpy_start);
1269 col += view->data_area.width;
1270 view_coord_to_offset (view, &new_offset, line, col);
1272 /* skip to the next line if the only thing that would be
1273 * displayed is the newline character. */
1274 view_offset_to_coord (view, &chk_line, &chk_col, new_offset);
1275 if (chk_line == line && chk_col == col
1276 && get_byte (view, new_offset) == '\n')
1277 new_offset++;
1279 view->dpy_start = new_offset;
1282 } else {
1283 offset_type line, col;
1285 view_offset_to_coord (view, &line, &col, view->dpy_start);
1286 line += lines;
1287 view_coord_to_offset (view, &(view->dpy_start), line, col);
1289 view_movement_fixups (view, (lines != 1));
1292 static void
1293 view_move_left (WView *view, offset_type columns)
1295 if (view->hex_mode) {
1296 assert (columns == 1);
1297 if (view->hexview_in_text || !view->hexedit_lownibble) {
1298 if (view->hex_cursor > 0)
1299 view->hex_cursor--;
1301 if (!view->hexview_in_text)
1302 view->hexedit_lownibble = !view->hexedit_lownibble;
1303 } else if (view->text_wrap_mode) {
1304 /* nothing to do */
1305 } else {
1306 if (view->dpy_text_column >= columns)
1307 view->dpy_text_column -= columns;
1308 else
1309 view->dpy_text_column = 0;
1311 view_movement_fixups (view, FALSE);
1314 static void
1315 view_move_right (WView *view, offset_type columns)
1317 if (view->hex_mode) {
1318 assert (columns == 1);
1319 if (view->hexview_in_text || view->hexedit_lownibble) {
1320 if (get_byte_indexed (view, view->hex_cursor, 1) != -1)
1321 view->hex_cursor++;
1323 if (!view->hexview_in_text)
1324 view->hexedit_lownibble = !view->hexedit_lownibble;
1325 } else if (view->text_wrap_mode) {
1326 /* nothing to do */
1327 } else {
1328 view->dpy_text_column += columns;
1330 view_movement_fixups (view, FALSE);
1333 /* {{{ Toggling of viewer modes }}} */
1335 static void
1336 view_toggle_hex_mode (WView *view)
1338 view->hex_mode = !view->hex_mode;
1340 if (view->hex_mode) {
1341 view->hex_cursor = view->dpy_start;
1342 view->dpy_start =
1343 offset_rounddown (view->dpy_start, view->bytes_per_line);
1344 view->widget.options |= W_WANT_CURSOR;
1345 } else {
1346 view->dpy_start = view->hex_cursor;
1347 view_moveto_bol (view);
1348 view->widget.options &= ~W_WANT_CURSOR;
1350 altered_hex_mode = 1;
1351 view->dpy_bbar_dirty = TRUE;
1352 view->dirty++;
1355 static void
1356 view_toggle_hexedit_mode (WView *view)
1358 view->hexedit_mode = !view->hexedit_mode;
1359 view->dpy_bbar_dirty = TRUE;
1360 view->dirty++;
1363 static void
1364 view_toggle_wrap_mode (WView *view)
1366 view->text_wrap_mode = !view->text_wrap_mode;
1367 if (view->text_wrap_mode) {
1368 view_scroll_to_cursor (view);
1369 } else {
1370 offset_type line;
1372 view_offset_to_coord (view, &line, &(view->dpy_text_column), view->dpy_start);
1373 view_coord_to_offset (view, &(view->dpy_start), line, 0);
1375 view->dpy_bbar_dirty = TRUE;
1376 view->dirty++;
1379 static void
1380 view_toggle_nroff_mode (WView *view)
1382 view->text_nroff_mode = !view->text_nroff_mode;
1383 altered_nroff_flag = 1;
1384 view->dpy_bbar_dirty = TRUE;
1385 view->dirty++;
1388 static void
1389 view_toggle_magic_mode (WView *view)
1391 char *filename, *command;
1393 altered_magic_flag = 1;
1394 view->magic_mode = !view->magic_mode;
1395 filename = g_strdup (view->filename);
1396 command = g_strdup (view->command);
1398 view_done (view);
1399 view_load (view, command, filename, 0);
1400 g_free (filename);
1401 g_free (command);
1402 view->dpy_bbar_dirty = TRUE;
1403 view->dirty++;
1406 /* {{{ Miscellaneous functions }}} */
1408 static void
1409 view_done (WView *view)
1411 /* Save current file position */
1412 if (mcview_remember_file_position && view->filename != NULL) {
1413 char *canon_fname;
1414 offset_type line, col;
1416 canon_fname = vfs_canon (view->filename);
1417 view_offset_to_coord (view, &line, &col, view->dpy_start);
1418 save_file_position (canon_fname, line + 1, col);
1419 g_free (canon_fname);
1422 /* Write back the global viewer mode */
1423 default_hex_mode = view->hex_mode;
1424 default_nroff_flag = view->text_nroff_mode;
1425 default_magic_flag = view->magic_mode;
1426 global_wrap_mode = view->text_wrap_mode;
1428 /* Free memory used by the viewer */
1430 /* view->widget needs no destructor */
1432 g_free (view->filename), view->filename = NULL;
1433 g_free (view->command), view->command = NULL;
1435 view_close_datasource (view);
1436 /* the growing buffer is freed with the datasource */
1438 if (view->coord_cache) {
1439 g_array_free (view->coord_cache, TRUE), view->coord_cache = NULL;
1442 view_hexedit_free_change_list (view);
1443 /* FIXME: what about view->search_exp? */
1446 static void
1447 view_show_error (WView *view, const char *msg)
1449 view_close_datasource (view);
1450 if (view_is_in_panel (view)) {
1451 view_set_datasource_string (view, msg);
1452 } else {
1453 message (1, MSG_ERROR, "%s", msg);
1457 static gboolean
1458 view_load_command_output (WView *view, const char *command)
1460 FILE *fp;
1462 view_close_datasource (view);
1464 open_error_pipe ();
1465 if ((fp = popen (command, "r")) == NULL) {
1466 /* Avoid two messages. Message from stderr has priority. */
1467 display (view);
1468 if (!close_error_pipe (view_is_in_panel (view) ? -1 : 1, NULL))
1469 view_show_error (view, _(" Cannot spawn child process "));
1470 return FALSE;
1473 /* First, check if filter produced any output */
1474 view_set_datasource_stdio_pipe (view, fp);
1475 if (get_byte (view, 0) == -1) {
1476 view_close_datasource (view);
1478 /* Avoid two messages. Message from stderr has priority. */
1479 display (view);
1480 if (!close_error_pipe (view_is_in_panel (view) ? -1 : 1, NULL))
1481 view_show_error (view, _("Empty output from child filter"));
1482 return FALSE;
1484 return TRUE;
1487 gboolean
1488 view_load (WView *view, const char *command, const char *file,
1489 int start_line)
1491 int i, type;
1492 int fd = -1;
1493 char tmp[BUF_MEDIUM];
1494 struct stat st;
1495 gboolean retval = FALSE;
1497 assert (view->bytes_per_line != 0);
1498 view_done (view);
1500 /* Set up the state */
1501 view_set_datasource_none (view);
1502 view->filename = g_strdup (file);
1503 view->command = 0;
1505 /* Clear the markers */
1506 view->marker = 0;
1507 for (i = 0; i < 10; i++)
1508 view->marks[i] = 0;
1510 if (!view_is_in_panel (view)) {
1511 view->dpy_text_column = 0;
1514 if (command && (view->magic_mode || file == NULL || file[0] == '\0')) {
1515 retval = view_load_command_output (view, command);
1516 } else if (file != NULL && file[0] != '\0') {
1517 /* Open the file */
1518 if ((fd = mc_open (file, O_RDONLY | O_NONBLOCK)) == -1) {
1519 g_snprintf (tmp, sizeof (tmp), _(" Cannot open \"%s\"\n %s "),
1520 file, unix_error_string (errno));
1521 view_show_error (view, tmp);
1522 goto finish;
1525 /* Make sure we are working with a regular file */
1526 if (mc_fstat (fd, &st) == -1) {
1527 mc_close (fd);
1528 g_snprintf (tmp, sizeof (tmp), _(" Cannot stat \"%s\"\n %s "),
1529 file, unix_error_string (errno));
1530 view_show_error (view, tmp);
1531 goto finish;
1534 if (!S_ISREG (st.st_mode)) {
1535 mc_close (fd);
1536 view_show_error (view, _(" Cannot view: not a regular file "));
1537 goto finish;
1540 if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1) {
1541 /* Must be one of those nice files that grow (/proc) */
1542 view_set_datasource_vfs_pipe (view, fd);
1543 } else {
1544 type = get_compression_type (fd);
1546 if (view->magic_mode && (type != COMPRESSION_NONE)) {
1547 g_free (view->filename);
1548 view->filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
1550 view_set_datasource_file (view, fd, &st);
1552 retval = TRUE;
1555 finish:
1556 view->command = g_strdup (command);
1557 view->dpy_start = 0;
1558 view->search_start = 0;
1559 view->search_length = 0;
1560 view->dpy_text_column = 0;
1561 view->last_search = 0; /* Start a new search */
1563 assert (view->bytes_per_line != 0);
1564 if (mcview_remember_file_position && file != NULL && start_line == 0) {
1565 long line, col;
1566 char *canon_fname;
1568 canon_fname = vfs_canon (file);
1569 load_file_position (file, &line, &col);
1570 g_free (canon_fname);
1571 view_moveto (view, offset_doz(line, 1), col);
1572 } else if (start_line > 0) {
1573 view_moveto (view, start_line - 1, 0);
1576 view->hexedit_lownibble = FALSE;
1577 view->hexview_in_text = FALSE;
1578 view->change_list = NULL;
1580 return retval;
1583 /* {{{ Display management }}} */
1585 static void
1586 view_update_bytes_per_line (WView *view)
1588 const screen_dimen cols = view->data_area.width;
1589 int bytes;
1591 if (cols < 8 + 17)
1592 bytes = 4;
1593 else
1594 bytes = 4 * ((cols - 8) / ((cols < 80) ? 17 : 18));
1595 assert(bytes != 0);
1597 view->bytes_per_line = bytes;
1598 view->dirty = max_dirt_limit + 1; /* To force refresh */
1601 static void
1602 view_percent (WView *view, offset_type p)
1604 const screen_dimen top = view->status_area.top;
1605 const screen_dimen right = view->status_area.left + view->status_area.width;
1606 const screen_dimen height = view->status_area.height;
1607 int percent;
1608 offset_type filesize;
1610 if (height < 1 || right < 4)
1611 return;
1612 if (view_may_still_grow (view))
1613 return;
1614 filesize = view_get_filesize (view);
1616 if (filesize == 0 || view->dpy_end == filesize)
1617 percent = 100;
1618 else if (p > (INT_MAX / 100))
1619 percent = p / (filesize / 100);
1620 else
1621 percent = p * 100 / filesize;
1623 widget_move (view, top, right - 4);
1624 tty_printf ("%3d%%", percent);
1627 static void
1628 view_display_status (WView *view)
1630 const screen_dimen top = view->status_area.top;
1631 const screen_dimen left = view->status_area.left;
1632 const screen_dimen width = view->status_area.width;
1633 const screen_dimen height = view->status_area.height;
1634 const char *file_label, *file_name;
1635 screen_dimen file_label_width;
1636 int i;
1638 if (height < 1)
1639 return;
1641 tty_setcolor (SELECTED_COLOR);
1642 widget_move (view, top, left);
1643 hline (' ', width);
1645 file_label = _("File: %s");
1646 file_label_width = strlen (file_label) - 2;
1647 file_name = view->filename ? view->filename
1648 : view->command ? view->command
1649 : "";
1651 if (width < file_label_width + 6)
1652 addstr ((char *) name_trunc (file_name, width));
1653 else {
1654 i = (width > 22 ? 22 : width) - file_label_width;
1655 tty_printf (file_label, name_trunc (file_name, i));
1656 if (width > 46) {
1657 widget_move (view, top, left + 24);
1658 /* FIXME: the format strings need to be changed when offset_type changes */
1659 if (view->hex_mode)
1660 tty_printf (_("Offset 0x%08lx"), (unsigned long) view->hex_cursor);
1661 else {
1662 offset_type line, col;
1663 view_offset_to_coord (view, &line, &col, view->dpy_start);
1664 tty_printf (_("Line %lu Col %lu"),
1665 (unsigned long) line + 1,
1666 (unsigned long) (view->text_wrap_mode ? col : view->dpy_text_column));
1669 if (width > 62) {
1670 offset_type filesize;
1671 filesize = view_get_filesize (view);
1672 widget_move (view, top, left + 43);
1673 if (!view_may_still_grow (view)) {
1674 tty_printf (_("%s bytes"), size_trunc (filesize));
1675 } else {
1676 tty_printf (_(">= %s bytes"), size_trunc (filesize));
1679 if (width > 26) {
1680 view_percent (view, view->hex_mode
1681 ? view->hex_cursor
1682 : view->dpy_end);
1685 tty_setcolor (SELECTED_COLOR);
1688 static inline void
1689 view_display_clean (WView *view)
1691 tty_setcolor (NORMAL_COLOR);
1692 widget_erase ((Widget *) view);
1693 if (view->dpy_frame_size != 0) {
1694 draw_double_box (view->widget.parent, view->widget.y,
1695 view->widget.x, view->widget.lines,
1696 view->widget.cols);
1700 typedef enum {
1701 MARK_NORMAL,
1702 MARK_SELECTED,
1703 MARK_CURSOR,
1704 MARK_CHANGED
1705 } mark_t;
1707 static inline int
1708 view_count_backspaces (WView *view, off_t offset)
1710 int backspaces = 0;
1711 while (offset >= 2 * backspaces
1712 && get_byte (view, offset - 2 * backspaces) == '\b')
1713 backspaces++;
1714 return backspaces;
1717 static void
1718 view_display_ruler (WView *view)
1720 static const char ruler_chars[] = "|----*----";
1721 const screen_dimen top = view->ruler_area.top;
1722 const screen_dimen left = view->ruler_area.left;
1723 const screen_dimen width = view->ruler_area.width;
1724 const screen_dimen height = view->ruler_area.height;
1725 const screen_dimen line_row = (ruler == RULER_TOP) ? 0 : 1;
1726 const screen_dimen nums_row = (ruler == RULER_TOP) ? 1 : 0;
1728 char r_buff[10];
1729 offset_type cl;
1730 screen_dimen c;
1732 if (ruler == RULER_NONE || height < 1)
1733 return;
1735 tty_setcolor (MARKED_COLOR);
1736 for (c = 0; c < width; c++) {
1737 cl = view->dpy_text_column + c;
1738 if (line_row < height) {
1739 widget_move (view, top + line_row, left + c);
1740 tty_print_char (ruler_chars[cl % 10]);
1743 if ((cl != 0) && (cl % 10) == 0) {
1744 g_snprintf (r_buff, sizeof (r_buff), "%"OFFSETTYPE_PRId, cl);
1745 if (nums_row < height) {
1746 widget_move (view, top + nums_row, left + c - 1);
1747 tty_print_string (r_buff);
1751 attrset (NORMAL_COLOR);
1754 static void
1755 view_display_hex (WView *view)
1757 const screen_dimen top = view->data_area.top;
1758 const screen_dimen left = view->data_area.left;
1759 const screen_dimen height = view->data_area.height;
1760 const screen_dimen width = view->data_area.width;
1761 const int ngroups = view->bytes_per_line / 4;
1762 const screen_dimen text_start =
1763 8 + 13 * ngroups + ((width < 80) ? 0 : (ngroups - 1 + 1));
1764 /* 8 characters are used for the file offset, and every hex group
1765 * takes 13 characters. On ``big'' screens, the groups are separated
1766 * by an extra vertical line, and there is an extra space before the
1767 * text column.
1770 screen_dimen row, col;
1771 offset_type from;
1772 int c;
1773 mark_t boldflag = MARK_NORMAL;
1774 struct hexedit_change_node *curr = view->change_list;
1775 size_t i;
1777 char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */
1778 int bytes; /* Number of bytes already printed on the line */
1780 view_display_clean (view);
1782 /* Find the first displayable changed byte */
1783 from = view->dpy_start;
1784 while (curr && (curr->offset < from)) {
1785 curr = curr->next;
1788 for (row = 0; get_byte (view, from) != -1 && row < height; row++) {
1789 col = 0;
1791 /* Print the hex offset */
1792 g_snprintf (hex_buff, sizeof (hex_buff), "%08"OFFSETTYPE_PRIX" ", from);
1793 widget_move (view, top + row, left);
1794 tty_setcolor (MARKED_COLOR);
1795 for (i = 0; col < width && hex_buff[i] != '\0'; i++) {
1796 tty_print_char(hex_buff[i]);
1797 col += 1;
1799 tty_setcolor (NORMAL_COLOR);
1801 for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++) {
1803 if ((c = get_byte (view, from)) == -1)
1804 break;
1806 /* Save the cursor position for view_place_cursor() */
1807 if (from == view->hex_cursor && !view->hexview_in_text) {
1808 view->cursor_row = row;
1809 view->cursor_col = col;
1812 /* Determine the state of the current byte */
1813 boldflag =
1814 (from == view->hex_cursor) ? MARK_CURSOR
1815 : (curr != NULL && from == curr->offset) ? MARK_CHANGED
1816 : (view->search_start <= from &&
1817 from < view->search_start + view->search_length
1818 ) ? MARK_SELECTED
1819 : MARK_NORMAL;
1821 /* Determine the value of the current byte */
1822 if (curr != NULL && from == curr->offset) {
1823 c = curr->value;
1824 curr = curr->next;
1827 /* Select the color for the hex number */
1828 tty_setcolor (
1829 boldflag == MARK_NORMAL ? NORMAL_COLOR :
1830 boldflag == MARK_SELECTED ? MARKED_COLOR :
1831 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
1832 /* boldflag == MARK_CURSOR */
1833 view->hexview_in_text ? MARKED_SELECTED_COLOR :
1834 VIEW_UNDERLINED_COLOR);
1836 /* Print the hex number */
1837 widget_move (view, top + row, left + col);
1838 if (col < width) {
1839 tty_print_char (hex_char[c / 16]);
1840 col += 1;
1842 if (col < width) {
1843 tty_print_char (hex_char[c % 16]);
1844 col += 1;
1847 /* Print the separator */
1848 tty_setcolor (NORMAL_COLOR);
1849 if (bytes != view->bytes_per_line - 1) {
1850 if (col < width) {
1851 tty_print_char (' ');
1852 col += 1;
1855 /* After every four bytes, print a group separator */
1856 if (bytes % 4 == 3) {
1857 if (view->data_area.width >= 80 && col < width) {
1858 tty_print_one_vline ();
1859 col += 1;
1861 if (col < width) {
1862 tty_print_char (' ');
1863 col += 1;
1868 /* Select the color for the character; this differs from the
1869 * hex color when boldflag == MARK_CURSOR */
1870 tty_setcolor (
1871 boldflag == MARK_NORMAL ? NORMAL_COLOR :
1872 boldflag == MARK_SELECTED ? MARKED_COLOR :
1873 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
1874 /* boldflag == MARK_CURSOR */
1875 view->hexview_in_text ? VIEW_UNDERLINED_COLOR :
1876 MARKED_SELECTED_COLOR);
1878 c = convert_to_display_c (c);
1879 if (!is_printable (c))
1880 c = '.';
1882 /* Print corresponding character on the text side */
1883 if (text_start + bytes < width) {
1884 widget_move (view, top + row, left + text_start + bytes);
1885 tty_print_char (c);
1888 /* Save the cursor position for view_place_cursor() */
1889 if (from == view->hex_cursor && view->hexview_in_text) {
1890 view->cursor_row = row;
1891 view->cursor_col = text_start + bytes;
1896 /* Be polite to the other functions */
1897 tty_setcolor (NORMAL_COLOR);
1899 view_place_cursor (view);
1900 view->dpy_end = from;
1903 static void
1904 view_display_text (WView * view)
1906 const screen_dimen left = view->data_area.left;
1907 const screen_dimen top = view->data_area.top;
1908 const screen_dimen width = view->data_area.width;
1909 const screen_dimen height = view->data_area.height;
1910 screen_dimen row, col;
1911 offset_type from;
1912 int c;
1913 struct hexedit_change_node *curr = view->change_list;
1915 view_display_clean (view);
1916 view_display_ruler (view);
1918 /* Find the first displayable changed byte */
1919 from = view->dpy_start;
1920 while (curr && (curr->offset < from)) {
1921 curr = curr->next;
1924 tty_setcolor (NORMAL_COLOR);
1925 for (row = 0, col = 0; row < height && (c = get_byte (view, from)) != -1; from++) {
1927 if (view->text_nroff_mode && c == '\b') {
1928 int c_prev;
1929 int c_next;
1931 if ((c_next = get_byte_indexed (view, from, 1)) != -1
1932 && is_printable (c_next)
1933 && from >= 1
1934 && (c_prev = get_byte (view, from - 1)) != -1
1935 && is_printable (c_prev)
1936 && (c_prev == c_next || c_prev == '_'
1937 || (c_prev == '+' && c_next == 'o'))) {
1938 if (col == 0) {
1939 if (row == 0) {
1940 /* We're inside an nroff character sequence at the
1941 * beginning of the screen -- just skip the
1942 * backspace and continue with the next character. */
1943 continue;
1945 row--;
1946 col = width;
1948 col--;
1949 if (c_prev == '_' && (c_next != '_' || view_count_backspaces (view, from) == 1))
1950 tty_setcolor (VIEW_UNDERLINED_COLOR);
1951 else
1952 tty_setcolor (MARKED_COLOR);
1953 continue;
1957 if ((c == '\n') || (col >= width && view->text_wrap_mode)) {
1958 col = 0;
1959 row++;
1960 if (c == '\n' || row >= height)
1961 continue;
1964 if (c == '\r') {
1965 c = get_byte_indexed(view, from, 1);
1966 if (c == '\r' || c == '\n')
1967 continue;
1968 col = 0;
1969 row++;
1970 continue;
1973 if (c == '\t') {
1974 offset_type line, column;
1975 view_offset_to_coord (view, &line, &column, from);
1976 col += (8 - column % 8);
1977 if (view->text_wrap_mode && col >= width && width != 0) {
1978 row += col / width;
1979 col %= width;
1981 continue;
1984 if (view->search_start <= from
1985 && from < view->search_start + view->search_length) {
1986 tty_setcolor (SELECTED_COLOR);
1989 if (col >= view->dpy_text_column
1990 && col - view->dpy_text_column < width) {
1991 widget_move (view, top + row, left + (col - view->dpy_text_column));
1992 c = convert_to_display_c (c);
1993 if (!is_printable (c))
1994 c = '.';
1995 tty_print_char (c);
1997 col++;
1998 tty_setcolor (NORMAL_COLOR);
2000 view->dpy_end = from;
2003 /* Displays as much data from view->dpy_start as fits on the screen */
2004 static void
2005 display (WView *view)
2007 view_compute_areas (view);
2008 if (view->hex_mode) {
2009 view_display_hex (view);
2010 } else {
2011 view_display_text (view);
2013 view_display_status (view);
2016 static void
2017 view_place_cursor (WView *view)
2019 const screen_dimen top = view->data_area.top;
2020 const screen_dimen left = view->data_area.left;
2021 screen_dimen col;
2023 col = view->cursor_col;
2024 if (!view->hexview_in_text && view->hexedit_lownibble)
2025 col++;
2026 widget_move (&view->widget, top + view->cursor_row, left + col);
2029 static void
2030 view_update (WView *view)
2032 static int dirt_limit = 1;
2034 if (view->dpy_bbar_dirty) {
2035 view->dpy_bbar_dirty = FALSE;
2036 view_labels (view);
2037 buttonbar_redraw (view->widget.parent);
2040 if (view->dirty > dirt_limit) {
2041 /* Too many updates skipped -> force a update */
2042 display (view);
2043 view->dirty = 0;
2044 /* Raise the update skipping limit */
2045 dirt_limit++;
2046 if (dirt_limit > max_dirt_limit)
2047 dirt_limit = max_dirt_limit;
2049 if (view->dirty) {
2050 if (is_idle ()) {
2051 /* We have time to update the screen properly */
2052 display (view);
2053 view->dirty = 0;
2054 if (dirt_limit > 1)
2055 dirt_limit--;
2056 } else {
2057 /* We are busy -> skipping full update,
2058 only the status line is updated */
2059 view_display_status (view);
2061 /* Here we had a refresh, if fast scrolling does not work
2062 restore the refresh, although this should not happen */
2066 /* {{{ Hex editor }}} */
2068 static void
2069 enqueue_change (struct hexedit_change_node **head,
2070 struct hexedit_change_node *node)
2072 /* chnode always either points to the head of the list or
2073 * to one of the ->next fields in the list. The value at
2074 * this location will be overwritten with the new node. */
2075 struct hexedit_change_node **chnode = head;
2077 while (*chnode != NULL && (*chnode)->offset < node->offset)
2078 chnode = &((*chnode)->next);
2080 node->next = *chnode;
2081 *chnode = node;
2084 static cb_ret_t
2085 view_handle_editkey (WView *view, int key)
2087 struct hexedit_change_node *node;
2088 byte byte_val;
2090 /* Has there been a change at this position? */
2091 node = view->change_list;
2092 while (node && (node->offset != view->hex_cursor))
2093 node = node->next;
2095 if (!view->hexview_in_text) {
2096 /* Hex editing */
2097 unsigned int hexvalue = 0;
2099 if (key >= '0' && key <= '9')
2100 hexvalue = 0 + (key - '0');
2101 else if (key >= 'A' && key <= 'F')
2102 hexvalue = 10 + (key - 'A');
2103 else if (key >= 'a' && key <= 'f')
2104 hexvalue = 10 + (key - 'a');
2105 else
2106 return MSG_NOT_HANDLED;
2108 if (node)
2109 byte_val = node->value;
2110 else
2111 byte_val = get_byte (view, view->hex_cursor);
2113 if (view->hexedit_lownibble) {
2114 byte_val = (byte_val & 0xf0) | (hexvalue);
2115 } else {
2116 byte_val = (byte_val & 0x0f) | (hexvalue << 4);
2118 } else {
2119 /* Text editing */
2120 if (key < 256 && (is_printable (key) || (key == '\n')))
2121 byte_val = key;
2122 else
2123 return MSG_NOT_HANDLED;
2125 if (!node) {
2126 node = g_new (struct hexedit_change_node, 1);
2127 node->offset = view->hex_cursor;
2128 node->value = byte_val;
2129 enqueue_change (&view->change_list, node);
2130 } else {
2131 node->value = byte_val;
2133 view->dirty++;
2134 view_update (view);
2135 view_move_right (view, 1);
2136 return MSG_HANDLED;
2139 static gboolean
2140 view_hexedit_save_changes (WView *view)
2142 struct hexedit_change_node *curr, *next;
2143 int fp, answer;
2144 char *text, *error;
2146 if (view->change_list == NULL)
2147 return TRUE;
2149 retry_save:
2150 assert (view->filename != NULL);
2151 fp = mc_open (view->filename, O_WRONLY);
2152 if (fp == -1)
2153 goto save_error;
2155 for (curr = view->change_list; curr != NULL; curr = next) {
2156 next = curr->next;
2158 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
2159 || mc_write (fp, &(curr->value), 1) != 1)
2160 goto save_error;
2162 /* delete the saved item from the change list */
2163 view->change_list = next;
2164 view->dirty++;
2165 view_set_byte (view, curr->offset, curr->value);
2166 g_free (curr);
2169 if (mc_close (fp) == -1) {
2170 error = g_strdup (strerror (errno));
2171 message (D_ERROR, _(" Save file "),
2172 _(" Error while closing the file: \n %s \n"
2173 " Data may have been written or not. "), error);
2174 g_free (error);
2176 view_update (view);
2177 return TRUE;
2179 save_error:
2180 error = g_strdup (strerror (errno));
2181 text = g_strdup_printf (_(" Cannot save file: \n %s "), error);
2182 g_free (error);
2183 (void) mc_close (fp);
2185 answer = query_dialog (_(" Save file "), text, D_ERROR,
2186 2, _("&Retry"), _("&Cancel"));
2187 g_free (text);
2189 if (answer == 0)
2190 goto retry_save;
2191 return FALSE;
2194 /* {{{ Miscellaneous functions }}} */
2196 static gboolean
2197 view_ok_to_quit (WView *view)
2199 int r;
2201 if (view->change_list == NULL)
2202 return TRUE;
2204 r = query_dialog (_("Quit"),
2205 _(" File was modified, Save with exit? "), D_NORMAL, 3,
2206 _("&Cancel quit"), _("&Yes"), _("&No"));
2208 switch (r) {
2209 case 1:
2210 return view_hexedit_save_changes (view);
2211 case 2:
2212 view_hexedit_free_change_list (view);
2213 return TRUE;
2214 default:
2215 return FALSE;
2219 static inline void
2220 my_define (Dlg_head *h, int idx, const char *text, void (*fn) (WView *),
2221 WView *view)
2223 buttonbar_set_label_data (h, idx, text, (buttonbarfn) fn, view);
2226 /* {{{ Searching }}} */
2228 /* Case insensitive search of text in data */
2229 static int
2230 icase_search_p (WView *view, char *text, char *data, int nothing)
2232 const char *q;
2233 int lng;
2234 const int direction = view->direction;
2236 (void) nothing;
2238 /* If we are searching backwards, reverse the string */
2239 if (direction == -1) {
2240 g_strreverse (text);
2241 g_strreverse (data);
2244 q = _icase_search (text, data, &lng);
2246 if (direction == -1) {
2247 g_strreverse (text);
2248 g_strreverse (data);
2251 if (q != 0) {
2252 if (direction > 0)
2253 view->search_start = q - data - lng;
2254 else
2255 view->search_start = strlen (data) - (q - data);
2256 view->search_length = lng;
2257 return 1;
2259 return 0;
2262 static char *
2263 grow_string_buffer (char *text, gulong *size)
2265 char *new;
2267 /* The grow steps */
2268 *size += 160;
2269 new = g_realloc (text, *size);
2270 if (text == NULL) {
2271 *new = '\0';
2273 return new;
2276 static char *
2277 get_line_at (WView *view, offset_type *p, offset_type *skipped)
2279 char *buffer = NULL;
2280 gulong buffer_size = 0;
2281 offset_type usable_size = 0;
2282 int ch;
2283 const int direction = view->direction;
2284 offset_type pos = *p;
2285 offset_type i = 0;
2286 int prev = '\0';
2288 *skipped = 0;
2290 if (pos == 0 && direction == -1)
2291 return 0;
2293 /* skip over all the possible zeros in the file */
2294 while ((ch = get_byte (view, pos)) == 0) {
2295 if (pos == 0 && direction == -1)
2296 return 0;
2297 pos += direction;
2298 i++;
2300 *skipped = i;
2302 if (i == 0 && (pos != 0 || direction == -1)) {
2303 prev = get_byte (view, pos - direction);
2304 if ((prev == -1) || (prev == '\n'))
2305 prev = '\0';
2308 for (i = 1; ch != -1; ch = get_byte (view, pos)) {
2309 if (i >= usable_size) {
2310 buffer = grow_string_buffer (buffer, &buffer_size);
2311 usable_size = buffer_size - 2; /* prev & null terminator */
2314 buffer[i++] = ch;
2316 if (pos == 0 && direction == -1)
2317 break;
2319 pos += direction;
2321 if (ch == '\n' || ch == '\0') {
2322 i--; /* Strip newline/zero */
2323 break;
2327 if (buffer) {
2328 buffer[0] = prev;
2329 buffer[i] = '\0';
2331 /* If we are searching backwards, reverse the string */
2332 if (direction == -1) {
2333 g_strreverse (buffer + 1);
2337 *p = pos;
2338 return buffer;
2341 static void
2342 search_update_steps (WView *view)
2344 offset_type filesize = view_get_filesize (view);
2345 if (filesize != 0)
2346 view->update_steps = 40000;
2347 else /* viewing a data stream, not a file */
2348 view->update_steps = filesize / 100;
2350 /* Do not update the percent display but every 20 ks */
2351 if (view->update_steps < 20000)
2352 view->update_steps = 20000;
2355 static void
2356 search (WView *view, char *text,
2357 int (*search) (WView *, char *, char *, int))
2359 char *s = NULL; /* The line we read from the view buffer */
2360 offset_type p, beginning, search_start;
2361 int found_len;
2362 int search_status;
2363 Dlg_head *d = 0;
2365 /* Used to keep track of where the line starts, when looking forward
2366 * is the index before transfering the line; the reverse case uses
2367 * the position returned after the line has been read */
2368 offset_type forward_line_start;
2369 offset_type reverse_line_start;
2370 offset_type t;
2372 if (verbose) {
2373 d = create_message (D_NORMAL, _("Search"), _("Searching %s"), text);
2374 mc_refresh ();
2377 found_len = view->search_length;
2378 search_start = view->search_start;
2380 if (view->direction == 1) {
2381 p = search_start + ((found_len) ? 1 : 0);
2382 } else {
2383 p = search_start - ((found_len && search_start >= 1) ? 1 : 0);
2385 beginning = p;
2387 /* Compute the percent steps */
2388 search_update_steps (view);
2389 view->update_activate = 0;
2391 enable_interrupt_key ();
2392 for (;; g_free (s)) {
2393 if (p >= view->update_activate) {
2394 view->update_activate += view->update_steps;
2395 if (verbose) {
2396 view_percent (view, p);
2397 mc_refresh ();
2399 if (got_interrupt ())
2400 break;
2402 forward_line_start = p;
2403 s = get_line_at (view, &p, &t);
2404 reverse_line_start = p;
2406 if (!s)
2407 break;
2409 search_status = (*search) (view, text, s + 1, match_normal);
2410 if (search_status < 0) {
2411 g_free (s);
2412 break;
2415 if (search_status == 0)
2416 continue;
2418 /* We found the string */
2420 /* Handle ^ and $ when regexp search starts at the middle of the line */
2421 if (*s && !view->search_start && (search == regexp_view_search)) {
2422 if ((*text == '^' && view->direction == 1)
2423 || (view->direction == -1 && text[strlen (text) - 1] == '$')
2425 continue;
2428 /* Record the position used to continue the search */
2429 if (view->direction == 1)
2430 t += forward_line_start;
2431 else
2432 t = reverse_line_start ? reverse_line_start + 2 : 0;
2433 view->search_start += t;
2435 if (t != beginning) {
2436 view->dpy_start = t;
2439 g_free (s);
2440 break;
2442 disable_interrupt_key ();
2443 if (verbose) {
2444 dlg_run_done (d);
2445 destroy_dlg (d);
2447 if (!s) {
2448 message (0, _("Search"), _(" Search string not found "));
2449 view->search_length = 0;
2453 /* Search buffer (its size is len) in the complete buffer
2454 * returns the position where the block was found or INVALID_OFFSET
2455 * if not found */
2456 static offset_type
2457 block_search (WView *view, const char *buffer, int len)
2459 int direction = view->direction;
2460 const char *d = buffer;
2461 char b;
2462 offset_type e;
2464 enable_interrupt_key ();
2465 if (direction == 1)
2466 e = view->search_start + ((view->search_length) ? 1 : 0);
2467 else
2468 e = view->search_start
2469 - ((view->search_length && view->search_start >= 1) ? 1 : 0);
2471 search_update_steps (view);
2472 view->update_activate = 0;
2474 if (direction == -1) {
2475 for (d += len - 1;; e--) {
2476 if (e <= view->update_activate) {
2477 view->update_activate -= view->update_steps;
2478 if (verbose) {
2479 view_percent (view, e);
2480 mc_refresh ();
2482 if (got_interrupt ())
2483 break;
2485 b = get_byte (view, e);
2487 if (*d == b) {
2488 if (d == buffer) {
2489 disable_interrupt_key ();
2490 return e;
2492 d--;
2493 } else {
2494 e += buffer + len - 1 - d;
2495 d = buffer + len - 1;
2497 if (e == 0)
2498 break;
2500 } else {
2501 while (get_byte (view, e) != -1) {
2502 if (e >= view->update_activate) {
2503 view->update_activate += view->update_steps;
2504 if (verbose) {
2505 view_percent (view, e);
2506 mc_refresh ();
2508 if (got_interrupt ())
2509 break;
2511 b = get_byte (view, e++);
2513 if (*d == b) {
2514 d++;
2515 if (d - buffer == len) {
2516 disable_interrupt_key ();
2517 return e - len;
2519 } else {
2520 e -= d - buffer;
2521 d = buffer;
2525 disable_interrupt_key ();
2526 return INVALID_OFFSET;
2530 * Search in the hex mode. Supported input:
2531 * - numbers (oct, dec, hex). Each of them matches one byte.
2532 * - strings in double quotes. Matches exactly without quotes.
2534 static void
2535 hex_search (WView *view, const char *text)
2537 char *buffer; /* Parsed search string */
2538 char *cur; /* Current position in it */
2539 int block_len; /* Length of the search string */
2540 offset_type pos; /* Position of the string in the file */
2541 int parse_error = 0;
2543 if (!*text) {
2544 view->search_length = 0;
2545 return;
2548 /* buffer will never be longer that text */
2549 buffer = g_new (char, strlen (text));
2550 cur = buffer;
2552 /* First convert the string to a stream of bytes */
2553 while (*text) {
2554 int val;
2555 int ptr;
2557 /* Skip leading spaces */
2558 if (*text == ' ' || *text == '\t') {
2559 text++;
2560 continue;
2563 /* %i matches octal, decimal, and hexadecimal numbers */
2564 if (sscanf (text, "%i%n", &val, &ptr) > 0) {
2565 /* Allow signed and unsigned char in the user input */
2566 if (val < -128 || val > 255) {
2567 parse_error = 1;
2568 break;
2571 *cur++ = (char) val;
2572 text += ptr;
2573 continue;
2576 /* Try quoted string, strip quotes */
2577 if (*text == '"') {
2578 const char *next_quote;
2580 text++;
2581 next_quote = strchr (text, '"');
2582 if (next_quote) {
2583 memcpy (cur, text, next_quote - text);
2584 cur += next_quote - text;
2585 text = next_quote + 1;
2586 continue;
2588 /* fall through */
2591 parse_error = 1;
2592 break;
2595 block_len = cur - buffer;
2597 /* No valid bytes in the user input */
2598 if (block_len <= 0 || parse_error) {
2599 message (0, _("Search"), _("Invalid hex search expression"));
2600 g_free (buffer);
2601 view->search_length = 0;
2602 return;
2605 /* Then start the search */
2606 pos = block_search (view, buffer, block_len);
2608 g_free (buffer);
2610 if (pos == INVALID_OFFSET) {
2611 message (0, _("Search"), _(" Search string not found "));
2612 view->search_length = 0;
2613 return;
2616 view->search_start = pos;
2617 view->search_length = block_len;
2618 /* Set the edit cursor to the search position, left nibble */
2619 view->hex_cursor = view->search_start;
2620 view->hexedit_lownibble = FALSE;
2622 /* Adjust the file offset */
2623 view->dpy_start = pos - pos % view->bytes_per_line;
2626 static int
2627 regexp_view_search (WView *view, char *pattern, char *string,
2628 int match_type)
2630 static regex_t r;
2631 static char *old_pattern = NULL;
2632 static int old_type;
2633 regmatch_t pmatch[1];
2634 int i, flags = REG_ICASE;
2636 if (old_pattern == NULL || strcmp (old_pattern, pattern) != 0
2637 || old_type != match_type) {
2638 if (old_pattern != NULL) {
2639 regfree (&r);
2640 g_free (old_pattern);
2641 old_pattern = 0;
2643 for (i = 0; pattern[i] != '\0'; i++) {
2644 if (isupper ((unsigned char) pattern[i])) {
2645 flags = 0;
2646 break;
2649 flags |= REG_EXTENDED;
2650 if (regcomp (&r, pattern, flags)) {
2651 message (1, MSG_ERROR, _(" Invalid regular expression "));
2652 return -1;
2654 old_pattern = g_strdup (pattern);
2655 old_type = match_type;
2657 if (regexec (&r, string, 1, pmatch, 0) != 0)
2658 return 0;
2659 view->search_length = pmatch[0].rm_eo - pmatch[0].rm_so;
2660 view->search_start = pmatch[0].rm_so;
2661 return 1;
2664 static void
2665 do_regexp_search (WView *view)
2667 search (view, view->search_exp, regexp_view_search);
2668 /* Had a refresh here */
2669 view->dirty++;
2670 view_update (view);
2673 static void
2674 do_normal_search (WView *view)
2676 if (view->hex_mode)
2677 hex_search (view, view->search_exp);
2678 else
2679 search (view, view->search_exp, icase_search_p);
2680 /* Had a refresh here */
2681 view->dirty++;
2682 view_update (view);
2685 /* {{{ User-definable commands }}} */
2688 The functions in this section can be bound to hotkeys. They are all
2689 of the same type (taking a pointer to WView as parameter and
2690 returning void). TODO: In the not-too-distant future, these commands
2691 will become fully configurable, like they already are in the
2692 internal editor. By convention, all the function names end in
2693 "_cmd".
2696 static void
2697 view_help_cmd (void)
2699 interactive_display (NULL, "[Internal File Viewer]");
2702 /* Toggle between hexview and hexedit mode */
2703 static void
2704 view_toggle_hexedit_mode_cmd (WView *view)
2706 view_toggle_hexedit_mode (view);
2707 view_update (view);
2710 /* Toggle between wrapped and unwrapped view */
2711 static void
2712 view_toggle_wrap_mode_cmd (WView *view)
2714 view_toggle_wrap_mode (view);
2715 view_update (view);
2718 /* Toggle between hex view and text view */
2719 static void
2720 view_toggle_hex_mode_cmd (WView *view)
2722 view_toggle_hex_mode (view);
2723 view_update (view);
2726 static void
2727 view_moveto_line_cmd (WView *view)
2729 char *answer, *answer_end, prompt[BUF_SMALL];
2730 offset_type line, col;
2732 view_offset_to_coord (view, &line, &col, view->dpy_start);
2734 g_snprintf (prompt, sizeof (prompt),
2735 _(" The current line number is %d.\n"
2736 " Enter the new line number:"), (int) (line + 1));
2737 answer = input_dialog (_(" Goto line "), prompt, MC_HISTORY_VIEW_GOTO_LINE, "");
2738 if (answer != NULL && answer[0] != '\0') {
2739 errno = 0;
2740 line = strtoul (answer, &answer_end, 10);
2741 if (*answer_end == '\0' && errno == 0 && line >= 1)
2742 view_moveto (view, line - 1, 0);
2744 g_free (answer);
2745 view->dirty++;
2746 view_update (view);
2749 static void
2750 view_moveto_addr_cmd (WView *view)
2752 char *line, *error, prompt[BUF_SMALL];
2753 offset_type addr;
2755 g_snprintf (prompt, sizeof (prompt),
2756 _(" The current address is 0x%lx.\n"
2757 " Enter the new address:"), view->hex_cursor);
2758 line = input_dialog (_(" Goto Address "), prompt, MC_HISTORY_VIEW_GOTO_ADDR, "");
2759 if (line != NULL) {
2760 if (*line != '\0') {
2761 addr = strtoul (line, &error, 0);
2762 if ((*error == '\0') && get_byte (view, addr) != -1) {
2763 view_moveto_offset (view, addr);
2764 } else {
2765 message (D_ERROR, _("Warning"), _(" Invalid address "));
2768 g_free (line);
2770 view->dirty++;
2771 view_update (view);
2774 static void
2775 view_hexedit_save_changes_cmd (WView *view)
2777 (void) view_hexedit_save_changes (view);
2780 /* {{{ Searching }}} */
2782 static void
2783 regexp_search (WView *view, int direction)
2785 const char *defval;
2786 char *regexp;
2787 static char *last_regexp;
2789 defval = (last_regexp != NULL ? last_regexp : "");
2791 regexp = input_dialog (_("Search"), _(" Enter regexp:"), MC_HISTORY_VIEW_SEARCH_REGEX, defval);
2792 if (regexp == NULL || regexp[0] == '\0') {
2793 g_free (regexp);
2794 return;
2797 g_free (last_regexp);
2798 view->search_exp = last_regexp = regexp;
2800 view->direction = direction;
2801 do_regexp_search (view);
2802 view->last_search = do_regexp_search;
2805 /* {{{ User-definable commands }}} */
2807 static void
2808 view_regexp_search_cmd (WView *view)
2810 regexp_search (view, 1);
2813 /* Both views */
2814 static void
2815 view_normal_search_cmd (WView *view)
2817 char *defval, *exp = NULL;
2818 static char *last_search_string;
2820 enum {
2821 SEARCH_DLG_HEIGHT = 8,
2822 SEARCH_DLG_WIDTH = 58
2825 static int replace_backwards;
2826 int treplace_backwards = replace_backwards;
2828 static QuickWidget quick_widgets[] = {
2829 {quick_button, 6, 10, 5, SEARCH_DLG_HEIGHT, N_("&Cancel"), 0,
2830 B_CANCEL,
2831 0, 0, NULL},
2832 {quick_button, 2, 10, 5, SEARCH_DLG_HEIGHT, N_("&OK"), 0, B_ENTER,
2833 0, 0, NULL},
2834 {quick_checkbox, 3, SEARCH_DLG_WIDTH, 4, SEARCH_DLG_HEIGHT,
2835 N_("&Backwards"), 0, 0,
2836 0, 0, NULL},
2837 {quick_input, 3, SEARCH_DLG_WIDTH, 3, SEARCH_DLG_HEIGHT, "", 52, 0,
2838 0, 0, N_("Search")},
2839 {quick_label, 2, SEARCH_DLG_WIDTH, 2, SEARCH_DLG_HEIGHT,
2840 N_(" Enter search string:"), 0, 0,
2841 0, 0, 0},
2842 NULL_QuickWidget
2844 static QuickDialog Quick_input = {
2845 SEARCH_DLG_WIDTH, SEARCH_DLG_HEIGHT, -1, 0, N_("Search"),
2846 "[Input Line Keys]", quick_widgets, 0
2849 defval = g_strdup (last_search_string != NULL ? last_search_string : "");
2850 convert_to_display (defval);
2852 quick_widgets[2].result = &treplace_backwards;
2853 quick_widgets[3].str_result = &exp;
2854 quick_widgets[3].text = defval;
2856 if (quick_dialog (&Quick_input) == B_CANCEL)
2857 goto cleanup;
2859 replace_backwards = treplace_backwards;
2861 if (exp == NULL || exp[0] == '\0')
2862 goto cleanup;
2864 convert_from_input (exp);
2866 g_free (last_search_string);
2867 view->search_exp = last_search_string = exp;
2868 exp = NULL;
2870 view->direction = replace_backwards ? -1 : 1;
2871 do_normal_search (view);
2872 view->last_search = do_normal_search;
2874 cleanup:
2875 g_free (exp);
2876 g_free (defval);
2879 static void
2880 view_toggle_magic_mode_cmd (WView *view)
2882 view_toggle_magic_mode (view);
2883 view_update (view);
2886 static void
2887 view_toggle_nroff_mode_cmd (WView *view)
2889 view_toggle_nroff_mode (view);
2890 view_update (view);
2893 static void
2894 view_quit_cmd (WView *view)
2896 if (view_ok_to_quit (view))
2897 dlg_stop (view->widget.parent);
2900 /* {{{ Miscellaneous functions }}} */
2902 /* Define labels and handlers for functional keys */
2903 static void
2904 view_labels (WView *view)
2906 Dlg_head *h = view->widget.parent;
2908 buttonbar_set_label (h, 1, Q_("ButtonBar|Help"), view_help_cmd);
2910 my_define (h, 10, Q_("ButtonBar|Quit"), view_quit_cmd, view);
2911 my_define (h, 4, view->hex_mode
2912 ? Q_("ButtonBar|Ascii")
2913 : Q_("ButtonBar|Hex"),
2914 view_toggle_hex_mode_cmd, view);
2915 my_define (h, 5, view->hex_mode
2916 ? Q_("ButtonBar|Goto")
2917 : Q_("ButtonBar|Line"),
2918 view->hex_mode ? view_moveto_addr_cmd : view_moveto_line_cmd, view);
2920 if (view->hex_mode) {
2921 if (view->hexedit_mode) {
2922 my_define (h, 2, Q_("ButtonBar|View"),
2923 view_toggle_hexedit_mode_cmd, view);
2924 } else if (view->datasource == DS_FILE) {
2925 my_define (h, 2, Q_("ButtonBar|Edit"),
2926 view_toggle_hexedit_mode_cmd, view);
2927 } else {
2928 buttonbar_clear_label (h, 2);
2930 my_define (h, 6, Q_("ButtonBar|Save"),
2931 view_hexedit_save_changes_cmd, view);
2932 } else {
2933 my_define (h, 2, view->text_wrap_mode
2934 ? Q_("ButtonBar|UnWrap")
2935 : Q_("ButtonBar|Wrap"),
2936 view_toggle_wrap_mode_cmd, view);
2937 my_define (h, 6, Q_("ButtonBar|RxSrch"),
2938 view_regexp_search_cmd, view);
2941 my_define (h, 7, view->hex_mode
2942 ? Q_("ButtonBar|HxSrch")
2943 : Q_("ButtonBar|Search"),
2944 view_normal_search_cmd, view);
2945 my_define (h, 8, view->magic_mode
2946 ? Q_("ButtonBar|Raw")
2947 : Q_("ButtonBar|Parse"),
2948 view_toggle_magic_mode_cmd, view);
2950 /* don't override the key to access the main menu */
2951 if (!view_is_in_panel (view)) {
2952 my_define (h, 9, view->text_nroff_mode
2953 ? Q_("ButtonBar|Unform")
2954 : Q_("ButtonBar|Format"),
2955 view_toggle_nroff_mode_cmd, view);
2956 my_define (h, 3, Q_("ButtonBar|Quit"), view_quit_cmd, view);
2960 /* {{{ Event handling }}} */
2962 /* Check for left and right arrows, possibly with modifiers */
2963 static cb_ret_t
2964 check_left_right_keys (WView *view, int c)
2966 if (c == KEY_LEFT) {
2967 view_move_left (view, 1);
2968 return MSG_HANDLED;
2971 if (c == KEY_RIGHT) {
2972 view_move_right (view, 1);
2973 return MSG_HANDLED;
2976 /* Ctrl with arrows moves by 10 postions in the unwrap mode */
2977 if (view->hex_mode || view->text_wrap_mode)
2978 return MSG_NOT_HANDLED;
2980 if (c == (KEY_M_CTRL | KEY_LEFT)) {
2981 if (view->dpy_text_column >= 10)
2982 view->dpy_text_column -= 10;
2983 else
2984 view->dpy_text_column = 0;
2985 view->dirty++;
2986 return MSG_HANDLED;
2989 if (c == (KEY_M_CTRL | KEY_RIGHT)) {
2990 if (view->dpy_text_column <= OFFSETTYPE_MAX - 10)
2991 view->dpy_text_column += 10;
2992 else
2993 view->dpy_text_column = OFFSETTYPE_MAX;
2994 view->dirty++;
2995 return MSG_HANDLED;
2998 return MSG_NOT_HANDLED;
3001 /* {{{ User-definable commands }}} */
3003 static void
3004 view_continue_search_cmd (WView *view)
3006 if (view->last_search) {
3007 view->last_search (view);
3008 } else {
3009 /* if not... then ask for an expression */
3010 view_normal_search_cmd (view);
3014 static void
3015 view_toggle_ruler_cmd (WView *view)
3017 static const enum ruler_type next[3] = {
3018 RULER_TOP,
3019 RULER_BOTTOM,
3020 RULER_NONE
3023 assert ((size_t) ruler < 3);
3024 ruler = next[(size_t) ruler];
3025 view->dirty++;
3028 /* {{{ Event handling }}} */
3030 static void view_cmk_move_up (void *w, int n) {
3031 view_move_up ((WView *) w, n);
3033 static void view_cmk_move_down (void *w, int n) {
3034 view_move_down ((WView *) w, n);
3036 static void view_cmk_moveto_top (void *w, int n) {
3037 (void) &n;
3038 view_moveto_top ((WView *) w);
3040 static void view_cmk_moveto_bottom (void *w, int n) {
3041 (void) &n;
3042 view_moveto_bottom ((WView *) w);
3045 /* Both views */
3046 static cb_ret_t
3047 view_handle_key (WView *view, int c)
3049 c = convert_from_input_c (c);
3051 if (view->hex_mode) {
3052 switch (c) {
3053 case '\t':
3054 view->hexview_in_text = !view->hexview_in_text;
3055 view->dirty++;
3056 return MSG_HANDLED;
3058 case XCTRL ('a'):
3059 view_moveto_bol (view);
3060 view->dirty++;
3061 return MSG_HANDLED;
3063 case XCTRL ('b'):
3064 view_move_left (view, 1);
3065 return MSG_HANDLED;
3067 case XCTRL ('e'):
3068 view_moveto_eol (view);
3069 return MSG_HANDLED;
3071 case XCTRL ('f'):
3072 view_move_right (view, 1);
3073 return MSG_HANDLED;
3076 if (view->hexedit_mode
3077 && view_handle_editkey (view, c) == MSG_HANDLED)
3078 return MSG_HANDLED;
3081 if (check_left_right_keys (view, c))
3082 return MSG_HANDLED;
3084 if (check_movement_keys (c, view->data_area.height + 1, view,
3085 view_cmk_move_up, view_cmk_move_down,
3086 view_cmk_moveto_top, view_cmk_moveto_bottom))
3087 return MSG_HANDLED;
3089 switch (c) {
3091 case '?':
3092 regexp_search (view, -1);
3093 return MSG_HANDLED;
3095 case '/':
3096 regexp_search (view, 1);
3097 return MSG_HANDLED;
3099 /* Continue search */
3100 case XCTRL ('r'):
3101 case XCTRL ('s'):
3102 case 'n':
3103 case KEY_F (17):
3104 view_continue_search_cmd (view);
3105 return MSG_HANDLED;
3107 /* toggle ruler */
3108 case ALT ('r'):
3109 view_toggle_ruler_cmd (view);
3110 return MSG_HANDLED;
3112 case 'h':
3113 view_move_left (view, 1);
3114 return MSG_HANDLED;
3116 case 'j':
3117 case '\n':
3118 case 'e':
3119 view_move_down (view, 1);
3120 return MSG_HANDLED;
3122 case 'd':
3123 view_move_down (view, (view->data_area.height + 1) / 2);
3124 return MSG_HANDLED;
3126 case 'u':
3127 view_move_up (view, (view->data_area.height + 1) / 2);
3128 return MSG_HANDLED;
3130 case 'k':
3131 case 'y':
3132 view_move_up (view, 1);
3133 return MSG_HANDLED;
3135 case 'l':
3136 view_move_right (view, 1);
3137 return MSG_HANDLED;
3139 case ' ':
3140 case 'f':
3141 view_move_down (view, view->data_area.height);
3142 return MSG_HANDLED;
3144 case XCTRL ('o'):
3145 view_other_cmd ();
3146 return MSG_HANDLED;
3148 /* Unlike Ctrl-O, run a new shell if the subshell is not running. */
3149 case '!':
3150 exec_shell ();
3151 return MSG_HANDLED;
3153 case 'b':
3154 view_move_up (view, view->data_area.height);
3155 return MSG_HANDLED;
3157 case KEY_IC:
3158 view_move_up (view, 2);
3159 return MSG_HANDLED;
3161 case KEY_DC:
3162 view_move_down (view, 2);
3163 return MSG_HANDLED;
3165 case 'm':
3166 view->marks[view->marker] = view->dpy_start;
3167 return MSG_HANDLED;
3169 case 'r':
3170 view->dpy_start = view->marks[view->marker];
3171 view->dirty++;
3172 return MSG_HANDLED;
3174 /* Use to indicate parent that we want to see the next/previous file */
3175 /* Does not work in panel mode */
3176 case XCTRL ('f'):
3177 case XCTRL ('b'):
3178 if (!view_is_in_panel (view))
3179 view->move_dir = c == XCTRL ('f') ? 1 : -1;
3180 /* FALLTHROUGH */
3181 case 'q':
3182 case XCTRL ('g'):
3183 case ESC_CHAR:
3184 if (view_ok_to_quit (view))
3185 view->want_to_quit = TRUE;
3186 return MSG_HANDLED;
3188 #ifdef HAVE_CHARSET
3189 case XCTRL ('t'):
3190 do_select_codepage ();
3191 view->dirty++;
3192 view_update (view);
3193 return MSG_HANDLED;
3194 #endif /* HAVE_CHARSET */
3196 #ifdef MC_ENABLE_DEBUGGING_CODE
3197 case 't': /* mnemonic: "test" */
3198 view_ccache_dump (view);
3199 return MSG_HANDLED;
3200 #endif
3202 if (c >= '0' && c <= '9')
3203 view->marker = c - '0';
3205 /* Key not used */
3206 return MSG_NOT_HANDLED;
3209 /* Both views */
3210 static int
3211 view_event (WView *view, Gpm_Event *event, int *result)
3213 screen_dimen y, x;
3215 *result = MOU_NORMAL;
3217 /* We are not interested in the release events */
3218 if (!(event->type & (GPM_DOWN | GPM_DRAG)))
3219 return 0;
3221 /* Wheel events */
3222 if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) {
3223 view_move_up (view, 2);
3224 return 1;
3226 if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
3227 view_move_down (view, 2);
3228 return 1;
3231 x = event->x;
3232 y = event->y;
3234 /* Scrolling left and right */
3235 if (!view->text_wrap_mode) {
3236 if (x < view->data_area.width * 1/4) {
3237 view_move_left (view, 1);
3238 goto processed;
3239 } else if (x < view->data_area.width * 3/4) {
3240 /* ignore the click */
3241 } else {
3242 view_move_right (view, 1);
3243 goto processed;
3247 /* Scrolling up and down */
3248 if (y < view->data_area.top + view->data_area.height * 1/3) {
3249 if (mouse_move_pages_viewer)
3250 view_move_up (view, view->data_area.height / 2);
3251 else
3252 view_move_up (view, 1);
3253 goto processed;
3254 } else if (y < view->data_area.top + view->data_area.height * 2/3) {
3255 /* ignore the click */
3256 } else {
3257 if (mouse_move_pages_viewer)
3258 view_move_down (view, view->data_area.height / 2);
3259 else
3260 view_move_down (view, 1);
3261 goto processed;
3264 return 0;
3266 processed:
3267 *result = MOU_REPEAT;
3268 return 1;
3271 /* Real view only */
3272 static int
3273 real_view_event (Gpm_Event *event, void *x)
3275 WView *view = (WView *) x;
3276 int result;
3278 if (view_event (view, event, &result))
3279 view_update (view);
3280 return result;
3283 static void
3284 view_adjust_size (Dlg_head *h)
3286 WView *view;
3287 WButtonBar *bar;
3289 /* Look up the viewer and the buttonbar, we assume only two widgets here */
3290 view = (WView *) find_widget_type (h, view_callback);
3291 bar = find_buttonbar (h);
3292 widget_set_size (&view->widget, 0, 0, LINES - 1, COLS);
3293 widget_set_size ((Widget *) bar, LINES - 1, 0, 1, COLS);
3295 view_compute_areas (view);
3296 view_update_bytes_per_line (view);
3299 /* Callback for the view dialog */
3300 static cb_ret_t
3301 view_dialog_callback (Dlg_head *h, dlg_msg_t msg, int parm)
3303 switch (msg) {
3304 case DLG_RESIZE:
3305 view_adjust_size (h);
3306 return MSG_HANDLED;
3308 default:
3309 return default_dlg_callback (h, msg, parm);
3313 /* {{{ External interface }}} */
3315 /* Real view only */
3317 mc_internal_viewer (const char *command, const char *file,
3318 int *move_dir_p, int start_line)
3320 gboolean succeeded;
3321 WView *wview;
3322 WButtonBar *bar;
3323 Dlg_head *view_dlg;
3325 /* Create dialog and widgets, put them on the dialog */
3326 view_dlg =
3327 create_dlg (0, 0, LINES, COLS, NULL, view_dialog_callback,
3328 "[Internal File Viewer]", NULL, DLG_WANT_TAB);
3330 wview = view_new (0, 0, COLS, LINES - 1, 0);
3332 bar = buttonbar_new (1);
3334 add_widget (view_dlg, bar);
3335 add_widget (view_dlg, wview);
3337 succeeded = view_load (wview, command, file, start_line);
3338 if (succeeded) {
3339 run_dlg (view_dlg);
3340 if (move_dir_p)
3341 *move_dir_p = wview->move_dir;
3342 } else {
3343 if (move_dir_p)
3344 *move_dir_p = 0;
3346 destroy_dlg (view_dlg);
3348 return succeeded;
3351 /* {{{ Miscellaneous functions }}} */
3353 static void
3354 view_hook (void *v)
3356 WView *view = (WView *) v;
3357 WPanel *panel;
3359 /* If the user is busy typing, wait until he finishes to update the
3360 screen */
3361 if (!is_idle ()) {
3362 if (!hook_present (idle_hook, view_hook))
3363 add_hook (&idle_hook, view_hook, v);
3364 return;
3367 delete_hook (&idle_hook, view_hook);
3369 if (get_current_type () == view_listing)
3370 panel = current_panel;
3371 else if (get_other_type () == view_listing)
3372 panel = other_panel;
3373 else
3374 return;
3376 view_load (view, 0, panel->dir.list[panel->selected].fname, 0);
3377 display (view);
3380 /* {{{ Event handling }}} */
3382 static cb_ret_t
3383 view_callback (Widget *w, widget_msg_t msg, int parm)
3385 WView *view = (WView *) w;
3386 cb_ret_t i;
3387 Dlg_head *h = view->widget.parent;
3389 view_compute_areas (view);
3390 view_update_bytes_per_line (view);
3392 switch (msg) {
3393 case WIDGET_INIT:
3394 if (view_is_in_panel (view))
3395 add_hook (&select_file_hook, view_hook, view);
3396 else
3397 view->dpy_bbar_dirty = TRUE;
3398 return MSG_HANDLED;
3400 case WIDGET_DRAW:
3401 display (view);
3402 return MSG_HANDLED;
3404 case WIDGET_CURSOR:
3405 if (view->hex_mode)
3406 view_place_cursor (view);
3407 return MSG_HANDLED;
3409 case WIDGET_KEY:
3410 i = view_handle_key ((WView *) view, parm);
3411 if (view->want_to_quit && !view_is_in_panel (view))
3412 dlg_stop (h);
3413 else {
3414 view_update (view);
3416 return i;
3418 case WIDGET_FOCUS:
3419 view->dpy_bbar_dirty = TRUE;
3420 view_update (view);
3421 return MSG_HANDLED;
3423 case WIDGET_DESTROY:
3424 view_done (view);
3425 if (view_is_in_panel (view))
3426 delete_hook (&select_file_hook, view_hook);
3427 return MSG_HANDLED;
3429 default:
3430 return default_proc (msg, parm);
3434 /* {{{ External interface }}} */
3436 WView *
3437 view_new (int y, int x, int cols, int lines, int is_panel)
3439 WView *view = g_new0 (WView, 1);
3440 size_t i;
3442 init_widget (&view->widget, y, x, lines, cols,
3443 view_callback,
3444 real_view_event);
3446 view->filename = NULL;
3447 view->command = NULL;
3449 view_set_datasource_none (view);
3451 view->growbuf_in_use = FALSE;
3452 /* leave the other growbuf fields uninitialized */
3454 view->hex_mode = FALSE;
3455 view->hexedit_mode = FALSE;
3456 view->hexview_in_text = FALSE;
3457 view->text_nroff_mode = FALSE;
3458 view->text_wrap_mode = FALSE;
3459 view->magic_mode = FALSE;
3461 view->hexedit_lownibble = FALSE;
3462 view->coord_cache = NULL;
3464 view->dpy_frame_size = is_panel ? 1 : 0;
3465 view->dpy_start = 0;
3466 view->dpy_text_column = 0;
3467 view->dpy_end= 0;
3468 view->hex_cursor = 0;
3469 view->cursor_col = 0;
3470 view->cursor_row = 0;
3471 view->change_list = NULL;
3473 /* {status,ruler,data}_area are left uninitialized */
3475 view->dirty = 0;
3476 view->dpy_bbar_dirty = TRUE;
3477 view->bytes_per_line = 1;
3479 view->search_start = 0;
3480 view->search_length = 0;
3481 view->search_exp = NULL;
3482 view->direction = 1; /* forward */
3483 view->last_search = 0; /* it's a function */
3485 view->want_to_quit = FALSE;
3486 view->marker = 0;
3487 for (i = 0; i < sizeof(view->marks) / sizeof(view->marks[0]); i++)
3488 view->marks[i] = 0;
3490 view->move_dir = 0;
3491 view->update_steps = 0;
3492 view->update_activate = 0;
3494 if (default_hex_mode)
3495 view_toggle_hex_mode (view);
3496 if (default_nroff_flag)
3497 view_toggle_nroff_mode (view);
3498 if (global_wrap_mode)
3499 view_toggle_wrap_mode (view);
3500 if (default_magic_flag)
3501 view_toggle_magic_mode (view);
3503 return view;