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.
42 #include <sys/types.h>
48 #include "cmd.h" /* For view_other_cmd */
49 #include "dialog.h" /* Needed by widget.h */
50 #include "widget.h" /* Needed for buttonbar_new */
54 #include "key.h" /* For mi_getch() */
57 #include "wtools.h" /* For query_set_sel() */
59 #include "panel.h" /* Needed for current_panel and other_panel */
62 #include "main.h" /* slow_terminal */
66 #include "selcodepage.h"
68 /* Block size for reading files in parts */
69 #define VIEW_PAGE_SIZE ((size_t) 8192)
70 #define VIEW_COORD_CACHE_GRANUL 1024
72 typedef unsigned char byte
;
74 /* Offset in bytes into a file */
75 typedef unsigned long offset_type
;
76 #define INVALID_OFFSET ((offset_type) -1)
77 #define OFFSETTYPE_MAX (~((offset_type) 0))
78 #define OFFSETTYPE_PRIX "lX"
79 #define OFFSETTYPE_PRId "lu"
81 /* A width or height on the screen */
82 typedef unsigned int screen_dimen
;
84 /* A cache entry for mapping offsets into line/column pairs and vice versa.
85 * cc_offset, cc_line, and cc_column are the 0-based values of the offset,
86 * line and column of that cache entry. cc_nroff_column is the column
87 * corresponding to cc_offset in nroff mode.
89 struct coord_cache_entry
{
90 offset_type cc_offset
;
92 offset_type cc_column
;
93 offset_type cc_nroff_column
;
96 /* A node for building a change list on change_list */
97 struct hexedit_change_node
{
98 struct hexedit_change_node
*next
;
103 /* data sources of the view */
105 DS_NONE
, /* No data available */
106 DS_STDIO_PIPE
, /* Data comes from a pipe using popen/pclose */
107 DS_VFS_PIPE
, /* Data comes from a piped-in VFS file */
108 DS_FILE
, /* Data comes from a VFS file */
109 DS_STRING
/* Data comes from a string in memory */
113 screen_dimen top
, left
;
114 screen_dimen height
, width
;
120 char *filename
; /* Name of the file */
121 char *command
; /* Command used to pipe data in */
123 enum view_ds datasource
; /* Where the displayed data comes from */
125 /* stdio pipe data source */
126 FILE *ds_stdio_pipe
; /* Output of a shell command */
128 /* vfs pipe data source */
129 int ds_vfs_pipe
; /* Non-seekable vfs file descriptor */
131 /* vfs file data source */
132 int ds_file_fd
; /* File with random access */
133 off_t ds_file_filesize
; /* Size of the file */
134 off_t ds_file_offset
; /* Offset of the currently loaded data */
135 byte
*ds_file_data
; /* Currently loaded data */
136 size_t ds_file_datalen
; /* Number of valid bytes in file_data */
137 size_t ds_file_datasize
; /* Number of allocated bytes in file_data */
139 /* string data source */
140 byte
*ds_string_data
; /* The characters of the string */
141 size_t ds_string_len
; /* The length of the string */
143 /* Growing buffers information */
144 gboolean growbuf_in_use
; /* Use the growing buffers? */
145 byte
**growbuf_blockptr
; /* Pointer to the block pointers */
146 size_t growbuf_blocks
; /* The number of blocks in *block_ptr */
147 size_t growbuf_lastindex
; /* Number of bytes in the last page of the
149 gboolean growbuf_finished
; /* TRUE when all data has been read. */
152 gboolean hex_mode
; /* Hexview or Hexedit */
153 gboolean hexedit_mode
; /* Hexedit */
154 gboolean hexview_in_text
; /* Is the hexview cursor in the text area? */
155 gboolean text_nroff_mode
; /* Nroff-style highlighting */
156 gboolean text_wrap_mode
; /* Wrap text lines to fit them on the screen */
157 gboolean magic_mode
; /* Preprocess the file using external programs */
159 /* Additional editor state */
160 gboolean hexedit_lownibble
; /* Are we editing the last significant nibble? */
161 GArray
*coord_cache
; /* Cache for mapping offsets to cursor positions */
163 /* Display information */
164 screen_dimen dpy_frame_size
;/* Size of the frame surrounding the real viewer */
165 offset_type dpy_start
; /* Offset of the displayed data */
166 offset_type dpy_end
; /* Offset after the displayed data */
167 offset_type dpy_text_column
;/* Number of skipped columns in non-wrap
169 offset_type hex_cursor
; /* Hexview cursor position in file */
170 screen_dimen cursor_col
; /* Cursor column */
171 screen_dimen cursor_row
; /* Cursor row */
172 struct hexedit_change_node
*change_list
; /* Linked list of changes */
173 struct area status_area
; /* Where the status line is displayed */
174 struct area ruler_area
; /* Where the ruler is displayed */
175 struct area data_area
; /* Where the data is displayed */
177 int dirty
; /* Number of skipped updates */
178 gboolean dpy_bbar_dirty
; /* Does the button bar need to be updated? */
181 int bytes_per_line
; /* Number of bytes per line in hex mode */
183 /* Search variables */
184 offset_type search_start
; /* First character to start searching from */
185 offset_type search_length
; /* Length of found string or 0 if none was found */
186 char *search_exp
; /* The search expression */
187 int direction
; /* 1= forward; -1 backward */
188 void (*last_search
)(WView
*);
189 /* Pointer to the last search command */
190 gboolean want_to_quit
; /* Prepare for cleanup ... */
193 int marker
; /* mark to use */
194 offset_type marks
[10]; /* 10 marks: 0..9 */
196 int move_dir
; /* return value from widget:
198 * -1 view previous file
202 offset_type update_steps
; /* The number of bytes between percent
204 offset_type update_activate
;/* Last point where we updated the status */
208 /* {{{ Global Variables }}} */
210 /* Maxlimit for skipping updates */
211 int max_dirt_limit
= 10;
213 /* If set, show a ruler */
214 static enum ruler_type
{
218 } ruler
= RULER_NONE
;
220 /* Scrolling is done in pages or line increments */
221 int mouse_move_pages_viewer
= 1;
223 /* wrap mode default */
224 int global_wrap_mode
= 1;
226 int default_hex_mode
= 0;
227 int default_magic_flag
= 1;
228 int default_nroff_flag
= 1;
229 int altered_hex_mode
= 0;
230 int altered_magic_flag
= 0;
231 int altered_nroff_flag
= 0;
233 static const char hex_char
[] = "0123456789ABCDEF";
235 int mcview_remember_file_position
= FALSE
;
237 /* {{{ Function Prototypes }}} */
239 /* Our widget callback */
240 static cb_ret_t
view_callback (Widget
*, widget_msg_t
, int);
242 static int regexp_view_search (WView
* view
, char *pattern
, char *string
,
244 static void view_labels (WView
* view
);
246 static void view_init_growbuf (WView
*);
247 static void view_place_cursor (WView
*view
);
248 static void display (WView
*);
249 static void view_done (WView
*);
251 /* {{{ Helper Functions }}} */
253 /* difference or zero */
254 static inline screen_dimen
255 dimen_doz (screen_dimen a
, screen_dimen b
)
257 return (a
>= b
) ? a
- b
: 0;
260 static inline screen_dimen
261 dimen_min (screen_dimen a
, screen_dimen b
)
263 return (a
< b
) ? a
: b
;
266 static inline offset_type
267 offset_doz (offset_type a
, offset_type b
)
269 return (a
>= b
) ? a
- b
: 0;
272 static inline offset_type
273 offset_rounddown (offset_type a
, offset_type b
)
279 /* {{{ Simple Primitive Functions for WView }}} */
281 static inline gboolean
282 view_is_in_panel (WView
*view
)
284 return (view
->dpy_frame_size
!= 0);
288 view_compute_areas (WView
*view
)
290 struct area view_area
;
291 screen_dimen height
, rest
, y
;
293 /* The viewer is surrounded by a frame of size view->dpy_frame_size.
294 * Inside that frame, there are: The status line (at the top),
295 * the data area and an optional ruler, which is shown above or
296 * below the data area. */
298 view_area
.top
= view
->dpy_frame_size
;
299 view_area
.left
= view
->dpy_frame_size
;
300 view_area
.height
= dimen_doz(view
->widget
.lines
, 2 * view
->dpy_frame_size
);
301 view_area
.width
= dimen_doz(view
->widget
.cols
, 2 * view
->dpy_frame_size
);
303 /* Most coordinates of the areas equal those of the whole viewer */
304 view
->status_area
= view_area
;
305 view
->ruler_area
= view_area
;
306 view
->data_area
= view_area
;
308 /* Compute the heights of the areas */
309 rest
= view_area
.height
;
311 height
= dimen_min(rest
, 1);
312 view
->status_area
.height
= height
;
315 height
= dimen_min(rest
, (ruler
== RULER_NONE
|| view
->hex_mode
) ? 0 : 2);
316 view
->ruler_area
.height
= height
;
319 view
->data_area
.height
= rest
;
321 /* Compute the position of the areas */
324 view
->status_area
.top
= y
;
325 y
+= view
->status_area
.height
;
327 if (ruler
== RULER_TOP
) {
328 view
->ruler_area
.top
= y
;
329 y
+= view
->ruler_area
.height
;
332 view
->data_area
.top
= y
;
333 y
+= view
->data_area
.height
;
335 if (ruler
== RULER_BOTTOM
) {
336 view
->ruler_area
.top
= y
;
337 y
+= view
->ruler_area
.height
;
342 view_hexedit_free_change_list (WView
*view
)
344 struct hexedit_change_node
*curr
, *next
;
346 for (curr
= view
->change_list
; curr
!= NULL
; curr
= next
) {
350 view
->change_list
= NULL
;
354 /* {{{ Growing buffer }}} */
357 view_init_growbuf (WView
*view
)
359 view
->growbuf_in_use
= TRUE
;
360 view
->growbuf_blockptr
= NULL
;
361 view
->growbuf_blocks
= 0;
362 view
->growbuf_lastindex
= VIEW_PAGE_SIZE
;
363 view
->growbuf_finished
= FALSE
;
367 view_growbuf_free (WView
*view
)
371 assert (view
->growbuf_in_use
);
373 for (i
= 0; i
< view
->growbuf_blocks
; i
++)
374 g_free (view
->growbuf_blockptr
[i
]);
375 g_free (view
->growbuf_blockptr
);
376 view
->growbuf_blockptr
= NULL
;
377 view
->growbuf_in_use
= FALSE
;
381 view_growbuf_filesize (WView
*view
)
383 assert(view
->growbuf_in_use
);
385 if (view
->growbuf_blocks
== 0)
388 return ((offset_type
) view
->growbuf_blocks
- 1) * VIEW_PAGE_SIZE
389 + view
->growbuf_lastindex
;
392 /* Copies the output from the pipe to the growing buffer, until either
393 * the end-of-pipe is reached or the interval [0..ofs) of the growing
394 * buffer is completely filled. */
396 view_growbuf_read_until (WView
*view
, offset_type ofs
)
403 assert (view
->growbuf_in_use
);
405 if (view
->growbuf_finished
)
409 while (view_growbuf_filesize (view
) < ofs
|| short_read
) {
410 if (view
->growbuf_lastindex
== VIEW_PAGE_SIZE
) {
411 /* Append a new block to the growing buffer */
412 byte
*newblock
= g_try_malloc (VIEW_PAGE_SIZE
);
413 byte
**newblocks
= g_try_malloc (sizeof (*newblocks
) * (view
->growbuf_blocks
+ 1));
414 if (!newblock
|| !newblocks
) {
419 memcpy (newblocks
, view
->growbuf_blockptr
, sizeof (*newblocks
) * view
->growbuf_blocks
);
420 g_free (view
->growbuf_blockptr
);
421 view
->growbuf_blockptr
= newblocks
;
422 view
->growbuf_blockptr
[view
->growbuf_blocks
++] = newblock
;
423 view
->growbuf_lastindex
= 0;
425 p
= view
->growbuf_blockptr
[view
->growbuf_blocks
- 1] + view
->growbuf_lastindex
;
426 bytesfree
= VIEW_PAGE_SIZE
- view
->growbuf_lastindex
;
428 if (view
->datasource
== DS_STDIO_PIPE
) {
429 nread
= fread (p
, 1, bytesfree
, view
->ds_stdio_pipe
);
431 view
->growbuf_finished
= TRUE
;
432 (void) pclose (view
->ds_stdio_pipe
);
434 close_error_pipe (D_NORMAL
, NULL
);
435 view
->ds_stdio_pipe
= NULL
;
439 assert (view
->datasource
== DS_VFS_PIPE
);
441 nread
= mc_read (view
->ds_vfs_pipe
, p
, bytesfree
);
442 } while (nread
== -1 && errno
== EINTR
);
443 if (nread
== -1 || nread
== 0) {
444 view
->growbuf_finished
= TRUE
;
445 (void) mc_close (view
->ds_vfs_pipe
);
446 view
->ds_vfs_pipe
= -1;
450 short_read
= ((size_t)nread
< bytesfree
);
451 view
->growbuf_lastindex
+= nread
;
456 get_byte_growing_buffer (WView
*view
, offset_type byte_index
)
458 offset_type pageno
= byte_index
/ VIEW_PAGE_SIZE
;
459 offset_type pageindex
= byte_index
% VIEW_PAGE_SIZE
;
461 assert (view
->growbuf_in_use
);
463 if ((size_t) pageno
!= pageno
)
466 view_growbuf_read_until (view
, byte_index
+ 1);
467 if (view
->growbuf_blocks
== 0)
469 if (pageno
< view
->growbuf_blocks
- 1)
470 return view
->growbuf_blockptr
[pageno
][pageindex
];
471 if (pageno
== view
->growbuf_blocks
- 1 && pageindex
< view
->growbuf_lastindex
)
472 return view
->growbuf_blockptr
[pageno
][pageindex
];
476 /* {{{ Data sources }}} */
479 The data source provides the viewer with data from either a file, a
480 string or the output of a command. The get_byte() function can be
481 used to get the value of a byte at a specific offset. If the offset
482 is out of range, -1 is returned. The function get_byte_indexed(a,b)
483 returns the byte at the offset a+b, or -1 if a+b is out of range.
485 The view_set_byte() function has the effect that later calls to
486 get_byte() will return the specified byte for this offset. This
487 function is designed only for use by the hexedit component after
488 saving its changes. Inspect the source before you want to use it for
491 The view_get_filesize() function returns the current size of the
492 data source. If the growing buffer is used, this size may increase
493 later on. Use the view_may_still_grow() function when you want to
494 know if the size can change later.
498 view_get_filesize (WView
*view
)
500 switch (view
->datasource
) {
505 return view_growbuf_filesize (view
);
507 return view
->ds_file_filesize
;
509 return view
->ds_string_len
;
511 assert(!"Unknown datasource type");
516 static inline gboolean
517 view_may_still_grow (WView
*view
)
519 return (view
->growbuf_in_use
&& !view
->growbuf_finished
);
522 /* returns TRUE if the idx lies in the half-open interval
523 * [offset; offset + size), FALSE otherwise.
525 static inline gboolean
526 already_loaded (offset_type offset
, offset_type idx
, size_t size
)
528 return (offset
<= idx
&& idx
- offset
< size
);
532 view_file_load_data (WView
*view
, offset_type byte_index
)
534 offset_type blockoffset
;
538 assert (view
->datasource
== DS_FILE
);
540 if (already_loaded (view
->ds_file_offset
, byte_index
, view
->ds_file_datalen
))
543 if (byte_index
>= view
->ds_file_filesize
)
546 blockoffset
= offset_rounddown (byte_index
, view
->ds_file_datasize
);
547 if (mc_lseek (view
->ds_file_fd
, blockoffset
, SEEK_SET
) == -1)
551 while (bytes_read
< view
->ds_file_datasize
) {
552 res
= mc_read (view
->ds_file_fd
, view
->ds_file_data
+ bytes_read
, view
->ds_file_datasize
- bytes_read
);
557 bytes_read
+= (size_t) res
;
559 view
->ds_file_offset
= blockoffset
;
560 if (bytes_read
> view
->ds_file_filesize
- view
->ds_file_offset
) {
561 /* the file has grown in the meantime -- stick to the old size */
562 view
->ds_file_datalen
= view
->ds_file_filesize
- view
->ds_file_offset
;
564 view
->ds_file_datalen
= bytes_read
;
569 view
->ds_file_datalen
= 0;
573 get_byte_none (WView
*view
, offset_type byte_index
)
575 assert (view
->datasource
== DS_NONE
);
582 get_byte_file (WView
*view
, offset_type byte_index
)
584 assert (view
->datasource
== DS_FILE
);
586 view_file_load_data (view
, byte_index
);
587 if (already_loaded(view
->ds_file_offset
, byte_index
, view
->ds_file_datalen
))
588 return view
->ds_file_data
[byte_index
- view
->ds_file_offset
];
593 get_byte_string (WView
*view
, offset_type byte_index
)
595 assert (view
->datasource
== DS_STRING
);
596 if (byte_index
< view
->ds_string_len
)
597 return view
->ds_string_data
[byte_index
];
602 get_byte (WView
*view
, offset_type offset
)
604 switch (view
->datasource
) {
607 return get_byte_growing_buffer (view
, offset
);
609 return get_byte_file (view
, offset
);
611 return get_byte_string (view
, offset
);
613 return get_byte_none (view
, offset
);
615 assert(!"Unknown datasource type");
620 get_byte_indexed (WView
*view
, offset_type base
, offset_type ofs
)
622 if (base
<= OFFSETTYPE_MAX
- ofs
)
623 return get_byte (view
, base
+ ofs
);
628 view_set_byte (WView
*view
, offset_type offset
, byte b
)
631 assert (offset
< view_get_filesize (view
));
632 assert (view
->datasource
== DS_FILE
);
633 view
->ds_file_datalen
= 0; /* just force reloading */
637 view_set_datasource_none (WView
*view
)
639 view
->datasource
= DS_NONE
;
643 view_set_datasource_vfs_pipe (WView
*view
, int fd
)
646 view
->datasource
= DS_VFS_PIPE
;
647 view
->ds_vfs_pipe
= fd
;
649 view_init_growbuf (view
);
653 view_set_datasource_stdio_pipe (WView
*view
, FILE *fp
)
656 view
->datasource
= DS_STDIO_PIPE
;
657 view
->ds_stdio_pipe
= fp
;
659 view_init_growbuf (view
);
663 view_set_datasource_string (WView
*view
, const char *s
)
665 view
->datasource
= DS_STRING
;
666 view
->ds_string_data
= (byte
*) g_strdup (s
);
667 view
->ds_string_len
= strlen (s
);
671 view_set_datasource_file (WView
*view
, int fd
, const struct stat
*st
)
673 view
->datasource
= DS_FILE
;
674 view
->ds_file_fd
= fd
;
675 view
->ds_file_filesize
= st
->st_size
;
676 view
->ds_file_offset
= 0;
677 view
->ds_file_data
= g_malloc (4096);
678 view
->ds_file_datalen
= 0;
679 view
->ds_file_datasize
= 4096;
683 view_close_datasource (WView
*view
)
685 switch (view
->datasource
) {
689 if (view
->ds_stdio_pipe
!= NULL
) {
690 (void) pclose (view
->ds_stdio_pipe
);
692 close_error_pipe (D_NORMAL
, NULL
);
693 view
->ds_stdio_pipe
= NULL
;
695 view_growbuf_free (view
);
698 if (view
->ds_vfs_pipe
!= -1) {
699 (void) mc_close (view
->ds_vfs_pipe
);
700 view
->ds_vfs_pipe
= -1;
702 view_growbuf_free (view
);
705 (void) mc_close (view
->ds_file_fd
);
706 view
->ds_file_fd
= -1;
707 g_free (view
->ds_file_data
);
708 view
->ds_file_data
= NULL
;
711 g_free (view
->ds_string_data
);
712 view
->ds_string_data
= NULL
;
715 assert (!"Unknown datasource type");
717 view
->datasource
= DS_NONE
;
720 /* {{{ The Coordinate Cache }}} */
723 This cache provides you with a fast lookup to map file offsets into
724 line/column pairs and vice versa. The interface to the mapping is
725 provided by the functions view_coord_to_offset() and
726 view_offset_to_coord().
728 The cache is implemented as a simple sorted array holding entries
729 that map some of the offsets to their line/column pair. Entries that
730 are not cached themselves are interpolated (exactly) from their
731 neighbor entries. The algorithm used for determining the line/column
732 for a specific offset needs to be kept synchronized with the one used
741 static inline gboolean
742 coord_cache_entry_less (const struct coord_cache_entry
*a
,
743 const struct coord_cache_entry
*b
, enum ccache_type crit
,
746 if (crit
== CCACHE_OFFSET
)
747 return (a
->cc_offset
< b
->cc_offset
);
749 if (a
->cc_line
< b
->cc_line
)
752 if (a
->cc_line
== b
->cc_line
) {
754 return (a
->cc_nroff_column
< b
->cc_nroff_column
);
756 return (a
->cc_column
< b
->cc_column
);
762 #ifdef MC_ENABLE_DEBUGGING_CODE
763 static void view_coord_to_offset (WView
*, offset_type
*, offset_type
, offset_type
);
764 static void view_offset_to_coord (WView
*, offset_type
*, offset_type
*, offset_type
);
767 view_ccache_dump (WView
*view
)
770 offset_type offset
, line
, column
, nextline_offset
, filesize
;
772 const struct coord_cache_entry
*cache
;
774 assert (view
->coord_cache
!= NULL
);
776 filesize
= view_get_filesize (view
);
777 cache
= &(g_array_index (view
->coord_cache
, struct coord_cache_entry
, 0));
779 f
= fopen("mcview-ccache.out", "w");
782 (void)setvbuf(f
, NULL
, _IONBF
, 0);
785 for (i
= 0; i
< view
->coord_cache
->len
; i
++) {
788 "offset %8"OFFSETTYPE_PRId
" "
789 "line %8"OFFSETTYPE_PRId
" "
790 "column %8"OFFSETTYPE_PRId
" "
791 "nroff_column %8"OFFSETTYPE_PRId
"\n",
792 (unsigned int) i
, cache
[i
].cc_offset
, cache
[i
].cc_line
,
793 cache
[i
].cc_column
, cache
[i
].cc_nroff_column
);
795 (void)fprintf (f
, "\n");
797 /* offset -> line/column translation */
798 for (offset
= 0; offset
< filesize
; offset
++) {
799 view_offset_to_coord (view
, &line
, &column
, offset
);
801 "offset %8"OFFSETTYPE_PRId
" "
802 "line %8"OFFSETTYPE_PRId
" "
803 "column %8"OFFSETTYPE_PRId
"\n",
804 offset
, line
, column
);
807 /* line/column -> offset translation */
808 for (line
= 0; TRUE
; line
++) {
809 view_coord_to_offset (view
, &nextline_offset
, line
+ 1, 0);
810 (void)fprintf (f
, "nextline_offset %8"OFFSETTYPE_PRId
"\n",
813 for (column
= 0; TRUE
; column
++) {
814 view_coord_to_offset (view
, &offset
, line
, column
);
815 if (offset
>= nextline_offset
)
818 (void)fprintf (f
, "line %8"OFFSETTYPE_PRId
" column %8"OFFSETTYPE_PRId
" offset %8"OFFSETTYPE_PRId
"\n",
819 line
, column
, offset
);
822 if (nextline_offset
>= filesize
- 1)
830 static inline gboolean
831 is_nroff_sequence (WView
*view
, offset_type offset
)
835 /* The following commands are ordered to speed up the calculation. */
837 c1
= get_byte_indexed (view
, offset
, 1);
838 if (c1
== -1 || c1
!= '\b')
841 c0
= get_byte_indexed (view
, offset
, 0);
842 if (c0
== -1 || !is_printable(c0
))
845 c2
= get_byte_indexed (view
, offset
, 2);
846 if (c2
== -1 || !is_printable(c2
))
849 return (c0
== c2
|| c0
== '_' || (c0
== '+' && c2
== 'o'));
852 /* Find and return the index of the last cache entry that is
853 * smaller than ''coord'', according to the criterion ''sort_by''. */
855 view_ccache_find (WView
*view
, const struct coord_cache_entry
*cache
,
856 const struct coord_cache_entry
*coord
, enum ccache_type sort_by
)
858 guint base
, i
, limit
;
860 limit
= view
->coord_cache
->len
;
865 i
= base
+ limit
/ 2;
866 if (coord_cache_entry_less (coord
, &cache
[i
], sort_by
, view
->text_nroff_mode
)) {
867 /* continue the search in the lower half of the cache */
869 /* continue the search in the upper half of the cache */
872 limit
= (limit
+ 1) / 2;
877 /* Look up the missing components of ''coord'', which are given by
878 * ''lookup_what''. The function returns the smallest value that
879 * matches the existing components of ''coord''.
882 view_ccache_lookup (WView
*view
, struct coord_cache_entry
*coord
,
883 enum ccache_type lookup_what
)
886 struct coord_cache_entry
*cache
, current
, next
, entry
;
887 enum ccache_type sorter
;
895 if (!view
->coord_cache
) {
896 view
->coord_cache
= g_array_new (FALSE
, FALSE
, sizeof(struct coord_cache_entry
));
897 current
.cc_offset
= 0;
899 current
.cc_column
= 0;
900 current
.cc_nroff_column
= 0;
901 g_array_append_val (view
->coord_cache
, current
);
904 sorter
= (lookup_what
== CCACHE_OFFSET
) ? CCACHE_LINECOL
: CCACHE_OFFSET
;
907 /* find the two neighbor entries in the cache */
908 cache
= &(g_array_index (view
->coord_cache
, struct coord_cache_entry
, 0));
909 i
= view_ccache_find (view
, cache
, coord
, sorter
);
910 /* now i points to the lower neighbor in the cache */
913 if (i
+ 1 < view
->coord_cache
->len
)
914 limit
= cache
[i
+ 1].cc_offset
;
916 limit
= current
.cc_offset
+ VIEW_COORD_CACHE_GRANUL
;
919 nroff_state
= NROFF_START
;
920 for (; current
.cc_offset
< limit
; current
= next
) {
923 if ((c
= get_byte (view
, current
.cc_offset
)) == -1)
926 if (!coord_cache_entry_less (¤t
, coord
, sorter
, view
->text_nroff_mode
)) {
927 if (lookup_what
== CCACHE_OFFSET
928 && view
->text_nroff_mode
929 && nroff_state
!= NROFF_START
) {
930 /* don't break here */
936 /* Provide useful default values for ''next'' */
937 next
.cc_offset
= current
.cc_offset
+ 1;
938 next
.cc_line
= current
.cc_line
;
939 next
.cc_column
= current
.cc_column
+ 1;
940 next
.cc_nroff_column
= current
.cc_nroff_column
+ 1;
942 /* and override some of them as necessary. */
944 nextc
= get_byte_indexed(view
, current
.cc_offset
, 1);
946 /* Ignore '\r' if it is followed by '\r' or '\n'. If it is
947 * followed by anything else, it is a Mac line ending and
948 * produces a line break.
950 if (nextc
== '\r' || nextc
== '\n') {
951 next
.cc_column
= current
.cc_column
;
952 next
.cc_nroff_column
= current
.cc_nroff_column
;
954 next
.cc_line
= current
.cc_line
+ 1;
956 next
.cc_nroff_column
= 0;
959 } else if (nroff_state
== NROFF_BACKSPACE
) {
960 next
.cc_nroff_column
= current
.cc_nroff_column
- 1;
962 } else if (c
== '\t') {
963 next
.cc_column
= offset_rounddown (current
.cc_column
, 8) + 8;
964 next
.cc_nroff_column
=
965 offset_rounddown (current
.cc_nroff_column
, 8) + 8;
967 } else if (c
== '\n') {
968 next
.cc_line
= current
.cc_line
+ 1;
970 next
.cc_nroff_column
= 0;
973 /* Use all default values from above */
976 switch (nroff_state
) {
978 case NROFF_CONTINUATION
:
979 if (is_nroff_sequence (view
, current
.cc_offset
))
980 nroff_state
= NROFF_BACKSPACE
;
982 nroff_state
= NROFF_START
;
984 case NROFF_BACKSPACE
:
985 nroff_state
= NROFF_CONTINUATION
;
989 /* Cache entries must guarantee that for each i < j,
990 * line[i] <= line[j] and column[i] < column[j]. In the case of
991 * nroff sequences and '\r' characters, this is not guaranteed,
992 * so we cannot save them. */
993 if (nroff_state
== NROFF_START
&& c
!= '\r')
997 if (i
+ 1 == view
->coord_cache
->len
&& entry
.cc_offset
!= cache
[i
].cc_offset
) {
998 g_array_append_val (view
->coord_cache
, entry
);
1002 if (lookup_what
== CCACHE_OFFSET
) {
1003 coord
->cc_offset
= current
.cc_offset
;
1005 coord
->cc_line
= current
.cc_line
;
1006 coord
->cc_column
= current
.cc_column
;
1007 coord
->cc_nroff_column
= current
.cc_nroff_column
;
1012 view_coord_to_offset (WView
*view
, offset_type
*ret_offset
,
1013 offset_type line
, offset_type column
)
1015 struct coord_cache_entry coord
;
1017 coord
.cc_line
= line
;
1018 coord
.cc_column
= column
;
1019 coord
.cc_nroff_column
= column
;
1020 view_ccache_lookup (view
, &coord
, CCACHE_OFFSET
);
1021 *ret_offset
= coord
.cc_offset
;
1025 view_offset_to_coord (WView
*view
, offset_type
*ret_line
,
1026 offset_type
*ret_column
, offset_type offset
)
1028 struct coord_cache_entry coord
;
1030 coord
.cc_offset
= offset
;
1031 view_ccache_lookup (view
, &coord
, CCACHE_LINECOL
);
1032 *ret_line
= coord
.cc_line
;
1033 *ret_column
= (view
->text_nroff_mode
)
1034 ? coord
.cc_nroff_column
1038 /* {{{ Cursor Movement }}} */
1041 The following variables have to do with the current position and are
1042 updated by the cursor movement functions.
1044 In hex view and wrapped text view mode, dpy_start marks the offset of
1045 the top-left corner on the screen, in non-wrapping text mode it is
1046 the beginning of the current line. In hex mode, hex_cursor is the
1047 offset of the cursor. In non-wrapping text mode, dpy_text_column is
1048 the number of columns that are hidden on the left side on the screen.
1050 In hex mode, dpy_start is updated by the view_fix_cursor_position()
1051 function in order to keep the other functions simple. In
1052 non-wrapping text mode dpy_start and dpy_text_column are normalized
1053 such that dpy_text_column < view_get_datacolumns().
1056 /* prototypes for functions used by view_moveto_bottom() */
1057 static void view_move_up (WView
*, offset_type
);
1058 static void view_moveto_bol (WView
*);
1061 view_scroll_to_cursor (WView
*view
)
1063 if (view
->hex_mode
) {
1064 const offset_type bytes
= view
->bytes_per_line
;
1065 const offset_type displaysize
= view
->data_area
.height
* bytes
;
1066 const offset_type cursor
= view
->hex_cursor
;
1067 offset_type topleft
= view
->dpy_start
;
1069 if (topleft
+ displaysize
<= cursor
)
1070 topleft
= offset_rounddown (cursor
, bytes
)
1071 - (displaysize
- bytes
);
1072 if (cursor
< topleft
)
1073 topleft
= offset_rounddown (cursor
, bytes
);
1074 view
->dpy_start
= topleft
;
1075 } else if (view
->text_wrap_mode
) {
1076 offset_type line
, col
, columns
;
1078 columns
= view
->data_area
.width
;
1079 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
+ view
->dpy_text_column
);
1081 col
= offset_rounddown (col
, columns
);
1082 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1083 view
->dpy_text_column
= 0;
1090 view_movement_fixups (WView
*view
, gboolean reset_search
)
1092 view_scroll_to_cursor (view
);
1094 view
->search_start
= view
->dpy_start
;
1095 view
->search_length
= 0;
1101 view_moveto_top (WView
*view
)
1103 view
->dpy_start
= 0;
1104 view
->hex_cursor
= 0;
1105 view
->dpy_text_column
= 0;
1106 view_movement_fixups (view
, TRUE
);
1110 view_moveto_bottom (WView
*view
)
1112 offset_type datalines
, lines_up
, filesize
, last_offset
;
1114 if (view
->growbuf_in_use
)
1115 view_growbuf_read_until (view
, OFFSETTYPE_MAX
);
1117 filesize
= view_get_filesize (view
);
1118 last_offset
= offset_doz(filesize
, 1);
1119 datalines
= view
->data_area
.height
;
1120 lines_up
= offset_doz(datalines
, 1);
1122 if (view
->hex_mode
) {
1123 view
->hex_cursor
= filesize
;
1124 view_move_up (view
, lines_up
);
1125 view
->hex_cursor
= last_offset
;
1127 view
->dpy_start
= last_offset
;
1128 view_moveto_bol (view
);
1129 view_move_up (view
, lines_up
);
1131 view_movement_fixups (view
, TRUE
);
1135 view_moveto_bol (WView
*view
)
1137 if (view
->hex_mode
) {
1138 view
->hex_cursor
-= view
->hex_cursor
% view
->bytes_per_line
;
1139 } else if (view
->text_wrap_mode
) {
1142 offset_type line
, column
;
1143 view_offset_to_coord (view
, &line
, &column
, view
->dpy_start
);
1144 view_coord_to_offset (view
, &(view
->dpy_start
), line
, 0);
1145 view
->dpy_text_column
= 0;
1147 view_movement_fixups (view
, TRUE
);
1151 view_moveto_eol (WView
*view
)
1153 if (view
->hex_mode
) {
1154 offset_type filesize
, bol
;
1156 bol
= offset_rounddown (view
->hex_cursor
, view
->bytes_per_line
);
1157 if (get_byte_indexed (view
, bol
, view
->bytes_per_line
- 1) != -1) {
1158 view
->hex_cursor
= bol
+ view
->bytes_per_line
- 1;
1160 filesize
= view_get_filesize (view
);
1161 view
->hex_cursor
= offset_doz(filesize
, 1);
1163 } else if (view
->text_wrap_mode
) {
1166 offset_type line
, col
;
1168 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1169 view_coord_to_offset (view
, &(view
->dpy_start
), line
, OFFSETTYPE_MAX
);
1171 view_movement_fixups (view
, FALSE
);
1175 view_moveto_offset (WView
*view
, offset_type offset
)
1177 if (view
->hex_mode
) {
1178 view
->hex_cursor
= offset
;
1179 view
->dpy_start
= offset
- offset
% view
->bytes_per_line
;
1181 view
->dpy_start
= offset
;
1183 view_movement_fixups (view
, TRUE
);
1187 view_moveto (WView
*view
, offset_type line
, offset_type col
)
1191 view_coord_to_offset (view
, &offset
, line
, col
);
1192 view_moveto_offset (view
, offset
);
1196 view_move_up (WView
*view
, offset_type lines
)
1198 if (view
->hex_mode
) {
1199 offset_type bytes
= lines
* view
->bytes_per_line
;
1200 if (view
->hex_cursor
>= bytes
) {
1201 view
->hex_cursor
-= bytes
;
1202 if (view
->hex_cursor
< view
->dpy_start
)
1203 view
->dpy_start
= offset_doz (view
->dpy_start
, bytes
);
1205 view
->hex_cursor
%= view
->bytes_per_line
;
1207 } else if (view
->text_wrap_mode
) {
1208 const screen_dimen width
= view
->data_area
.width
;
1209 offset_type i
, col
, line
, linestart
;
1211 for (i
= 0; i
< lines
; i
++) {
1212 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1215 } else if (line
>= 1) {
1216 view_coord_to_offset (view
, &linestart
, line
, 0);
1217 view_offset_to_coord (view
, &line
, &col
, linestart
- 1);
1219 /* if the only thing that would be displayed were a
1220 * single newline character, advance to the previous
1221 * part of the line. */
1222 if (col
> 0 && col
% width
== 0)
1229 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1232 offset_type line
, column
;
1234 view_offset_to_coord (view
, &line
, &column
, view
->dpy_start
);
1235 line
= offset_doz(line
, lines
);
1236 view_coord_to_offset (view
, &(view
->dpy_start
), line
, column
);
1238 view_movement_fixups (view
, (lines
!= 1));
1242 view_move_down (WView
*view
, offset_type lines
)
1244 if (view
->hex_mode
) {
1245 offset_type i
, limit
, last_byte
;
1247 last_byte
= view_get_filesize (view
);
1248 if (last_byte
>= (offset_type
) view
->bytes_per_line
)
1249 limit
= last_byte
- view
->bytes_per_line
;
1252 for (i
= 0; i
< lines
&& view
->hex_cursor
< limit
; i
++) {
1253 view
->hex_cursor
+= view
->bytes_per_line
;
1255 view
->dpy_start
+= view
->bytes_per_line
;
1258 } else if (view
->dpy_end
== view_get_filesize (view
)) {
1259 /* don't move further down. There's nothing more to see. */
1261 } else if (view
->text_wrap_mode
) {
1262 offset_type line
, col
, i
;
1264 for (i
= 0; i
< lines
; i
++) {
1265 offset_type new_offset
, chk_line
, chk_col
;
1267 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1268 col
+= view
->data_area
.width
;
1269 view_coord_to_offset (view
, &new_offset
, line
, col
);
1271 /* skip to the next line if the only thing that would be
1272 * displayed is the newline character. */
1273 view_offset_to_coord (view
, &chk_line
, &chk_col
, new_offset
);
1274 if (chk_line
== line
&& chk_col
== col
1275 && get_byte (view
, new_offset
) == '\n')
1278 view
->dpy_start
= new_offset
;
1282 offset_type line
, col
;
1284 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1286 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1288 view_movement_fixups (view
, (lines
!= 1));
1292 view_move_left (WView
*view
, offset_type columns
)
1294 if (view
->hex_mode
) {
1295 assert (columns
== 1);
1296 if (view
->hexview_in_text
|| !view
->hexedit_lownibble
) {
1297 if (view
->hex_cursor
> 0)
1300 if (!view
->hexview_in_text
)
1301 view
->hexedit_lownibble
= !view
->hexedit_lownibble
;
1302 } else if (view
->text_wrap_mode
) {
1305 if (view
->dpy_text_column
>= columns
)
1306 view
->dpy_text_column
-= columns
;
1308 view
->dpy_text_column
= 0;
1310 view_movement_fixups (view
, FALSE
);
1314 view_move_right (WView
*view
, offset_type columns
)
1316 if (view
->hex_mode
) {
1317 assert (columns
== 1);
1318 if (view
->hexview_in_text
|| view
->hexedit_lownibble
) {
1319 if (get_byte_indexed (view
, view
->hex_cursor
, 1) != -1)
1322 if (!view
->hexview_in_text
)
1323 view
->hexedit_lownibble
= !view
->hexedit_lownibble
;
1324 } else if (view
->text_wrap_mode
) {
1327 view
->dpy_text_column
+= columns
;
1329 view_movement_fixups (view
, FALSE
);
1332 /* {{{ Toggling of viewer modes }}} */
1335 view_toggle_hex_mode (WView
*view
)
1337 view
->hex_mode
= !view
->hex_mode
;
1339 if (view
->hex_mode
) {
1340 view
->hex_cursor
= view
->dpy_start
;
1342 offset_rounddown (view
->dpy_start
, view
->bytes_per_line
);
1343 view
->widget
.options
|= W_WANT_CURSOR
;
1345 view
->dpy_start
= view
->hex_cursor
;
1346 view_moveto_bol (view
);
1347 view
->widget
.options
&= ~W_WANT_CURSOR
;
1349 altered_hex_mode
= 1;
1350 view
->dpy_bbar_dirty
= TRUE
;
1355 view_toggle_hexedit_mode (WView
*view
)
1357 view
->hexedit_mode
= !view
->hexedit_mode
;
1358 view
->dpy_bbar_dirty
= TRUE
;
1363 view_toggle_wrap_mode (WView
*view
)
1365 view
->text_wrap_mode
= !view
->text_wrap_mode
;
1366 if (view
->text_wrap_mode
) {
1367 view_scroll_to_cursor (view
);
1371 view_offset_to_coord (view
, &line
, &(view
->dpy_text_column
), view
->dpy_start
);
1372 view_coord_to_offset (view
, &(view
->dpy_start
), line
, 0);
1374 view
->dpy_bbar_dirty
= TRUE
;
1379 view_toggle_nroff_mode (WView
*view
)
1381 view
->text_nroff_mode
= !view
->text_nroff_mode
;
1382 altered_nroff_flag
= 1;
1383 view
->dpy_bbar_dirty
= TRUE
;
1388 view_toggle_magic_mode (WView
*view
)
1390 char *filename
, *command
;
1392 altered_magic_flag
= 1;
1393 view
->magic_mode
= !view
->magic_mode
;
1394 filename
= g_strdup (view
->filename
);
1395 command
= g_strdup (view
->command
);
1398 view_load (view
, command
, filename
, 0);
1401 view
->dpy_bbar_dirty
= TRUE
;
1405 /* {{{ Miscellaneous functions }}} */
1408 view_done (WView
*view
)
1410 /* Save current file position */
1411 if (mcview_remember_file_position
&& view
->filename
!= NULL
) {
1413 offset_type line
, col
;
1415 canon_fname
= vfs_canon (view
->filename
);
1416 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1417 save_file_position (canon_fname
, line
+ 1, col
);
1418 g_free (canon_fname
);
1421 /* Write back the global viewer mode */
1422 default_hex_mode
= view
->hex_mode
;
1423 default_nroff_flag
= view
->text_nroff_mode
;
1424 default_magic_flag
= view
->magic_mode
;
1425 global_wrap_mode
= view
->text_wrap_mode
;
1427 /* Free memory used by the viewer */
1429 /* view->widget needs no destructor */
1431 g_free (view
->filename
), view
->filename
= NULL
;
1432 g_free (view
->command
), view
->command
= NULL
;
1434 view_close_datasource (view
);
1435 /* the growing buffer is freed with the datasource */
1437 if (view
->coord_cache
) {
1438 g_array_free (view
->coord_cache
, TRUE
), view
->coord_cache
= NULL
;
1441 view_hexedit_free_change_list (view
);
1442 /* FIXME: what about view->search_exp? */
1446 view_show_error (WView
*view
, const char *msg
)
1448 view_close_datasource (view
);
1449 if (view_is_in_panel (view
)) {
1450 view_set_datasource_string (view
, msg
);
1452 message (D_ERROR
, MSG_ERROR
, "%s", msg
);
1457 view_load_command_output (WView
*view
, const char *command
)
1461 view_close_datasource (view
);
1464 if ((fp
= popen (command
, "r")) == NULL
) {
1465 /* Avoid two messages. Message from stderr has priority. */
1467 if (!close_error_pipe (view_is_in_panel (view
) ? -1 : D_ERROR
, NULL
))
1468 view_show_error (view
, _(" Cannot spawn child process "));
1472 /* First, check if filter produced any output */
1473 view_set_datasource_stdio_pipe (view
, fp
);
1474 if (get_byte (view
, 0) == -1) {
1475 view_close_datasource (view
);
1477 /* Avoid two messages. Message from stderr has priority. */
1479 if (!close_error_pipe (view_is_in_panel (view
) ? -1 : D_ERROR
, NULL
))
1480 view_show_error (view
, _("Empty output from child filter"));
1487 view_load (WView
*view
, const char *command
, const char *file
,
1492 char tmp
[BUF_MEDIUM
];
1494 gboolean retval
= FALSE
;
1496 assert (view
->bytes_per_line
!= 0);
1499 /* Set up the state */
1500 view_set_datasource_none (view
);
1501 view
->filename
= g_strdup (file
);
1504 /* Clear the markers */
1506 for (i
= 0; i
< 10; i
++)
1509 if (!view_is_in_panel (view
)) {
1510 view
->dpy_text_column
= 0;
1513 if (command
&& (view
->magic_mode
|| file
== NULL
|| file
[0] == '\0')) {
1514 retval
= view_load_command_output (view
, command
);
1515 } else if (file
!= NULL
&& file
[0] != '\0') {
1517 if ((fd
= mc_open (file
, O_RDONLY
| O_NONBLOCK
)) == -1) {
1518 g_snprintf (tmp
, sizeof (tmp
), _(" Cannot open \"%s\"\n %s "),
1519 file
, unix_error_string (errno
));
1520 view_show_error (view
, tmp
);
1524 /* Make sure we are working with a regular file */
1525 if (mc_fstat (fd
, &st
) == -1) {
1527 g_snprintf (tmp
, sizeof (tmp
), _(" Cannot stat \"%s\"\n %s "),
1528 file
, unix_error_string (errno
));
1529 view_show_error (view
, tmp
);
1533 if (!S_ISREG (st
.st_mode
)) {
1535 view_show_error (view
, _(" Cannot view: not a regular file "));
1539 if (st
.st_size
== 0 || mc_lseek (fd
, 0, SEEK_SET
) == -1) {
1540 /* Must be one of those nice files that grow (/proc) */
1541 view_set_datasource_vfs_pipe (view
, fd
);
1543 type
= get_compression_type (fd
);
1545 if (view
->magic_mode
&& (type
!= COMPRESSION_NONE
)) {
1546 g_free (view
->filename
);
1547 view
->filename
= g_strconcat (file
, decompress_extension (type
), (char *) NULL
);
1549 view_set_datasource_file (view
, fd
, &st
);
1555 view
->command
= g_strdup (command
);
1556 view
->dpy_start
= 0;
1557 view
->search_start
= 0;
1558 view
->search_length
= 0;
1559 view
->dpy_text_column
= 0;
1560 view
->last_search
= 0; /* Start a new search */
1562 assert (view
->bytes_per_line
!= 0);
1563 if (mcview_remember_file_position
&& file
!= NULL
&& start_line
== 0) {
1567 canon_fname
= vfs_canon (file
);
1568 load_file_position (file
, &line
, &col
);
1569 g_free (canon_fname
);
1570 view_moveto (view
, offset_doz(line
, 1), col
);
1571 } else if (start_line
> 0) {
1572 view_moveto (view
, start_line
- 1, 0);
1575 view
->hexedit_lownibble
= FALSE
;
1576 view
->hexview_in_text
= FALSE
;
1577 view
->change_list
= NULL
;
1582 /* {{{ Display management }}} */
1585 view_update_bytes_per_line (WView
*view
)
1587 const screen_dimen cols
= view
->data_area
.width
;
1593 bytes
= 4 * ((cols
- 8) / ((cols
< 80) ? 17 : 18));
1596 view
->bytes_per_line
= bytes
;
1597 view
->dirty
= max_dirt_limit
+ 1; /* To force refresh */
1601 view_percent (WView
*view
, offset_type p
)
1603 const screen_dimen top
= view
->status_area
.top
;
1604 const screen_dimen right
= view
->status_area
.left
+ view
->status_area
.width
;
1605 const screen_dimen height
= view
->status_area
.height
;
1607 offset_type filesize
;
1609 if (height
< 1 || right
< 4)
1611 if (view_may_still_grow (view
))
1613 filesize
= view_get_filesize (view
);
1615 if (filesize
== 0 || view
->dpy_end
== filesize
)
1617 else if (p
> (INT_MAX
/ 100))
1618 percent
= p
/ (filesize
/ 100);
1620 percent
= p
* 100 / filesize
;
1622 widget_move (view
, top
, right
- 4);
1623 tty_printf ("%3d%%", percent
);
1627 view_display_status (WView
*view
)
1629 const screen_dimen top
= view
->status_area
.top
;
1630 const screen_dimen left
= view
->status_area
.left
;
1631 const screen_dimen width
= view
->status_area
.width
;
1632 const screen_dimen height
= view
->status_area
.height
;
1633 const char *file_label
, *file_name
;
1634 screen_dimen file_label_width
;
1640 tty_setcolor (SELECTED_COLOR
);
1641 widget_move (view
, top
, left
);
1644 file_label
= _("File: %s");
1645 file_label_width
= strlen (file_label
) - 2;
1646 file_name
= view
->filename
? view
->filename
1647 : view
->command
? view
->command
1650 if (width
< file_label_width
+ 6)
1651 addstr ((char *) name_trunc (file_name
, width
));
1653 i
= (width
> 22 ? 22 : width
) - file_label_width
;
1654 tty_printf (file_label
, name_trunc (file_name
, i
));
1656 widget_move (view
, top
, left
+ 24);
1657 /* FIXME: the format strings need to be changed when offset_type changes */
1659 tty_printf (_("Offset 0x%08lx"), (unsigned long) view
->hex_cursor
);
1661 offset_type line
, col
;
1662 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1663 tty_printf (_("Line %lu Col %lu"),
1664 (unsigned long) line
+ 1,
1665 (unsigned long) (view
->text_wrap_mode
? col
: view
->dpy_text_column
));
1669 offset_type filesize
;
1670 filesize
= view_get_filesize (view
);
1671 widget_move (view
, top
, left
+ 43);
1672 if (!view_may_still_grow (view
)) {
1673 tty_printf (_("%s bytes"), size_trunc (filesize
));
1675 tty_printf (_(">= %s bytes"), size_trunc (filesize
));
1679 view_percent (view
, view
->hex_mode
1684 tty_setcolor (SELECTED_COLOR
);
1688 view_display_clean (WView
*view
)
1690 tty_setcolor (NORMAL_COLOR
);
1691 widget_erase ((Widget
*) view
);
1692 if (view
->dpy_frame_size
!= 0) {
1693 draw_double_box (view
->widget
.parent
, view
->widget
.y
,
1694 view
->widget
.x
, view
->widget
.lines
,
1707 view_count_backspaces (WView
*view
, off_t offset
)
1710 while (offset
>= 2 * backspaces
1711 && get_byte (view
, offset
- 2 * backspaces
) == '\b')
1717 view_display_ruler (WView
*view
)
1719 static const char ruler_chars
[] = "|----*----";
1720 const screen_dimen top
= view
->ruler_area
.top
;
1721 const screen_dimen left
= view
->ruler_area
.left
;
1722 const screen_dimen width
= view
->ruler_area
.width
;
1723 const screen_dimen height
= view
->ruler_area
.height
;
1724 const screen_dimen line_row
= (ruler
== RULER_TOP
) ? 0 : 1;
1725 const screen_dimen nums_row
= (ruler
== RULER_TOP
) ? 1 : 0;
1731 if (ruler
== RULER_NONE
|| height
< 1)
1734 tty_setcolor (MARKED_COLOR
);
1735 for (c
= 0; c
< width
; c
++) {
1736 cl
= view
->dpy_text_column
+ c
;
1737 if (line_row
< height
) {
1738 widget_move (view
, top
+ line_row
, left
+ c
);
1739 tty_print_char (ruler_chars
[cl
% 10]);
1742 if ((cl
!= 0) && (cl
% 10) == 0) {
1743 g_snprintf (r_buff
, sizeof (r_buff
), "%"OFFSETTYPE_PRId
, cl
);
1744 if (nums_row
< height
) {
1745 widget_move (view
, top
+ nums_row
, left
+ c
- 1);
1746 tty_print_string (r_buff
);
1750 attrset (NORMAL_COLOR
);
1754 view_display_hex (WView
*view
)
1756 const screen_dimen top
= view
->data_area
.top
;
1757 const screen_dimen left
= view
->data_area
.left
;
1758 const screen_dimen height
= view
->data_area
.height
;
1759 const screen_dimen width
= view
->data_area
.width
;
1760 const int ngroups
= view
->bytes_per_line
/ 4;
1761 const screen_dimen text_start
=
1762 8 + 13 * ngroups
+ ((width
< 80) ? 0 : (ngroups
- 1 + 1));
1763 /* 8 characters are used for the file offset, and every hex group
1764 * takes 13 characters. On ``big'' screens, the groups are separated
1765 * by an extra vertical line, and there is an extra space before the
1769 screen_dimen row
, col
;
1772 mark_t boldflag
= MARK_NORMAL
;
1773 struct hexedit_change_node
*curr
= view
->change_list
;
1776 char hex_buff
[10]; /* A temporary buffer for sprintf and mvwaddstr */
1777 int bytes
; /* Number of bytes already printed on the line */
1779 view_display_clean (view
);
1781 /* Find the first displayable changed byte */
1782 from
= view
->dpy_start
;
1783 while (curr
&& (curr
->offset
< from
)) {
1787 for (row
= 0; get_byte (view
, from
) != -1 && row
< height
; row
++) {
1790 /* Print the hex offset */
1791 g_snprintf (hex_buff
, sizeof (hex_buff
), "%08"OFFSETTYPE_PRIX
" ", from
);
1792 widget_move (view
, top
+ row
, left
);
1793 tty_setcolor (MARKED_COLOR
);
1794 for (i
= 0; col
< width
&& hex_buff
[i
] != '\0'; i
++) {
1795 tty_print_char(hex_buff
[i
]);
1798 tty_setcolor (NORMAL_COLOR
);
1800 for (bytes
= 0; bytes
< view
->bytes_per_line
; bytes
++, from
++) {
1802 if ((c
= get_byte (view
, from
)) == -1)
1805 /* Save the cursor position for view_place_cursor() */
1806 if (from
== view
->hex_cursor
&& !view
->hexview_in_text
) {
1807 view
->cursor_row
= row
;
1808 view
->cursor_col
= col
;
1811 /* Determine the state of the current byte */
1813 (from
== view
->hex_cursor
) ? MARK_CURSOR
1814 : (curr
!= NULL
&& from
== curr
->offset
) ? MARK_CHANGED
1815 : (view
->search_start
<= from
&&
1816 from
< view
->search_start
+ view
->search_length
1820 /* Determine the value of the current byte */
1821 if (curr
!= NULL
&& from
== curr
->offset
) {
1826 /* Select the color for the hex number */
1828 boldflag
== MARK_NORMAL
? NORMAL_COLOR
:
1829 boldflag
== MARK_SELECTED
? MARKED_COLOR
:
1830 boldflag
== MARK_CHANGED
? VIEW_UNDERLINED_COLOR
:
1831 /* boldflag == MARK_CURSOR */
1832 view
->hexview_in_text
? MARKED_SELECTED_COLOR
:
1833 VIEW_UNDERLINED_COLOR
);
1835 /* Print the hex number */
1836 widget_move (view
, top
+ row
, left
+ col
);
1838 tty_print_char (hex_char
[c
/ 16]);
1842 tty_print_char (hex_char
[c
% 16]);
1846 /* Print the separator */
1847 tty_setcolor (NORMAL_COLOR
);
1848 if (bytes
!= view
->bytes_per_line
- 1) {
1850 tty_print_char (' ');
1854 /* After every four bytes, print a group separator */
1855 if (bytes
% 4 == 3) {
1856 if (view
->data_area
.width
>= 80 && col
< width
) {
1857 tty_print_one_vline ();
1861 tty_print_char (' ');
1867 /* Select the color for the character; this differs from the
1868 * hex color when boldflag == MARK_CURSOR */
1870 boldflag
== MARK_NORMAL
? NORMAL_COLOR
:
1871 boldflag
== MARK_SELECTED
? MARKED_COLOR
:
1872 boldflag
== MARK_CHANGED
? VIEW_UNDERLINED_COLOR
:
1873 /* boldflag == MARK_CURSOR */
1874 view
->hexview_in_text
? VIEW_UNDERLINED_COLOR
:
1875 MARKED_SELECTED_COLOR
);
1877 c
= convert_to_display_c (c
);
1878 if (!is_printable (c
))
1881 /* Print corresponding character on the text side */
1882 if (text_start
+ bytes
< width
) {
1883 widget_move (view
, top
+ row
, left
+ text_start
+ bytes
);
1887 /* Save the cursor position for view_place_cursor() */
1888 if (from
== view
->hex_cursor
&& view
->hexview_in_text
) {
1889 view
->cursor_row
= row
;
1890 view
->cursor_col
= text_start
+ bytes
;
1895 /* Be polite to the other functions */
1896 tty_setcolor (NORMAL_COLOR
);
1898 view_place_cursor (view
);
1899 view
->dpy_end
= from
;
1903 view_display_text (WView
* view
)
1905 const screen_dimen left
= view
->data_area
.left
;
1906 const screen_dimen top
= view
->data_area
.top
;
1907 const screen_dimen width
= view
->data_area
.width
;
1908 const screen_dimen height
= view
->data_area
.height
;
1909 screen_dimen row
, col
;
1912 struct hexedit_change_node
*curr
= view
->change_list
;
1914 view_display_clean (view
);
1915 view_display_ruler (view
);
1917 /* Find the first displayable changed byte */
1918 from
= view
->dpy_start
;
1919 while (curr
&& (curr
->offset
< from
)) {
1923 tty_setcolor (NORMAL_COLOR
);
1924 for (row
= 0, col
= 0; row
< height
&& (c
= get_byte (view
, from
)) != -1; from
++) {
1926 if (view
->text_nroff_mode
&& c
== '\b') {
1930 if ((c_next
= get_byte_indexed (view
, from
, 1)) != -1
1931 && is_printable (c_next
)
1933 && (c_prev
= get_byte (view
, from
- 1)) != -1
1934 && is_printable (c_prev
)
1935 && (c_prev
== c_next
|| c_prev
== '_'
1936 || (c_prev
== '+' && c_next
== 'o'))) {
1939 /* We're inside an nroff character sequence at the
1940 * beginning of the screen -- just skip the
1941 * backspace and continue with the next character. */
1948 if (c_prev
== '_' && (c_next
!= '_' || view_count_backspaces (view
, from
) == 1))
1949 tty_setcolor (VIEW_UNDERLINED_COLOR
);
1951 tty_setcolor (MARKED_COLOR
);
1956 if ((c
== '\n') || (col
>= width
&& view
->text_wrap_mode
)) {
1959 if (c
== '\n' || row
>= height
)
1964 c
= get_byte_indexed(view
, from
, 1);
1965 if (c
== '\r' || c
== '\n')
1973 offset_type line
, column
;
1974 view_offset_to_coord (view
, &line
, &column
, from
);
1975 col
+= (8 - column
% 8);
1976 if (view
->text_wrap_mode
&& col
>= width
&& width
!= 0) {
1983 if (view
->search_start
<= from
1984 && from
< view
->search_start
+ view
->search_length
) {
1985 tty_setcolor (SELECTED_COLOR
);
1988 if (col
>= view
->dpy_text_column
1989 && col
- view
->dpy_text_column
< width
) {
1990 widget_move (view
, top
+ row
, left
+ (col
- view
->dpy_text_column
));
1991 c
= convert_to_display_c (c
);
1992 if (!is_printable (c
))
1997 tty_setcolor (NORMAL_COLOR
);
1999 view
->dpy_end
= from
;
2002 /* Displays as much data from view->dpy_start as fits on the screen */
2004 display (WView
*view
)
2006 view_compute_areas (view
);
2007 if (view
->hex_mode
) {
2008 view_display_hex (view
);
2010 view_display_text (view
);
2012 view_display_status (view
);
2016 view_place_cursor (WView
*view
)
2018 const screen_dimen top
= view
->data_area
.top
;
2019 const screen_dimen left
= view
->data_area
.left
;
2022 col
= view
->cursor_col
;
2023 if (!view
->hexview_in_text
&& view
->hexedit_lownibble
)
2025 widget_move (&view
->widget
, top
+ view
->cursor_row
, left
+ col
);
2029 view_update (WView
*view
)
2031 static int dirt_limit
= 1;
2033 if (view
->dpy_bbar_dirty
) {
2034 view
->dpy_bbar_dirty
= FALSE
;
2036 buttonbar_redraw (view
->widget
.parent
);
2039 if (view
->dirty
> dirt_limit
) {
2040 /* Too many updates skipped -> force a update */
2043 /* Raise the update skipping limit */
2045 if (dirt_limit
> max_dirt_limit
)
2046 dirt_limit
= max_dirt_limit
;
2050 /* We have time to update the screen properly */
2056 /* We are busy -> skipping full update,
2057 only the status line is updated */
2058 view_display_status (view
);
2060 /* Here we had a refresh, if fast scrolling does not work
2061 restore the refresh, although this should not happen */
2065 /* {{{ Hex editor }}} */
2068 enqueue_change (struct hexedit_change_node
**head
,
2069 struct hexedit_change_node
*node
)
2071 /* chnode always either points to the head of the list or
2072 * to one of the ->next fields in the list. The value at
2073 * this location will be overwritten with the new node. */
2074 struct hexedit_change_node
**chnode
= head
;
2076 while (*chnode
!= NULL
&& (*chnode
)->offset
< node
->offset
)
2077 chnode
= &((*chnode
)->next
);
2079 node
->next
= *chnode
;
2084 view_handle_editkey (WView
*view
, int key
)
2086 struct hexedit_change_node
*node
;
2089 /* Has there been a change at this position? */
2090 node
= view
->change_list
;
2091 while (node
&& (node
->offset
!= view
->hex_cursor
))
2094 if (!view
->hexview_in_text
) {
2096 unsigned int hexvalue
= 0;
2098 if (key
>= '0' && key
<= '9')
2099 hexvalue
= 0 + (key
- '0');
2100 else if (key
>= 'A' && key
<= 'F')
2101 hexvalue
= 10 + (key
- 'A');
2102 else if (key
>= 'a' && key
<= 'f')
2103 hexvalue
= 10 + (key
- 'a');
2105 return MSG_NOT_HANDLED
;
2108 byte_val
= node
->value
;
2110 byte_val
= get_byte (view
, view
->hex_cursor
);
2112 if (view
->hexedit_lownibble
) {
2113 byte_val
= (byte_val
& 0xf0) | (hexvalue
);
2115 byte_val
= (byte_val
& 0x0f) | (hexvalue
<< 4);
2119 if (key
< 256 && (is_printable (key
) || (key
== '\n')))
2122 return MSG_NOT_HANDLED
;
2125 node
= g_new (struct hexedit_change_node
, 1);
2126 node
->offset
= view
->hex_cursor
;
2127 node
->value
= byte_val
;
2128 enqueue_change (&view
->change_list
, node
);
2130 node
->value
= byte_val
;
2134 view_move_right (view
, 1);
2139 view_hexedit_save_changes (WView
*view
)
2141 struct hexedit_change_node
*curr
, *next
;
2145 if (view
->change_list
== NULL
)
2149 assert (view
->filename
!= NULL
);
2150 fp
= mc_open (view
->filename
, O_WRONLY
);
2154 for (curr
= view
->change_list
; curr
!= NULL
; curr
= next
) {
2157 if (mc_lseek (fp
, curr
->offset
, SEEK_SET
) == -1
2158 || mc_write (fp
, &(curr
->value
), 1) != 1)
2161 /* delete the saved item from the change list */
2162 view
->change_list
= next
;
2164 view_set_byte (view
, curr
->offset
, curr
->value
);
2168 if (mc_close (fp
) == -1) {
2169 error
= g_strdup (strerror (errno
));
2170 message (D_ERROR
, _(" Save file "),
2171 _(" Error while closing the file: \n %s \n"
2172 " Data may have been written or not. "), error
);
2179 error
= g_strdup (strerror (errno
));
2180 text
= g_strdup_printf (_(" Cannot save file: \n %s "), error
);
2182 (void) mc_close (fp
);
2184 answer
= query_dialog (_(" Save file "), text
, D_ERROR
,
2185 2, _("&Retry"), _("&Cancel"));
2193 /* {{{ Miscellaneous functions }}} */
2196 view_ok_to_quit (WView
*view
)
2200 if (view
->change_list
== NULL
)
2203 r
= query_dialog (_("Quit"),
2204 _(" File was modified, Save with exit? "), D_NORMAL
, 3,
2205 _("&Cancel quit"), _("&Yes"), _("&No"));
2209 return view_hexedit_save_changes (view
);
2211 view_hexedit_free_change_list (view
);
2219 my_define (Dlg_head
*h
, int idx
, const char *text
, void (*fn
) (WView
*),
2222 buttonbar_set_label_data (h
, idx
, text
, (buttonbarfn
) fn
, view
);
2225 /* {{{ Searching }}} */
2227 /* Case insensitive search of text in data */
2229 icase_search_p (WView
*view
, char *text
, char *data
, int nothing
)
2233 const int direction
= view
->direction
;
2237 /* If we are searching backwards, reverse the string */
2238 if (direction
== -1) {
2239 g_strreverse (text
);
2240 g_strreverse (data
);
2243 q
= _icase_search (text
, data
, &lng
);
2245 if (direction
== -1) {
2246 g_strreverse (text
);
2247 g_strreverse (data
);
2252 view
->search_start
= q
- data
- lng
;
2254 view
->search_start
= strlen (data
) - (q
- data
);
2255 view
->search_length
= lng
;
2262 grow_string_buffer (char *text
, gulong
*size
)
2266 /* The grow steps */
2268 new = g_realloc (text
, *size
);
2276 get_line_at (WView
*view
, offset_type
*p
, offset_type
*skipped
)
2278 char *buffer
= NULL
;
2279 gulong buffer_size
= 0;
2280 offset_type usable_size
= 0;
2282 const int direction
= view
->direction
;
2283 offset_type pos
= *p
;
2289 if (pos
== 0 && direction
== -1)
2292 /* skip over all the possible zeros in the file */
2293 while ((ch
= get_byte (view
, pos
)) == 0) {
2294 if (pos
== 0 && direction
== -1)
2301 if (i
== 0 && (pos
!= 0 || direction
== -1)) {
2302 prev
= get_byte (view
, pos
- direction
);
2303 if ((prev
== -1) || (prev
== '\n'))
2307 for (i
= 1; ch
!= -1; ch
= get_byte (view
, pos
)) {
2308 if (i
>= usable_size
) {
2309 buffer
= grow_string_buffer (buffer
, &buffer_size
);
2310 usable_size
= buffer_size
- 2; /* prev & null terminator */
2315 if (pos
== 0 && direction
== -1)
2320 if (ch
== '\n' || ch
== '\0') {
2321 i
--; /* Strip newline/zero */
2330 /* If we are searching backwards, reverse the string */
2331 if (direction
== -1) {
2332 g_strreverse (buffer
+ 1);
2341 search_update_steps (WView
*view
)
2343 offset_type filesize
= view_get_filesize (view
);
2345 view
->update_steps
= 40000;
2346 else /* viewing a data stream, not a file */
2347 view
->update_steps
= filesize
/ 100;
2349 /* Do not update the percent display but every 20 ks */
2350 if (view
->update_steps
< 20000)
2351 view
->update_steps
= 20000;
2355 search (WView
*view
, char *text
,
2356 int (*search
) (WView
*, char *, char *, int))
2358 char *s
= NULL
; /* The line we read from the view buffer */
2359 offset_type p
, beginning
, search_start
;
2364 /* Used to keep track of where the line starts, when looking forward
2365 * is the index before transfering the line; the reverse case uses
2366 * the position returned after the line has been read */
2367 offset_type forward_line_start
;
2368 offset_type reverse_line_start
;
2372 d
= create_message (D_NORMAL
, _("Search"), _("Searching %s"), text
);
2376 found_len
= view
->search_length
;
2377 search_start
= view
->search_start
;
2379 if (view
->direction
== 1) {
2380 p
= search_start
+ ((found_len
) ? 1 : 0);
2382 p
= search_start
- ((found_len
&& search_start
>= 1) ? 1 : 0);
2386 /* Compute the percent steps */
2387 search_update_steps (view
);
2388 view
->update_activate
= 0;
2390 enable_interrupt_key ();
2391 for (;; g_free (s
)) {
2392 if (p
>= view
->update_activate
) {
2393 view
->update_activate
+= view
->update_steps
;
2395 view_percent (view
, p
);
2398 if (got_interrupt ())
2401 forward_line_start
= p
;
2402 s
= get_line_at (view
, &p
, &t
);
2403 reverse_line_start
= p
;
2408 search_status
= (*search
) (view
, text
, s
+ 1, match_normal
);
2409 if (search_status
< 0) {
2414 if (search_status
== 0)
2417 /* We found the string */
2419 /* Handle ^ and $ when regexp search starts at the middle of the line */
2420 if (*s
&& !view
->search_start
&& (search
== regexp_view_search
)) {
2421 if ((*text
== '^' && view
->direction
== 1)
2422 || (view
->direction
== -1 && text
[strlen (text
) - 1] == '$')
2427 /* Record the position used to continue the search */
2428 if (view
->direction
== 1)
2429 t
+= forward_line_start
;
2431 t
= reverse_line_start
? reverse_line_start
+ 2 : 0;
2432 view
->search_start
+= t
;
2434 if (t
!= beginning
) {
2435 view
->dpy_start
= t
;
2441 disable_interrupt_key ();
2447 message (D_NORMAL
, _("Search"), _(" Search string not found "));
2448 view
->search_length
= 0;
2452 /* Search buffer (its size is len) in the complete buffer
2453 * returns the position where the block was found or INVALID_OFFSET
2456 block_search (WView
*view
, const char *buffer
, int len
)
2458 int direction
= view
->direction
;
2459 const char *d
= buffer
;
2463 enable_interrupt_key ();
2465 e
= view
->search_start
+ ((view
->search_length
) ? 1 : 0);
2467 e
= view
->search_start
2468 - ((view
->search_length
&& view
->search_start
>= 1) ? 1 : 0);
2470 search_update_steps (view
);
2471 view
->update_activate
= 0;
2473 if (direction
== -1) {
2474 for (d
+= len
- 1;; e
--) {
2475 if (e
<= view
->update_activate
) {
2476 view
->update_activate
-= view
->update_steps
;
2478 view_percent (view
, e
);
2481 if (got_interrupt ())
2484 b
= get_byte (view
, e
);
2488 disable_interrupt_key ();
2493 e
+= buffer
+ len
- 1 - d
;
2494 d
= buffer
+ len
- 1;
2500 while (get_byte (view
, e
) != -1) {
2501 if (e
>= view
->update_activate
) {
2502 view
->update_activate
+= view
->update_steps
;
2504 view_percent (view
, e
);
2507 if (got_interrupt ())
2510 b
= get_byte (view
, e
++);
2514 if (d
- buffer
== len
) {
2515 disable_interrupt_key ();
2524 disable_interrupt_key ();
2525 return INVALID_OFFSET
;
2529 * Search in the hex mode. Supported input:
2530 * - numbers (oct, dec, hex). Each of them matches one byte.
2531 * - strings in double quotes. Matches exactly without quotes.
2534 hex_search (WView
*view
, const char *text
)
2536 char *buffer
; /* Parsed search string */
2537 char *cur
; /* Current position in it */
2538 int block_len
; /* Length of the search string */
2539 offset_type pos
; /* Position of the string in the file */
2540 int parse_error
= 0;
2543 view
->search_length
= 0;
2547 /* buffer will never be longer that text */
2548 buffer
= g_new (char, strlen (text
));
2551 /* First convert the string to a stream of bytes */
2556 /* Skip leading spaces */
2557 if (*text
== ' ' || *text
== '\t') {
2562 /* %i matches octal, decimal, and hexadecimal numbers */
2563 if (sscanf (text
, "%i%n", &val
, &ptr
) > 0) {
2564 /* Allow signed and unsigned char in the user input */
2565 if (val
< -128 || val
> 255) {
2570 *cur
++ = (char) val
;
2575 /* Try quoted string, strip quotes */
2577 const char *next_quote
;
2580 next_quote
= strchr (text
, '"');
2582 memcpy (cur
, text
, next_quote
- text
);
2583 cur
+= next_quote
- text
;
2584 text
= next_quote
+ 1;
2594 block_len
= cur
- buffer
;
2596 /* No valid bytes in the user input */
2597 if (block_len
<= 0 || parse_error
) {
2598 message (D_NORMAL
, _("Search"), _("Invalid hex search expression"));
2600 view
->search_length
= 0;
2604 /* Then start the search */
2605 pos
= block_search (view
, buffer
, block_len
);
2609 if (pos
== INVALID_OFFSET
) {
2610 message (D_NORMAL
, _("Search"), _(" Search string not found "));
2611 view
->search_length
= 0;
2615 view
->search_start
= pos
;
2616 view
->search_length
= block_len
;
2617 /* Set the edit cursor to the search position, left nibble */
2618 view
->hex_cursor
= view
->search_start
;
2619 view
->hexedit_lownibble
= FALSE
;
2621 /* Adjust the file offset */
2622 view
->dpy_start
= pos
- pos
% view
->bytes_per_line
;
2626 regexp_view_search (WView
*view
, char *pattern
, char *string
,
2630 static char *old_pattern
= NULL
;
2631 static int old_type
;
2632 regmatch_t pmatch
[1];
2633 int i
, flags
= REG_ICASE
;
2635 if (old_pattern
== NULL
|| strcmp (old_pattern
, pattern
) != 0
2636 || old_type
!= match_type
) {
2637 if (old_pattern
!= NULL
) {
2639 g_free (old_pattern
);
2642 for (i
= 0; pattern
[i
] != '\0'; i
++) {
2643 if (isupper ((unsigned char) pattern
[i
])) {
2648 flags
|= REG_EXTENDED
;
2649 if (regcomp (&r
, pattern
, flags
)) {
2650 message (D_ERROR
, MSG_ERROR
, _(" Invalid regular expression "));
2653 old_pattern
= g_strdup (pattern
);
2654 old_type
= match_type
;
2656 if (regexec (&r
, string
, 1, pmatch
, 0) != 0)
2658 view
->search_length
= pmatch
[0].rm_eo
- pmatch
[0].rm_so
;
2659 view
->search_start
= pmatch
[0].rm_so
;
2664 do_regexp_search (WView
*view
)
2666 search (view
, view
->search_exp
, regexp_view_search
);
2667 /* Had a refresh here */
2673 do_normal_search (WView
*view
)
2676 hex_search (view
, view
->search_exp
);
2678 search (view
, view
->search_exp
, icase_search_p
);
2679 /* Had a refresh here */
2684 /* {{{ User-definable commands }}} */
2687 The functions in this section can be bound to hotkeys. They are all
2688 of the same type (taking a pointer to WView as parameter and
2689 returning void). TODO: In the not-too-distant future, these commands
2690 will become fully configurable, like they already are in the
2691 internal editor. By convention, all the function names end in
2696 view_help_cmd (void)
2698 interactive_display (NULL
, "[Internal File Viewer]");
2701 /* Toggle between hexview and hexedit mode */
2703 view_toggle_hexedit_mode_cmd (WView
*view
)
2705 view_toggle_hexedit_mode (view
);
2709 /* Toggle between wrapped and unwrapped view */
2711 view_toggle_wrap_mode_cmd (WView
*view
)
2713 view_toggle_wrap_mode (view
);
2717 /* Toggle between hex view and text view */
2719 view_toggle_hex_mode_cmd (WView
*view
)
2721 view_toggle_hex_mode (view
);
2726 view_moveto_line_cmd (WView
*view
)
2728 char *answer
, *answer_end
, prompt
[BUF_SMALL
];
2729 offset_type line
, col
;
2731 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
2733 g_snprintf (prompt
, sizeof (prompt
),
2734 _(" The current line number is %d.\n"
2735 " Enter the new line number:"), (int) (line
+ 1));
2736 answer
= input_dialog (_(" Goto line "), prompt
, MC_HISTORY_VIEW_GOTO_LINE
, "");
2737 if (answer
!= NULL
&& answer
[0] != '\0') {
2739 line
= strtoul (answer
, &answer_end
, 10);
2740 if (*answer_end
== '\0' && errno
== 0 && line
>= 1)
2741 view_moveto (view
, line
- 1, 0);
2749 view_moveto_addr_cmd (WView
*view
)
2751 char *line
, *error
, prompt
[BUF_SMALL
];
2754 g_snprintf (prompt
, sizeof (prompt
),
2755 _(" The current address is 0x%lx.\n"
2756 " Enter the new address:"), view
->hex_cursor
);
2757 line
= input_dialog (_(" Goto Address "), prompt
, MC_HISTORY_VIEW_GOTO_ADDR
, "");
2759 if (*line
!= '\0') {
2760 addr
= strtoul (line
, &error
, 0);
2761 if ((*error
== '\0') && get_byte (view
, addr
) != -1) {
2762 view_moveto_offset (view
, addr
);
2764 message (D_ERROR
, _("Warning"), _(" Invalid address "));
2774 view_hexedit_save_changes_cmd (WView
*view
)
2776 (void) view_hexedit_save_changes (view
);
2779 /* {{{ Searching }}} */
2782 regexp_search (WView
*view
, int direction
)
2786 static char *last_regexp
;
2788 defval
= (last_regexp
!= NULL
? last_regexp
: "");
2790 regexp
= input_dialog (_("Search"), _(" Enter regexp:"), MC_HISTORY_VIEW_SEARCH_REGEX
, defval
);
2791 if (regexp
== NULL
|| regexp
[0] == '\0') {
2796 g_free (last_regexp
);
2797 view
->search_exp
= last_regexp
= regexp
;
2799 view
->direction
= direction
;
2800 do_regexp_search (view
);
2801 view
->last_search
= do_regexp_search
;
2804 /* {{{ User-definable commands }}} */
2807 view_regexp_search_cmd (WView
*view
)
2809 regexp_search (view
, 1);
2814 view_normal_search_cmd (WView
*view
)
2816 char *defval
, *exp
= NULL
;
2817 static char *last_search_string
;
2820 SEARCH_DLG_HEIGHT
= 8,
2821 SEARCH_DLG_WIDTH
= 58
2824 static int replace_backwards
;
2825 int treplace_backwards
= replace_backwards
;
2827 static QuickWidget quick_widgets
[] = {
2828 {quick_button
, 6, 10, 5, SEARCH_DLG_HEIGHT
, N_("&Cancel"), 0,
2831 {quick_button
, 2, 10, 5, SEARCH_DLG_HEIGHT
, N_("&OK"), 0, B_ENTER
,
2833 {quick_checkbox
, 3, SEARCH_DLG_WIDTH
, 4, SEARCH_DLG_HEIGHT
,
2834 N_("&Backwards"), 0, 0,
2836 {quick_input
, 3, SEARCH_DLG_WIDTH
, 3, SEARCH_DLG_HEIGHT
, "", 52, 0,
2837 0, 0, N_("Search")},
2838 {quick_label
, 2, SEARCH_DLG_WIDTH
, 2, SEARCH_DLG_HEIGHT
,
2839 N_(" Enter search string:"), 0, 0,
2843 static QuickDialog Quick_input
= {
2844 SEARCH_DLG_WIDTH
, SEARCH_DLG_HEIGHT
, -1, 0, N_("Search"),
2845 "[Input Line Keys]", quick_widgets
, 0
2848 defval
= g_strdup (last_search_string
!= NULL
? last_search_string
: "");
2849 convert_to_display (defval
);
2851 quick_widgets
[2].result
= &treplace_backwards
;
2852 quick_widgets
[3].str_result
= &exp
;
2853 quick_widgets
[3].text
= defval
;
2855 if (quick_dialog (&Quick_input
) == B_CANCEL
)
2858 replace_backwards
= treplace_backwards
;
2860 if (exp
== NULL
|| exp
[0] == '\0')
2863 convert_from_input (exp
);
2865 g_free (last_search_string
);
2866 view
->search_exp
= last_search_string
= exp
;
2869 view
->direction
= replace_backwards
? -1 : 1;
2870 do_normal_search (view
);
2871 view
->last_search
= do_normal_search
;
2879 view_toggle_magic_mode_cmd (WView
*view
)
2881 view_toggle_magic_mode (view
);
2886 view_toggle_nroff_mode_cmd (WView
*view
)
2888 view_toggle_nroff_mode (view
);
2893 view_quit_cmd (WView
*view
)
2895 if (view_ok_to_quit (view
))
2896 dlg_stop (view
->widget
.parent
);
2899 /* {{{ Miscellaneous functions }}} */
2901 /* Define labels and handlers for functional keys */
2903 view_labels (WView
*view
)
2905 Dlg_head
*h
= view
->widget
.parent
;
2907 buttonbar_set_label (h
, 1, Q_("ButtonBar|Help"), view_help_cmd
);
2909 my_define (h
, 10, Q_("ButtonBar|Quit"), view_quit_cmd
, view
);
2910 my_define (h
, 4, view
->hex_mode
2911 ? Q_("ButtonBar|Ascii")
2912 : Q_("ButtonBar|Hex"),
2913 view_toggle_hex_mode_cmd
, view
);
2914 my_define (h
, 5, view
->hex_mode
2915 ? Q_("ButtonBar|Goto")
2916 : Q_("ButtonBar|Line"),
2917 view
->hex_mode
? view_moveto_addr_cmd
: view_moveto_line_cmd
, view
);
2919 if (view
->hex_mode
) {
2920 if (view
->hexedit_mode
) {
2921 my_define (h
, 2, Q_("ButtonBar|View"),
2922 view_toggle_hexedit_mode_cmd
, view
);
2923 } else if (view
->datasource
== DS_FILE
) {
2924 my_define (h
, 2, Q_("ButtonBar|Edit"),
2925 view_toggle_hexedit_mode_cmd
, view
);
2927 buttonbar_clear_label (h
, 2);
2929 my_define (h
, 6, Q_("ButtonBar|Save"),
2930 view_hexedit_save_changes_cmd
, view
);
2932 my_define (h
, 2, view
->text_wrap_mode
2933 ? Q_("ButtonBar|UnWrap")
2934 : Q_("ButtonBar|Wrap"),
2935 view_toggle_wrap_mode_cmd
, view
);
2936 my_define (h
, 6, Q_("ButtonBar|RxSrch"),
2937 view_regexp_search_cmd
, view
);
2940 my_define (h
, 7, view
->hex_mode
2941 ? Q_("ButtonBar|HxSrch")
2942 : Q_("ButtonBar|Search"),
2943 view_normal_search_cmd
, view
);
2944 my_define (h
, 8, view
->magic_mode
2945 ? Q_("ButtonBar|Raw")
2946 : Q_("ButtonBar|Parse"),
2947 view_toggle_magic_mode_cmd
, view
);
2949 /* don't override the key to access the main menu */
2950 if (!view_is_in_panel (view
)) {
2951 my_define (h
, 9, view
->text_nroff_mode
2952 ? Q_("ButtonBar|Unform")
2953 : Q_("ButtonBar|Format"),
2954 view_toggle_nroff_mode_cmd
, view
);
2955 my_define (h
, 3, Q_("ButtonBar|Quit"), view_quit_cmd
, view
);
2959 /* {{{ Event handling }}} */
2961 /* Check for left and right arrows, possibly with modifiers */
2963 check_left_right_keys (WView
*view
, int c
)
2965 if (c
== KEY_LEFT
) {
2966 view_move_left (view
, 1);
2970 if (c
== KEY_RIGHT
) {
2971 view_move_right (view
, 1);
2975 /* Ctrl with arrows moves by 10 postions in the unwrap mode */
2976 if (view
->hex_mode
|| view
->text_wrap_mode
)
2977 return MSG_NOT_HANDLED
;
2979 if (c
== (KEY_M_CTRL
| KEY_LEFT
)) {
2980 if (view
->dpy_text_column
>= 10)
2981 view
->dpy_text_column
-= 10;
2983 view
->dpy_text_column
= 0;
2988 if (c
== (KEY_M_CTRL
| KEY_RIGHT
)) {
2989 if (view
->dpy_text_column
<= OFFSETTYPE_MAX
- 10)
2990 view
->dpy_text_column
+= 10;
2992 view
->dpy_text_column
= OFFSETTYPE_MAX
;
2997 return MSG_NOT_HANDLED
;
3000 /* {{{ User-definable commands }}} */
3003 view_continue_search_cmd (WView
*view
)
3005 if (view
->last_search
) {
3006 view
->last_search (view
);
3008 /* if not... then ask for an expression */
3009 view_normal_search_cmd (view
);
3014 view_toggle_ruler_cmd (WView
*view
)
3016 static const enum ruler_type next
[3] = {
3022 assert ((size_t) ruler
< 3);
3023 ruler
= next
[(size_t) ruler
];
3027 /* {{{ Event handling }}} */
3029 static void view_cmk_move_up (void *w
, int n
) {
3030 view_move_up ((WView
*) w
, n
);
3032 static void view_cmk_move_down (void *w
, int n
) {
3033 view_move_down ((WView
*) w
, n
);
3035 static void view_cmk_moveto_top (void *w
, int n
) {
3037 view_moveto_top ((WView
*) w
);
3039 static void view_cmk_moveto_bottom (void *w
, int n
) {
3041 view_moveto_bottom ((WView
*) w
);
3046 view_handle_key (WView
*view
, int c
)
3048 c
= convert_from_input_c (c
);
3050 if (view
->hex_mode
) {
3053 view
->hexview_in_text
= !view
->hexview_in_text
;
3058 view_moveto_bol (view
);
3063 view_move_left (view
, 1);
3067 view_moveto_eol (view
);
3071 view_move_right (view
, 1);
3075 if (view
->hexedit_mode
3076 && view_handle_editkey (view
, c
) == MSG_HANDLED
)
3080 if (check_left_right_keys (view
, c
))
3083 if (check_movement_keys (c
, view
->data_area
.height
+ 1, view
,
3084 view_cmk_move_up
, view_cmk_move_down
,
3085 view_cmk_moveto_top
, view_cmk_moveto_bottom
))
3091 regexp_search (view
, -1);
3095 regexp_search (view
, 1);
3098 /* Continue search */
3103 view_continue_search_cmd (view
);
3108 view_toggle_ruler_cmd (view
);
3112 view_move_left (view
, 1);
3118 view_move_down (view
, 1);
3122 view_move_down (view
, (view
->data_area
.height
+ 1) / 2);
3126 view_move_up (view
, (view
->data_area
.height
+ 1) / 2);
3131 view_move_up (view
, 1);
3135 view_move_right (view
, 1);
3140 view_move_down (view
, view
->data_area
.height
);
3147 /* Unlike Ctrl-O, run a new shell if the subshell is not running. */
3153 view_move_up (view
, view
->data_area
.height
);
3157 view_move_up (view
, 2);
3161 view_move_down (view
, 2);
3165 view
->marks
[view
->marker
] = view
->dpy_start
;
3169 view
->dpy_start
= view
->marks
[view
->marker
];
3173 /* Use to indicate parent that we want to see the next/previous file */
3174 /* Does not work in panel mode */
3177 if (!view_is_in_panel (view
))
3178 view
->move_dir
= c
== XCTRL ('f') ? 1 : -1;
3183 if (view_ok_to_quit (view
))
3184 view
->want_to_quit
= TRUE
;
3189 do_select_codepage ();
3193 #endif /* HAVE_CHARSET */
3195 #ifdef MC_ENABLE_DEBUGGING_CODE
3196 case 't': /* mnemonic: "test" */
3197 view_ccache_dump (view
);
3201 if (c
>= '0' && c
<= '9')
3202 view
->marker
= c
- '0';
3205 return MSG_NOT_HANDLED
;
3210 view_event (WView
*view
, Gpm_Event
*event
, int *result
)
3214 *result
= MOU_NORMAL
;
3216 /* We are not interested in the release events */
3217 if (!(event
->type
& (GPM_DOWN
| GPM_DRAG
)))
3221 if ((event
->buttons
& GPM_B_UP
) && (event
->type
& GPM_DOWN
)) {
3222 view_move_up (view
, 2);
3225 if ((event
->buttons
& GPM_B_DOWN
) && (event
->type
& GPM_DOWN
)) {
3226 view_move_down (view
, 2);
3233 /* Scrolling left and right */
3234 if (!view
->text_wrap_mode
) {
3235 if (x
< view
->data_area
.width
* 1/4) {
3236 view_move_left (view
, 1);
3238 } else if (x
< view
->data_area
.width
* 3/4) {
3239 /* ignore the click */
3241 view_move_right (view
, 1);
3246 /* Scrolling up and down */
3247 if (y
< view
->data_area
.top
+ view
->data_area
.height
* 1/3) {
3248 if (mouse_move_pages_viewer
)
3249 view_move_up (view
, view
->data_area
.height
/ 2);
3251 view_move_up (view
, 1);
3253 } else if (y
< view
->data_area
.top
+ view
->data_area
.height
* 2/3) {
3254 /* ignore the click */
3256 if (mouse_move_pages_viewer
)
3257 view_move_down (view
, view
->data_area
.height
/ 2);
3259 view_move_down (view
, 1);
3266 *result
= MOU_REPEAT
;
3270 /* Real view only */
3272 real_view_event (Gpm_Event
*event
, void *x
)
3274 WView
*view
= (WView
*) x
;
3277 if (view_event (view
, event
, &result
))
3283 view_adjust_size (Dlg_head
*h
)
3288 /* Look up the viewer and the buttonbar, we assume only two widgets here */
3289 view
= (WView
*) find_widget_type (h
, view_callback
);
3290 bar
= find_buttonbar (h
);
3291 widget_set_size (&view
->widget
, 0, 0, LINES
- 1, COLS
);
3292 widget_set_size ((Widget
*) bar
, LINES
- 1, 0, 1, COLS
);
3294 view_compute_areas (view
);
3295 view_update_bytes_per_line (view
);
3298 /* Callback for the view dialog */
3300 view_dialog_callback (Dlg_head
*h
, dlg_msg_t msg
, int parm
)
3304 view_adjust_size (h
);
3308 return default_dlg_callback (h
, msg
, parm
);
3312 /* {{{ External interface }}} */
3314 /* Real view only */
3316 mc_internal_viewer (const char *command
, const char *file
,
3317 int *move_dir_p
, int start_line
)
3324 /* Create dialog and widgets, put them on the dialog */
3326 create_dlg (0, 0, LINES
, COLS
, NULL
, view_dialog_callback
,
3327 "[Internal File Viewer]", NULL
, DLG_WANT_TAB
);
3329 wview
= view_new (0, 0, COLS
, LINES
- 1, 0);
3331 bar
= buttonbar_new (1);
3333 add_widget (view_dlg
, bar
);
3334 add_widget (view_dlg
, wview
);
3336 succeeded
= view_load (wview
, command
, file
, start_line
);
3340 *move_dir_p
= wview
->move_dir
;
3345 destroy_dlg (view_dlg
);
3350 /* {{{ Miscellaneous functions }}} */
3355 WView
*view
= (WView
*) v
;
3358 /* If the user is busy typing, wait until he finishes to update the
3361 if (!hook_present (idle_hook
, view_hook
))
3362 add_hook (&idle_hook
, view_hook
, v
);
3366 delete_hook (&idle_hook
, view_hook
);
3368 if (get_current_type () == view_listing
)
3369 panel
= current_panel
;
3370 else if (get_other_type () == view_listing
)
3371 panel
= other_panel
;
3375 view_load (view
, 0, panel
->dir
.list
[panel
->selected
].fname
, 0);
3379 /* {{{ Event handling }}} */
3382 view_callback (Widget
*w
, widget_msg_t msg
, int parm
)
3384 WView
*view
= (WView
*) w
;
3386 Dlg_head
*h
= view
->widget
.parent
;
3388 view_compute_areas (view
);
3389 view_update_bytes_per_line (view
);
3393 if (view_is_in_panel (view
))
3394 add_hook (&select_file_hook
, view_hook
, view
);
3396 view
->dpy_bbar_dirty
= TRUE
;
3405 view_place_cursor (view
);
3409 i
= view_handle_key ((WView
*) view
, parm
);
3410 if (view
->want_to_quit
&& !view_is_in_panel (view
))
3418 view
->dpy_bbar_dirty
= TRUE
;
3422 case WIDGET_DESTROY
:
3424 if (view_is_in_panel (view
))
3425 delete_hook (&select_file_hook
, view_hook
);
3429 return default_proc (msg
, parm
);
3433 /* {{{ External interface }}} */
3436 view_new (int y
, int x
, int cols
, int lines
, int is_panel
)
3438 WView
*view
= g_new0 (WView
, 1);
3441 init_widget (&view
->widget
, y
, x
, lines
, cols
,
3445 view
->filename
= NULL
;
3446 view
->command
= NULL
;
3448 view_set_datasource_none (view
);
3450 view
->growbuf_in_use
= FALSE
;
3451 /* leave the other growbuf fields uninitialized */
3453 view
->hex_mode
= FALSE
;
3454 view
->hexedit_mode
= FALSE
;
3455 view
->hexview_in_text
= FALSE
;
3456 view
->text_nroff_mode
= FALSE
;
3457 view
->text_wrap_mode
= FALSE
;
3458 view
->magic_mode
= FALSE
;
3460 view
->hexedit_lownibble
= FALSE
;
3461 view
->coord_cache
= NULL
;
3463 view
->dpy_frame_size
= is_panel
? 1 : 0;
3464 view
->dpy_start
= 0;
3465 view
->dpy_text_column
= 0;
3467 view
->hex_cursor
= 0;
3468 view
->cursor_col
= 0;
3469 view
->cursor_row
= 0;
3470 view
->change_list
= NULL
;
3472 /* {status,ruler,data}_area are left uninitialized */
3475 view
->dpy_bbar_dirty
= TRUE
;
3476 view
->bytes_per_line
= 1;
3478 view
->search_start
= 0;
3479 view
->search_length
= 0;
3480 view
->search_exp
= NULL
;
3481 view
->direction
= 1; /* forward */
3482 view
->last_search
= 0; /* it's a function */
3484 view
->want_to_quit
= FALSE
;
3486 for (i
= 0; i
< sizeof(view
->marks
) / sizeof(view
->marks
[0]); i
++)
3490 view
->update_steps
= 0;
3491 view
->update_activate
= 0;
3493 if (default_hex_mode
)
3494 view_toggle_hex_mode (view
);
3495 if (default_nroff_flag
)
3496 view_toggle_nroff_mode (view
);
3497 if (global_wrap_mode
)
3498 view_toggle_wrap_mode (view
);
3499 if (default_magic_flag
)
3500 view_toggle_magic_mode (view
);