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
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.
43 #include <sys/types.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 */
55 #include "key.h" /* For mi_getch() */
58 #include "wtools.h" /* For query_set_sel() */
60 #include "panel.h" /* Needed for current_panel and other_panel */
63 #include "main.h" /* slow_terminal */
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
;
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
;
104 /* data sources of the view */
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 */
114 screen_dimen top
, left
;
115 screen_dimen height
, width
;
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
150 gboolean growbuf_finished
; /* TRUE when all data has been read. */
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
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? */
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 ... */
194 int marker
; /* mark to use */
195 offset_type marks
[10]; /* 10 marks: 0..9 */
197 int move_dir
; /* return value from widget:
199 * -1 view previous file
203 offset_type update_steps
; /* The number of bytes between percent
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
{
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
,
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
)
280 /* {{{ Simple Primitive Functions for WView }}} */
282 static inline gboolean
283 view_is_in_panel (WView
*view
)
285 return (view
->dpy_frame_size
!= 0);
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
;
316 height
= dimen_min(rest
, (ruler
== RULER_NONE
|| view
->hex_mode
) ? 0 : 2);
317 view
->ruler_area
.height
= height
;
320 view
->data_area
.height
= rest
;
322 /* Compute the position of the areas */
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
;
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
) {
351 view
->change_list
= NULL
;
355 /* {{{ Growing buffer }}} */
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
;
368 view_growbuf_free (WView
*view
)
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
;
382 view_growbuf_filesize (WView
*view
)
384 assert(view
->growbuf_in_use
);
386 if (view
->growbuf_blocks
== 0)
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. */
397 view_growbuf_read_until (WView
*view
, offset_type ofs
)
404 assert (view
->growbuf_in_use
);
406 if (view
->growbuf_finished
)
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
) {
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
);
432 view
->growbuf_finished
= TRUE
;
433 (void) pclose (view
->ds_stdio_pipe
);
435 close_error_pipe (0, NULL
);
436 view
->ds_stdio_pipe
= NULL
;
440 assert (view
->datasource
== DS_VFS_PIPE
);
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;
451 short_read
= ((size_t)nread
< bytesfree
);
452 view
->growbuf_lastindex
+= nread
;
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
)
467 view_growbuf_read_until (view
, byte_index
+ 1);
468 if (view
->growbuf_blocks
== 0)
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
];
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
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.
499 view_get_filesize (WView
*view
)
501 switch (view
->datasource
) {
506 return view_growbuf_filesize (view
);
508 return view
->ds_file_filesize
;
510 return view
->ds_string_len
;
512 assert(!"Unknown datasource type");
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
);
533 view_file_load_data (WView
*view
, offset_type byte_index
)
535 offset_type blockoffset
;
539 assert (view
->datasource
== DS_FILE
);
541 if (already_loaded (view
->ds_file_offset
, byte_index
, view
->ds_file_datalen
))
544 if (byte_index
>= view
->ds_file_filesize
)
547 blockoffset
= offset_rounddown (byte_index
, view
->ds_file_datasize
);
548 if (mc_lseek (view
->ds_file_fd
, blockoffset
, SEEK_SET
) == -1)
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
);
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
;
565 view
->ds_file_datalen
= bytes_read
;
570 view
->ds_file_datalen
= 0;
574 get_byte_none (WView
*view
, offset_type byte_index
)
576 assert (view
->datasource
== DS_NONE
);
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
];
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
];
603 get_byte (WView
*view
, offset_type offset
)
605 switch (view
->datasource
) {
608 return get_byte_growing_buffer (view
, offset
);
610 return get_byte_file (view
, offset
);
612 return get_byte_string (view
, offset
);
614 return get_byte_none (view
, offset
);
616 assert(!"Unknown datasource type");
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
);
629 view_set_byte (WView
*view
, offset_type offset
, byte b
)
632 assert (offset
< view_get_filesize (view
));
633 assert (view
->datasource
== DS_FILE
);
634 view
->ds_file_datalen
= 0; /* just force reloading */
638 view_set_datasource_none (WView
*view
)
640 view
->datasource
= DS_NONE
;
644 view_set_datasource_vfs_pipe (WView
*view
, int fd
)
647 view
->datasource
= DS_VFS_PIPE
;
648 view
->ds_vfs_pipe
= fd
;
650 view_init_growbuf (view
);
654 view_set_datasource_stdio_pipe (WView
*view
, FILE *fp
)
657 view
->datasource
= DS_STDIO_PIPE
;
658 view
->ds_stdio_pipe
= fp
;
660 view_init_growbuf (view
);
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
);
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;
684 view_close_datasource (WView
*view
)
686 switch (view
->datasource
) {
690 if (view
->ds_stdio_pipe
!= NULL
) {
691 (void) pclose (view
->ds_stdio_pipe
);
693 close_error_pipe (0, NULL
);
694 view
->ds_stdio_pipe
= NULL
;
696 view_growbuf_free (view
);
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
);
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
;
712 g_free (view
->ds_string_data
);
713 view
->ds_string_data
= NULL
;
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
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
,
747 if (crit
== CCACHE_OFFSET
)
748 return (a
->cc_offset
< b
->cc_offset
);
750 if (a
->cc_line
< b
->cc_line
)
753 if (a
->cc_line
== b
->cc_line
) {
755 return (a
->cc_nroff_column
< b
->cc_nroff_column
);
757 return (a
->cc_column
< b
->cc_column
);
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
);
768 view_ccache_dump (WView
*view
)
771 offset_type offset
, line
, column
, nextline_offset
, filesize
;
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");
783 (void)setvbuf(f
, NULL
, _IONBF
, 0);
786 for (i
= 0; i
< view
->coord_cache
->len
; i
++) {
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
);
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",
814 for (column
= 0; TRUE
; column
++) {
815 view_coord_to_offset (view
, &offset
, line
, column
);
816 if (offset
>= nextline_offset
)
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)
831 static inline gboolean
832 is_nroff_sequence (WView
*view
, offset_type offset
)
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')
842 c0
= get_byte_indexed (view
, offset
, 0);
843 if (c0
== -1 || !is_printable(c0
))
846 c2
= get_byte_indexed (view
, offset
, 2);
847 if (c2
== -1 || !is_printable(c2
))
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''. */
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
;
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 */
870 /* continue the search in the upper half of the cache */
873 limit
= (limit
+ 1) / 2;
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''.
883 view_ccache_lookup (WView
*view
, struct coord_cache_entry
*coord
,
884 enum ccache_type lookup_what
)
887 struct coord_cache_entry
*cache
, current
, next
, entry
;
888 enum ccache_type sorter
;
896 if (!view
->coord_cache
) {
897 view
->coord_cache
= g_array_new (FALSE
, FALSE
, sizeof(struct coord_cache_entry
));
898 current
.cc_offset
= 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
;
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 */
914 if (i
+ 1 < view
->coord_cache
->len
)
915 limit
= cache
[i
+ 1].cc_offset
;
917 limit
= current
.cc_offset
+ VIEW_COORD_CACHE_GRANUL
;
920 nroff_state
= NROFF_START
;
921 for (; current
.cc_offset
< limit
; current
= next
) {
924 if ((c
= get_byte (view
, current
.cc_offset
)) == -1)
927 if (!coord_cache_entry_less (¤t
, 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 */
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. */
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
;
955 next
.cc_line
= current
.cc_line
+ 1;
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;
971 next
.cc_nroff_column
= 0;
974 /* Use all default values from above */
977 switch (nroff_state
) {
979 case NROFF_CONTINUATION
:
980 if (is_nroff_sequence (view
, current
.cc_offset
))
981 nroff_state
= NROFF_BACKSPACE
;
983 nroff_state
= NROFF_START
;
985 case NROFF_BACKSPACE
:
986 nroff_state
= NROFF_CONTINUATION
;
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')
998 if (i
+ 1 == view
->coord_cache
->len
&& entry
.cc_offset
!= cache
[i
].cc_offset
) {
999 g_array_append_val (view
->coord_cache
, entry
);
1003 if (lookup_what
== CCACHE_OFFSET
) {
1004 coord
->cc_offset
= current
.cc_offset
;
1006 coord
->cc_line
= current
.cc_line
;
1007 coord
->cc_column
= current
.cc_column
;
1008 coord
->cc_nroff_column
= current
.cc_nroff_column
;
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
;
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
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
*);
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
);
1082 col
= offset_rounddown (col
, columns
);
1083 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1084 view
->dpy_text_column
= 0;
1091 view_movement_fixups (WView
*view
, gboolean reset_search
)
1093 view_scroll_to_cursor (view
);
1095 view
->search_start
= view
->dpy_start
;
1096 view
->search_length
= 0;
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
);
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
;
1128 view
->dpy_start
= last_offset
;
1129 view_moveto_bol (view
);
1130 view_move_up (view
, lines_up
);
1132 view_movement_fixups (view
, TRUE
);
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
) {
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
);
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;
1161 filesize
= view_get_filesize (view
);
1162 view
->hex_cursor
= offset_doz(filesize
, 1);
1164 } else if (view
->text_wrap_mode
) {
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
);
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
;
1182 view
->dpy_start
= offset
;
1184 view_movement_fixups (view
, TRUE
);
1188 view_moveto (WView
*view
, offset_type line
, offset_type col
)
1192 view_coord_to_offset (view
, &offset
, line
, col
);
1193 view_moveto_offset (view
, offset
);
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
);
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
);
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)
1230 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
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));
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
;
1253 for (i
= 0; i
< lines
&& view
->hex_cursor
< limit
; i
++) {
1254 view
->hex_cursor
+= view
->bytes_per_line
;
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')
1279 view
->dpy_start
= new_offset
;
1283 offset_type line
, col
;
1285 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1287 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1289 view_movement_fixups (view
, (lines
!= 1));
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)
1301 if (!view
->hexview_in_text
)
1302 view
->hexedit_lownibble
= !view
->hexedit_lownibble
;
1303 } else if (view
->text_wrap_mode
) {
1306 if (view
->dpy_text_column
>= columns
)
1307 view
->dpy_text_column
-= columns
;
1309 view
->dpy_text_column
= 0;
1311 view_movement_fixups (view
, FALSE
);
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)
1323 if (!view
->hexview_in_text
)
1324 view
->hexedit_lownibble
= !view
->hexedit_lownibble
;
1325 } else if (view
->text_wrap_mode
) {
1328 view
->dpy_text_column
+= columns
;
1330 view_movement_fixups (view
, FALSE
);
1333 /* {{{ Toggling of viewer modes }}} */
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
;
1343 offset_rounddown (view
->dpy_start
, view
->bytes_per_line
);
1344 view
->widget
.options
|= W_WANT_CURSOR
;
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
;
1356 view_toggle_hexedit_mode (WView
*view
)
1358 view
->hexedit_mode
= !view
->hexedit_mode
;
1359 view
->dpy_bbar_dirty
= TRUE
;
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
);
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
;
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
;
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
);
1399 view_load (view
, command
, filename
, 0);
1402 view
->dpy_bbar_dirty
= TRUE
;
1406 /* {{{ Miscellaneous functions }}} */
1409 view_done (WView
*view
)
1411 /* Save current file position */
1412 if (mcview_remember_file_position
&& view
->filename
!= NULL
) {
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? */
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
);
1453 message (1, MSG_ERROR
, "%s", msg
);
1458 view_load_command_output (WView
*view
, const char *command
)
1462 view_close_datasource (view
);
1465 if ((fp
= popen (command
, "r")) == NULL
) {
1466 /* Avoid two messages. Message from stderr has priority. */
1468 if (!close_error_pipe (view_is_in_panel (view
) ? -1 : 1, NULL
))
1469 view_show_error (view
, _(" Cannot spawn child process "));
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. */
1480 if (!close_error_pipe (view_is_in_panel (view
) ? -1 : 1, NULL
))
1481 view_show_error (view
, _("Empty output from child filter"));
1488 view_load (WView
*view
, const char *command
, const char *file
,
1493 char tmp
[BUF_MEDIUM
];
1495 gboolean retval
= FALSE
;
1497 assert (view
->bytes_per_line
!= 0);
1500 /* Set up the state */
1501 view_set_datasource_none (view
);
1502 view
->filename
= g_strdup (file
);
1505 /* Clear the markers */
1507 for (i
= 0; i
< 10; i
++)
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') {
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
);
1525 /* Make sure we are working with a regular file */
1526 if (mc_fstat (fd
, &st
) == -1) {
1528 g_snprintf (tmp
, sizeof (tmp
), _(" Cannot stat \"%s\"\n %s "),
1529 file
, unix_error_string (errno
));
1530 view_show_error (view
, tmp
);
1534 if (!S_ISREG (st
.st_mode
)) {
1536 view_show_error (view
, _(" Cannot view: not a regular file "));
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
);
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
);
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) {
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
;
1583 /* {{{ Display management }}} */
1586 view_update_bytes_per_line (WView
*view
)
1588 const screen_dimen cols
= view
->data_area
.width
;
1594 bytes
= 4 * ((cols
- 8) / ((cols
< 80) ? 17 : 18));
1597 view
->bytes_per_line
= bytes
;
1598 view
->dirty
= max_dirt_limit
+ 1; /* To force refresh */
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
;
1608 offset_type filesize
;
1610 if (height
< 1 || right
< 4)
1612 if (view_may_still_grow (view
))
1614 filesize
= view_get_filesize (view
);
1616 if (filesize
== 0 || view
->dpy_end
== filesize
)
1618 else if (p
> (INT_MAX
/ 100))
1619 percent
= p
/ (filesize
/ 100);
1621 percent
= p
* 100 / filesize
;
1623 widget_move (view
, top
, right
- 4);
1624 tty_printf ("%3d%%", percent
);
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
;
1641 tty_setcolor (SELECTED_COLOR
);
1642 widget_move (view
, top
, left
);
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
1651 if (width
< file_label_width
+ 6)
1652 addstr ((char *) name_trunc (file_name
, width
));
1654 i
= (width
> 22 ? 22 : width
) - file_label_width
;
1655 tty_printf (file_label
, name_trunc (file_name
, i
));
1657 widget_move (view
, top
, left
+ 24);
1658 /* FIXME: the format strings need to be changed when offset_type changes */
1660 tty_printf (_("Offset 0x%08lx"), (unsigned long) view
->hex_cursor
);
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
));
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
));
1676 tty_printf (_(">= %s bytes"), size_trunc (filesize
));
1680 view_percent (view
, view
->hex_mode
1685 tty_setcolor (SELECTED_COLOR
);
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
,
1708 view_count_backspaces (WView
*view
, off_t offset
)
1711 while (offset
>= 2 * backspaces
1712 && get_byte (view
, offset
- 2 * backspaces
) == '\b')
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;
1732 if (ruler
== RULER_NONE
|| height
< 1)
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
);
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
1770 screen_dimen row
, col
;
1773 mark_t boldflag
= MARK_NORMAL
;
1774 struct hexedit_change_node
*curr
= view
->change_list
;
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
)) {
1788 for (row
= 0; get_byte (view
, from
) != -1 && row
< height
; row
++) {
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
]);
1799 tty_setcolor (NORMAL_COLOR
);
1801 for (bytes
= 0; bytes
< view
->bytes_per_line
; bytes
++, from
++) {
1803 if ((c
= get_byte (view
, from
)) == -1)
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 */
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
1821 /* Determine the value of the current byte */
1822 if (curr
!= NULL
&& from
== curr
->offset
) {
1827 /* Select the color for the hex number */
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
);
1839 tty_print_char (hex_char
[c
/ 16]);
1843 tty_print_char (hex_char
[c
% 16]);
1847 /* Print the separator */
1848 tty_setcolor (NORMAL_COLOR
);
1849 if (bytes
!= view
->bytes_per_line
- 1) {
1851 tty_print_char (' ');
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 ();
1862 tty_print_char (' ');
1868 /* Select the color for the character; this differs from the
1869 * hex color when boldflag == MARK_CURSOR */
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
))
1882 /* Print corresponding character on the text side */
1883 if (text_start
+ bytes
< width
) {
1884 widget_move (view
, top
+ row
, left
+ text_start
+ bytes
);
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
;
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
;
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
)) {
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') {
1931 if ((c_next
= get_byte_indexed (view
, from
, 1)) != -1
1932 && is_printable (c_next
)
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'))) {
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. */
1949 if (c_prev
== '_' && (c_next
!= '_' || view_count_backspaces (view
, from
) == 1))
1950 tty_setcolor (VIEW_UNDERLINED_COLOR
);
1952 tty_setcolor (MARKED_COLOR
);
1957 if ((c
== '\n') || (col
>= width
&& view
->text_wrap_mode
)) {
1960 if (c
== '\n' || row
>= height
)
1965 c
= get_byte_indexed(view
, from
, 1);
1966 if (c
== '\r' || c
== '\n')
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) {
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
))
1998 tty_setcolor (NORMAL_COLOR
);
2000 view
->dpy_end
= from
;
2003 /* Displays as much data from view->dpy_start as fits on the screen */
2005 display (WView
*view
)
2007 view_compute_areas (view
);
2008 if (view
->hex_mode
) {
2009 view_display_hex (view
);
2011 view_display_text (view
);
2013 view_display_status (view
);
2017 view_place_cursor (WView
*view
)
2019 const screen_dimen top
= view
->data_area
.top
;
2020 const screen_dimen left
= view
->data_area
.left
;
2023 col
= view
->cursor_col
;
2024 if (!view
->hexview_in_text
&& view
->hexedit_lownibble
)
2026 widget_move (&view
->widget
, top
+ view
->cursor_row
, left
+ col
);
2030 view_update (WView
*view
)
2032 static int dirt_limit
= 1;
2034 if (view
->dpy_bbar_dirty
) {
2035 view
->dpy_bbar_dirty
= FALSE
;
2037 buttonbar_redraw (view
->widget
.parent
);
2040 if (view
->dirty
> dirt_limit
) {
2041 /* Too many updates skipped -> force a update */
2044 /* Raise the update skipping limit */
2046 if (dirt_limit
> max_dirt_limit
)
2047 dirt_limit
= max_dirt_limit
;
2051 /* We have time to update the screen properly */
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 }}} */
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
;
2085 view_handle_editkey (WView
*view
, int key
)
2087 struct hexedit_change_node
*node
;
2090 /* Has there been a change at this position? */
2091 node
= view
->change_list
;
2092 while (node
&& (node
->offset
!= view
->hex_cursor
))
2095 if (!view
->hexview_in_text
) {
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');
2106 return MSG_NOT_HANDLED
;
2109 byte_val
= node
->value
;
2111 byte_val
= get_byte (view
, view
->hex_cursor
);
2113 if (view
->hexedit_lownibble
) {
2114 byte_val
= (byte_val
& 0xf0) | (hexvalue
);
2116 byte_val
= (byte_val
& 0x0f) | (hexvalue
<< 4);
2120 if (key
< 256 && (is_printable (key
) || (key
== '\n')))
2123 return MSG_NOT_HANDLED
;
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
);
2131 node
->value
= byte_val
;
2135 view_move_right (view
, 1);
2140 view_hexedit_save_changes (WView
*view
)
2142 struct hexedit_change_node
*curr
, *next
;
2146 if (view
->change_list
== NULL
)
2150 assert (view
->filename
!= NULL
);
2151 fp
= mc_open (view
->filename
, O_WRONLY
);
2155 for (curr
= view
->change_list
; curr
!= NULL
; curr
= next
) {
2158 if (mc_lseek (fp
, curr
->offset
, SEEK_SET
) == -1
2159 || mc_write (fp
, &(curr
->value
), 1) != 1)
2162 /* delete the saved item from the change list */
2163 view
->change_list
= next
;
2165 view_set_byte (view
, curr
->offset
, curr
->value
);
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
);
2180 error
= g_strdup (strerror (errno
));
2181 text
= g_strdup_printf (_(" Cannot save file: \n %s "), error
);
2183 (void) mc_close (fp
);
2185 answer
= query_dialog (_(" Save file "), text
, D_ERROR
,
2186 2, _("&Retry"), _("&Cancel"));
2194 /* {{{ Miscellaneous functions }}} */
2197 view_ok_to_quit (WView
*view
)
2201 if (view
->change_list
== NULL
)
2204 r
= query_dialog (_("Quit"),
2205 _(" File was modified, Save with exit? "), D_NORMAL
, 3,
2206 _("&Cancel quit"), _("&Yes"), _("&No"));
2210 return view_hexedit_save_changes (view
);
2212 view_hexedit_free_change_list (view
);
2220 my_define (Dlg_head
*h
, int idx
, const char *text
, void (*fn
) (WView
*),
2223 buttonbar_set_label_data (h
, idx
, text
, (buttonbarfn
) fn
, view
);
2226 /* {{{ Searching }}} */
2228 /* Case insensitive search of text in data */
2230 icase_search_p (WView
*view
, char *text
, char *data
, int nothing
)
2234 const int direction
= view
->direction
;
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
);
2253 view
->search_start
= q
- data
- lng
;
2255 view
->search_start
= strlen (data
) - (q
- data
);
2256 view
->search_length
= lng
;
2263 grow_string_buffer (char *text
, gulong
*size
)
2267 /* The grow steps */
2269 new = g_realloc (text
, *size
);
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;
2283 const int direction
= view
->direction
;
2284 offset_type pos
= *p
;
2290 if (pos
== 0 && direction
== -1)
2293 /* skip over all the possible zeros in the file */
2294 while ((ch
= get_byte (view
, pos
)) == 0) {
2295 if (pos
== 0 && direction
== -1)
2302 if (i
== 0 && (pos
!= 0 || direction
== -1)) {
2303 prev
= get_byte (view
, pos
- direction
);
2304 if ((prev
== -1) || (prev
== '\n'))
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 */
2316 if (pos
== 0 && direction
== -1)
2321 if (ch
== '\n' || ch
== '\0') {
2322 i
--; /* Strip newline/zero */
2331 /* If we are searching backwards, reverse the string */
2332 if (direction
== -1) {
2333 g_strreverse (buffer
+ 1);
2342 search_update_steps (WView
*view
)
2344 offset_type filesize
= view_get_filesize (view
);
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;
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
;
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
;
2373 d
= create_message (D_NORMAL
, _("Search"), _("Searching %s"), text
);
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);
2383 p
= search_start
- ((found_len
&& search_start
>= 1) ? 1 : 0);
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
;
2396 view_percent (view
, p
);
2399 if (got_interrupt ())
2402 forward_line_start
= p
;
2403 s
= get_line_at (view
, &p
, &t
);
2404 reverse_line_start
= p
;
2409 search_status
= (*search
) (view
, text
, s
+ 1, match_normal
);
2410 if (search_status
< 0) {
2415 if (search_status
== 0)
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] == '$')
2428 /* Record the position used to continue the search */
2429 if (view
->direction
== 1)
2430 t
+= forward_line_start
;
2432 t
= reverse_line_start
? reverse_line_start
+ 2 : 0;
2433 view
->search_start
+= t
;
2435 if (t
!= beginning
) {
2436 view
->dpy_start
= t
;
2442 disable_interrupt_key ();
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
2457 block_search (WView
*view
, const char *buffer
, int len
)
2459 int direction
= view
->direction
;
2460 const char *d
= buffer
;
2464 enable_interrupt_key ();
2466 e
= view
->search_start
+ ((view
->search_length
) ? 1 : 0);
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
;
2479 view_percent (view
, e
);
2482 if (got_interrupt ())
2485 b
= get_byte (view
, e
);
2489 disable_interrupt_key ();
2494 e
+= buffer
+ len
- 1 - d
;
2495 d
= buffer
+ len
- 1;
2501 while (get_byte (view
, e
) != -1) {
2502 if (e
>= view
->update_activate
) {
2503 view
->update_activate
+= view
->update_steps
;
2505 view_percent (view
, e
);
2508 if (got_interrupt ())
2511 b
= get_byte (view
, e
++);
2515 if (d
- buffer
== len
) {
2516 disable_interrupt_key ();
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.
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;
2544 view
->search_length
= 0;
2548 /* buffer will never be longer that text */
2549 buffer
= g_new (char, strlen (text
));
2552 /* First convert the string to a stream of bytes */
2557 /* Skip leading spaces */
2558 if (*text
== ' ' || *text
== '\t') {
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) {
2571 *cur
++ = (char) val
;
2576 /* Try quoted string, strip quotes */
2578 const char *next_quote
;
2581 next_quote
= strchr (text
, '"');
2583 memcpy (cur
, text
, next_quote
- text
);
2584 cur
+= next_quote
- text
;
2585 text
= next_quote
+ 1;
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"));
2601 view
->search_length
= 0;
2605 /* Then start the search */
2606 pos
= block_search (view
, buffer
, block_len
);
2610 if (pos
== INVALID_OFFSET
) {
2611 message (0, _("Search"), _(" Search string not found "));
2612 view
->search_length
= 0;
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
;
2627 regexp_view_search (WView
*view
, char *pattern
, char *string
,
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
) {
2640 g_free (old_pattern
);
2643 for (i
= 0; pattern
[i
] != '\0'; i
++) {
2644 if (isupper ((unsigned char) pattern
[i
])) {
2649 flags
|= REG_EXTENDED
;
2650 if (regcomp (&r
, pattern
, flags
)) {
2651 message (1, MSG_ERROR
, _(" Invalid regular expression "));
2654 old_pattern
= g_strdup (pattern
);
2655 old_type
= match_type
;
2657 if (regexec (&r
, string
, 1, pmatch
, 0) != 0)
2659 view
->search_length
= pmatch
[0].rm_eo
- pmatch
[0].rm_so
;
2660 view
->search_start
= pmatch
[0].rm_so
;
2665 do_regexp_search (WView
*view
)
2667 search (view
, view
->search_exp
, regexp_view_search
);
2668 /* Had a refresh here */
2674 do_normal_search (WView
*view
)
2677 hex_search (view
, view
->search_exp
);
2679 search (view
, view
->search_exp
, icase_search_p
);
2680 /* Had a refresh here */
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
2697 view_help_cmd (void)
2699 interactive_display (NULL
, "[Internal File Viewer]");
2702 /* Toggle between hexview and hexedit mode */
2704 view_toggle_hexedit_mode_cmd (WView
*view
)
2706 view_toggle_hexedit_mode (view
);
2710 /* Toggle between wrapped and unwrapped view */
2712 view_toggle_wrap_mode_cmd (WView
*view
)
2714 view_toggle_wrap_mode (view
);
2718 /* Toggle between hex view and text view */
2720 view_toggle_hex_mode_cmd (WView
*view
)
2722 view_toggle_hex_mode (view
);
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') {
2740 line
= strtoul (answer
, &answer_end
, 10);
2741 if (*answer_end
== '\0' && errno
== 0 && line
>= 1)
2742 view_moveto (view
, line
- 1, 0);
2750 view_moveto_addr_cmd (WView
*view
)
2752 char *line
, *error
, prompt
[BUF_SMALL
];
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
, "");
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
);
2765 message (D_ERROR
, _("Warning"), _(" Invalid address "));
2775 view_hexedit_save_changes_cmd (WView
*view
)
2777 (void) view_hexedit_save_changes (view
);
2780 /* {{{ Searching }}} */
2783 regexp_search (WView
*view
, int direction
)
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') {
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 }}} */
2808 view_regexp_search_cmd (WView
*view
)
2810 regexp_search (view
, 1);
2815 view_normal_search_cmd (WView
*view
)
2817 char *defval
, *exp
= NULL
;
2818 static char *last_search_string
;
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,
2832 {quick_button
, 2, 10, 5, SEARCH_DLG_HEIGHT
, N_("&OK"), 0, B_ENTER
,
2834 {quick_checkbox
, 3, SEARCH_DLG_WIDTH
, 4, SEARCH_DLG_HEIGHT
,
2835 N_("&Backwards"), 0, 0,
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,
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
)
2859 replace_backwards
= treplace_backwards
;
2861 if (exp
== NULL
|| exp
[0] == '\0')
2864 convert_from_input (exp
);
2866 g_free (last_search_string
);
2867 view
->search_exp
= last_search_string
= exp
;
2870 view
->direction
= replace_backwards
? -1 : 1;
2871 do_normal_search (view
);
2872 view
->last_search
= do_normal_search
;
2880 view_toggle_magic_mode_cmd (WView
*view
)
2882 view_toggle_magic_mode (view
);
2887 view_toggle_nroff_mode_cmd (WView
*view
)
2889 view_toggle_nroff_mode (view
);
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 */
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
);
2928 buttonbar_clear_label (h
, 2);
2930 my_define (h
, 6, Q_("ButtonBar|Save"),
2931 view_hexedit_save_changes_cmd
, view
);
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 */
2964 check_left_right_keys (WView
*view
, int c
)
2966 if (c
== KEY_LEFT
) {
2967 view_move_left (view
, 1);
2971 if (c
== KEY_RIGHT
) {
2972 view_move_right (view
, 1);
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;
2984 view
->dpy_text_column
= 0;
2989 if (c
== (KEY_M_CTRL
| KEY_RIGHT
)) {
2990 if (view
->dpy_text_column
<= OFFSETTYPE_MAX
- 10)
2991 view
->dpy_text_column
+= 10;
2993 view
->dpy_text_column
= OFFSETTYPE_MAX
;
2998 return MSG_NOT_HANDLED
;
3001 /* {{{ User-definable commands }}} */
3004 view_continue_search_cmd (WView
*view
)
3006 if (view
->last_search
) {
3007 view
->last_search (view
);
3009 /* if not... then ask for an expression */
3010 view_normal_search_cmd (view
);
3015 view_toggle_ruler_cmd (WView
*view
)
3017 static const enum ruler_type next
[3] = {
3023 assert ((size_t) ruler
< 3);
3024 ruler
= next
[(size_t) ruler
];
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
) {
3038 view_moveto_top ((WView
*) w
);
3040 static void view_cmk_moveto_bottom (void *w
, int n
) {
3042 view_moveto_bottom ((WView
*) w
);
3047 view_handle_key (WView
*view
, int c
)
3049 c
= convert_from_input_c (c
);
3051 if (view
->hex_mode
) {
3054 view
->hexview_in_text
= !view
->hexview_in_text
;
3059 view_moveto_bol (view
);
3064 view_move_left (view
, 1);
3068 view_moveto_eol (view
);
3072 view_move_right (view
, 1);
3076 if (view
->hexedit_mode
3077 && view_handle_editkey (view
, c
) == MSG_HANDLED
)
3081 if (check_left_right_keys (view
, c
))
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
))
3092 regexp_search (view
, -1);
3096 regexp_search (view
, 1);
3099 /* Continue search */
3104 view_continue_search_cmd (view
);
3109 view_toggle_ruler_cmd (view
);
3113 view_move_left (view
, 1);
3119 view_move_down (view
, 1);
3123 view_move_down (view
, (view
->data_area
.height
+ 1) / 2);
3127 view_move_up (view
, (view
->data_area
.height
+ 1) / 2);
3132 view_move_up (view
, 1);
3136 view_move_right (view
, 1);
3141 view_move_down (view
, view
->data_area
.height
);
3148 /* Unlike Ctrl-O, run a new shell if the subshell is not running. */
3154 view_move_up (view
, view
->data_area
.height
);
3158 view_move_up (view
, 2);
3162 view_move_down (view
, 2);
3166 view
->marks
[view
->marker
] = view
->dpy_start
;
3170 view
->dpy_start
= view
->marks
[view
->marker
];
3174 /* Use to indicate parent that we want to see the next/previous file */
3175 /* Does not work in panel mode */
3178 if (!view_is_in_panel (view
))
3179 view
->move_dir
= c
== XCTRL ('f') ? 1 : -1;
3184 if (view_ok_to_quit (view
))
3185 view
->want_to_quit
= TRUE
;
3190 do_select_codepage ();
3194 #endif /* HAVE_CHARSET */
3196 #ifdef MC_ENABLE_DEBUGGING_CODE
3197 case 't': /* mnemonic: "test" */
3198 view_ccache_dump (view
);
3202 if (c
>= '0' && c
<= '9')
3203 view
->marker
= c
- '0';
3206 return MSG_NOT_HANDLED
;
3211 view_event (WView
*view
, Gpm_Event
*event
, int *result
)
3215 *result
= MOU_NORMAL
;
3217 /* We are not interested in the release events */
3218 if (!(event
->type
& (GPM_DOWN
| GPM_DRAG
)))
3222 if ((event
->buttons
& GPM_B_UP
) && (event
->type
& GPM_DOWN
)) {
3223 view_move_up (view
, 2);
3226 if ((event
->buttons
& GPM_B_DOWN
) && (event
->type
& GPM_DOWN
)) {
3227 view_move_down (view
, 2);
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);
3239 } else if (x
< view
->data_area
.width
* 3/4) {
3240 /* ignore the click */
3242 view_move_right (view
, 1);
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);
3252 view_move_up (view
, 1);
3254 } else if (y
< view
->data_area
.top
+ view
->data_area
.height
* 2/3) {
3255 /* ignore the click */
3257 if (mouse_move_pages_viewer
)
3258 view_move_down (view
, view
->data_area
.height
/ 2);
3260 view_move_down (view
, 1);
3267 *result
= MOU_REPEAT
;
3271 /* Real view only */
3273 real_view_event (Gpm_Event
*event
, void *x
)
3275 WView
*view
= (WView
*) x
;
3278 if (view_event (view
, event
, &result
))
3284 view_adjust_size (Dlg_head
*h
)
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 */
3301 view_dialog_callback (Dlg_head
*h
, dlg_msg_t msg
, int parm
)
3305 view_adjust_size (h
);
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
)
3325 /* Create dialog and widgets, put them on the dialog */
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
);
3341 *move_dir_p
= wview
->move_dir
;
3346 destroy_dlg (view_dlg
);
3351 /* {{{ Miscellaneous functions }}} */
3356 WView
*view
= (WView
*) v
;
3359 /* If the user is busy typing, wait until he finishes to update the
3362 if (!hook_present (idle_hook
, view_hook
))
3363 add_hook (&idle_hook
, view_hook
, v
);
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
;
3376 view_load (view
, 0, panel
->dir
.list
[panel
->selected
].fname
, 0);
3380 /* {{{ Event handling }}} */
3383 view_callback (Widget
*w
, widget_msg_t msg
, int parm
)
3385 WView
*view
= (WView
*) w
;
3387 Dlg_head
*h
= view
->widget
.parent
;
3389 view_compute_areas (view
);
3390 view_update_bytes_per_line (view
);
3394 if (view_is_in_panel (view
))
3395 add_hook (&select_file_hook
, view_hook
, view
);
3397 view
->dpy_bbar_dirty
= TRUE
;
3406 view_place_cursor (view
);
3410 i
= view_handle_key ((WView
*) view
, parm
);
3411 if (view
->want_to_quit
&& !view_is_in_panel (view
))
3419 view
->dpy_bbar_dirty
= TRUE
;
3423 case WIDGET_DESTROY
:
3425 if (view_is_in_panel (view
))
3426 delete_hook (&select_file_hook
, view_hook
);
3430 return default_proc (msg
, parm
);
3434 /* {{{ External interface }}} */
3437 view_new (int y
, int x
, int cols
, int lines
, int is_panel
)
3439 WView
*view
= g_new0 (WView
, 1);
3442 init_widget (&view
->widget
, y
, x
, lines
, cols
,
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;
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 */
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
;
3487 for (i
= 0; i
< sizeof(view
->marks
) / sizeof(view
->marks
[0]); i
++)
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
);