2 Internal file viewer for the Midnight Commander
4 Copyright (C) 1994, 1995, 1996 The Free Software Foundation
6 Written by: 1994, 1995, 1998 Miguel de Icaza
7 1994, 1995 Janne Kukonlehto
12 2004 Roland Illig <roland.illig@gmx.de>
13 2005 Roland Illig <roland.illig@gmx.de>
15 This program is free software; you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation; either version 2 of the License, or
18 (at your option) any later version.
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 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 (0, 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 blockoffset
= offset_rounddown (byte_index
, view
->ds_file_datasize
);
544 if (mc_lseek (view
->ds_file_fd
, blockoffset
, SEEK_SET
) == -1)
548 while (bytes_read
< view
->ds_file_datasize
) {
549 res
= mc_read (view
->ds_file_fd
, view
->ds_file_data
+ bytes_read
, view
->ds_file_datasize
- bytes_read
);
554 bytes_read
+= (size_t) res
;
556 view
->ds_file_offset
= blockoffset
;
557 if (bytes_read
> view
->ds_file_filesize
- view
->ds_file_offset
) {
558 /* the file has grown in the meantime -- stick to the old size */
559 view
->ds_file_datalen
= view
->ds_file_filesize
- view
->ds_file_offset
;
561 view
->ds_file_datalen
= bytes_read
;
566 view
->ds_file_datalen
= 0;
570 get_byte_none (WView
*view
, offset_type byte_index
)
572 assert (view
->datasource
== DS_NONE
);
579 get_byte_file (WView
*view
, offset_type byte_index
)
581 assert (view
->datasource
== DS_FILE
);
583 view_file_load_data (view
, byte_index
);
584 if (already_loaded(view
->ds_file_offset
, byte_index
, view
->ds_file_datalen
))
585 return view
->ds_file_data
[byte_index
- view
->ds_file_offset
];
590 get_byte_string (WView
*view
, offset_type byte_index
)
592 assert (view
->datasource
== DS_STRING
);
593 if (byte_index
< view
->ds_string_len
)
594 return view
->ds_string_data
[byte_index
];
599 get_byte (WView
*view
, offset_type offset
)
601 switch (view
->datasource
) {
604 return get_byte_growing_buffer (view
, offset
);
606 return get_byte_file (view
, offset
);
608 return get_byte_string (view
, offset
);
610 return get_byte_none (view
, offset
);
612 assert(!"Unknown datasource type");
617 get_byte_indexed (WView
*view
, offset_type base
, offset_type ofs
)
619 if (base
<= OFFSETTYPE_MAX
- ofs
)
620 return get_byte (view
, base
+ ofs
);
625 view_set_byte (WView
*view
, offset_type offset
, byte b
)
628 assert (offset
< view_get_filesize (view
));
629 assert (view
->datasource
== DS_FILE
);
630 view
->ds_file_datalen
= 0; /* just force reloading */
634 view_set_datasource_none (WView
*view
)
636 view
->datasource
= DS_NONE
;
640 view_set_datasource_vfs_pipe (WView
*view
, int fd
)
643 view
->datasource
= DS_VFS_PIPE
;
644 view
->ds_vfs_pipe
= fd
;
646 view_init_growbuf (view
);
650 view_set_datasource_stdio_pipe (WView
*view
, FILE *fp
)
653 view
->datasource
= DS_STDIO_PIPE
;
654 view
->ds_stdio_pipe
= fp
;
656 view_init_growbuf (view
);
660 view_set_datasource_string (WView
*view
, const char *s
)
662 view
->datasource
= DS_STRING
;
663 view
->ds_string_data
= (byte
*) g_strdup (s
);
664 view
->ds_string_len
= strlen (s
);
668 view_set_datasource_file (WView
*view
, int fd
, const struct stat
*st
)
670 view
->datasource
= DS_FILE
;
671 view
->ds_file_fd
= fd
;
672 view
->ds_file_filesize
= st
->st_size
;
673 view
->ds_file_offset
= 0;
674 view
->ds_file_data
= g_malloc (4096);
675 view
->ds_file_datalen
= 0;
676 view
->ds_file_datasize
= 4096;
680 view_close_datasource (WView
*view
)
682 switch (view
->datasource
) {
686 if (view
->ds_stdio_pipe
!= NULL
) {
687 (void) pclose (view
->ds_stdio_pipe
);
689 close_error_pipe (0, NULL
);
690 view
->ds_stdio_pipe
= NULL
;
692 view_growbuf_free (view
);
695 if (view
->ds_vfs_pipe
!= -1) {
696 (void) mc_close (view
->ds_vfs_pipe
);
697 view
->ds_vfs_pipe
= -1;
699 view_growbuf_free (view
);
702 (void) mc_close (view
->ds_file_fd
);
703 view
->ds_file_fd
= -1;
704 g_free (view
->ds_file_data
);
705 view
->ds_file_data
= NULL
;
708 g_free (view
->ds_string_data
);
709 view
->ds_string_data
= NULL
;
712 assert (!"Unknown datasource type");
714 view
->datasource
= DS_NONE
;
717 /* {{{ The Coordinate Cache }}} */
720 This cache provides you with a fast lookup to map file offsets into
721 line/column pairs and vice versa. The interface to the mapping is
722 provided by the functions view_coord_to_offset() and
723 view_offset_to_coord().
725 The cache is implemented as a simple sorted array holding entries
726 that map some of the offsets to their line/column pair. Entries that
727 are not cached themselves are interpolated (exactly) from their
728 neighbor entries. The algorithm used for determining the line/column
729 for a specific offset needs to be kept synchronized with the one used
738 static inline gboolean
739 coord_cache_entry_less (const struct coord_cache_entry
*a
,
740 const struct coord_cache_entry
*b
, enum ccache_type crit
,
743 if (crit
== CCACHE_OFFSET
)
744 return (a
->cc_offset
< b
->cc_offset
);
746 if (a
->cc_line
< b
->cc_line
)
749 if (a
->cc_line
== b
->cc_line
) {
751 return (a
->cc_nroff_column
< b
->cc_nroff_column
);
753 return (a
->cc_column
< b
->cc_column
);
759 #ifdef MC_ENABLE_DEBUGGING_CODE
760 static void view_coord_to_offset (WView
*, offset_type
*, offset_type
, offset_type
);
761 static void view_offset_to_coord (WView
*, offset_type
*, offset_type
*, offset_type
);
764 view_ccache_dump (WView
*view
)
767 offset_type offset
, line
, column
, nextline_offset
, filesize
;
769 const struct coord_cache_entry
*cache
;
771 assert (view
->coord_cache
!= NULL
);
773 filesize
= view_get_filesize (view
);
774 cache
= &(g_array_index (view
->coord_cache
, struct coord_cache_entry
, 0));
776 f
= fopen("mcview-ccache.out", "w");
779 (void)setvbuf(f
, NULL
, _IONBF
, 0);
782 for (i
= 0; i
< view
->coord_cache
->len
; i
++) {
785 "offset %8"OFFSETTYPE_PRId
" "
786 "line %8"OFFSETTYPE_PRId
" "
787 "column %8"OFFSETTYPE_PRId
" "
788 "nroff_column %8"OFFSETTYPE_PRId
"\n",
789 (unsigned int) i
, cache
[i
].cc_offset
, cache
[i
].cc_line
,
790 cache
[i
].cc_column
, cache
[i
].cc_nroff_column
);
792 (void)fprintf (f
, "\n");
794 /* offset -> line/column translation */
795 for (offset
= 0; offset
< filesize
; offset
++) {
796 view_offset_to_coord (view
, &line
, &column
, offset
);
798 "offset %8"OFFSETTYPE_PRId
" "
799 "line %8"OFFSETTYPE_PRId
" "
800 "column %8"OFFSETTYPE_PRId
"\n",
801 offset
, line
, column
);
804 /* line/column -> offset translation */
805 for (line
= 0; TRUE
; line
++) {
806 view_coord_to_offset (view
, &nextline_offset
, line
+ 1, 0);
807 (void)fprintf (f
, "nextline_offset %8"OFFSETTYPE_PRId
"\n",
810 for (column
= 0; TRUE
; column
++) {
811 view_coord_to_offset (view
, &offset
, line
, column
);
812 if (offset
>= nextline_offset
)
815 (void)fprintf (f
, "line %8"OFFSETTYPE_PRId
" column %8"OFFSETTYPE_PRId
" offset %8"OFFSETTYPE_PRId
"\n",
816 line
, column
, offset
);
819 if (nextline_offset
>= filesize
- 1)
827 static inline gboolean
828 is_nroff_sequence (WView
*view
, offset_type offset
)
832 /* The following commands are ordered to speed up the calculation. */
834 c1
= get_byte_indexed (view
, offset
, 1);
835 if (c1
== -1 || c1
!= '\b')
838 c0
= get_byte_indexed (view
, offset
, 0);
839 if (c0
== -1 || !is_printable(c0
))
842 c2
= get_byte_indexed (view
, offset
, 2);
843 if (c2
== -1 || !is_printable(c2
))
846 return (c0
== c2
|| c0
== '_' || (c0
== '+' && c2
== 'o'));
849 /* Find and return the index of the last cache entry that is
850 * smaller than ''coord'', according to the criterion ''sort_by''. */
852 view_ccache_find (WView
*view
, const struct coord_cache_entry
*cache
,
853 const struct coord_cache_entry
*coord
, enum ccache_type sort_by
)
855 guint base
, i
, limit
;
857 limit
= view
->coord_cache
->len
;
862 i
= base
+ limit
/ 2;
863 if (coord_cache_entry_less (coord
, &cache
[i
], sort_by
, view
->text_nroff_mode
)) {
864 /* continue the search in the lower half of the cache */
866 /* continue the search in the upper half of the cache */
869 limit
= (limit
+ 1) / 2;
874 /* Look up the missing components of ''coord'', which are given by
875 * ''lookup_what''. The function returns the smallest value that
876 * matches the existing components of ''coord''.
879 view_ccache_lookup (WView
*view
, struct coord_cache_entry
*coord
,
880 enum ccache_type lookup_what
)
883 struct coord_cache_entry
*cache
, current
, next
, entry
;
884 enum ccache_type sorter
;
892 if (!view
->coord_cache
) {
893 view
->coord_cache
= g_array_new (FALSE
, FALSE
, sizeof(struct coord_cache_entry
));
894 current
.cc_offset
= 0;
896 current
.cc_column
= 0;
897 current
.cc_nroff_column
= 0;
898 g_array_append_val (view
->coord_cache
, current
);
901 sorter
= (lookup_what
== CCACHE_OFFSET
) ? CCACHE_LINECOL
: CCACHE_OFFSET
;
904 /* find the two neighbor entries in the cache */
905 cache
= &(g_array_index (view
->coord_cache
, struct coord_cache_entry
, 0));
906 i
= view_ccache_find (view
, cache
, coord
, sorter
);
907 /* now i points to the lower neighbor in the cache */
910 if (i
+ 1 < view
->coord_cache
->len
)
911 limit
= cache
[i
+ 1].cc_offset
;
913 limit
= current
.cc_offset
+ VIEW_COORD_CACHE_GRANUL
;
916 nroff_state
= NROFF_START
;
917 for (; current
.cc_offset
< limit
; current
= next
) {
920 if ((c
= get_byte (view
, current
.cc_offset
)) == -1)
923 if (!coord_cache_entry_less (¤t
, coord
, sorter
, view
->text_nroff_mode
)) {
924 if (lookup_what
== CCACHE_OFFSET
925 && view
->text_nroff_mode
926 && nroff_state
!= NROFF_START
) {
927 /* don't break here */
933 /* Provide useful default values for ''next'' */
934 next
.cc_offset
= current
.cc_offset
+ 1;
935 next
.cc_line
= current
.cc_line
;
936 next
.cc_column
= current
.cc_column
+ 1;
937 next
.cc_nroff_column
= current
.cc_nroff_column
+ 1;
939 /* and override some of them as necessary. */
941 next
.cc_column
= current
.cc_column
;
942 next
.cc_nroff_column
= current
.cc_nroff_column
;
944 } else if (nroff_state
== NROFF_BACKSPACE
) {
945 next
.cc_nroff_column
= current
.cc_nroff_column
- 1;
947 } else if (c
== '\t') {
948 next
.cc_column
= offset_rounddown (current
.cc_column
, 8) + 8;
949 next
.cc_nroff_column
=
950 offset_rounddown (current
.cc_nroff_column
, 8) + 8;
952 } else if (c
== '\n') {
953 next
.cc_line
= current
.cc_line
+ 1;
955 next
.cc_nroff_column
= 0;
958 /* Use all default values from above */
961 switch (nroff_state
) {
963 case NROFF_CONTINUATION
:
964 if (is_nroff_sequence (view
, current
.cc_offset
))
965 nroff_state
= NROFF_BACKSPACE
;
967 nroff_state
= NROFF_START
;
969 case NROFF_BACKSPACE
:
970 nroff_state
= NROFF_CONTINUATION
;
974 /* Cache entries must guarantee that for each i < j,
975 * line[i] <= line[j] and column[i] < column[j]. In the case of
976 * nroff sequences and '\r' characters, this is not guaranteed,
977 * so we cannot save them. */
978 if (nroff_state
== NROFF_START
&& c
!= '\r')
982 if (i
+ 1 == view
->coord_cache
->len
&& entry
.cc_offset
!= cache
[i
].cc_offset
) {
983 g_array_append_val (view
->coord_cache
, entry
);
987 if (lookup_what
== CCACHE_OFFSET
) {
988 coord
->cc_offset
= current
.cc_offset
;
990 coord
->cc_line
= current
.cc_line
;
991 coord
->cc_column
= current
.cc_column
;
992 coord
->cc_nroff_column
= current
.cc_nroff_column
;
997 view_coord_to_offset (WView
*view
, offset_type
*ret_offset
,
998 offset_type line
, offset_type column
)
1000 struct coord_cache_entry coord
;
1002 coord
.cc_line
= line
;
1003 coord
.cc_column
= column
;
1004 coord
.cc_nroff_column
= column
;
1005 view_ccache_lookup (view
, &coord
, CCACHE_OFFSET
);
1006 *ret_offset
= coord
.cc_offset
;
1010 view_offset_to_coord (WView
*view
, offset_type
*ret_line
,
1011 offset_type
*ret_column
, offset_type offset
)
1013 struct coord_cache_entry coord
;
1015 coord
.cc_offset
= offset
;
1016 view_ccache_lookup (view
, &coord
, CCACHE_LINECOL
);
1017 *ret_line
= coord
.cc_line
;
1018 *ret_column
= (view
->text_nroff_mode
)
1019 ? coord
.cc_nroff_column
1023 /* {{{ Cursor Movement }}} */
1026 The following variables have to do with the current position and are
1027 updated by the cursor movement functions.
1029 In hex view and wrapped text view mode, dpy_start marks the offset of
1030 the top-left corner on the screen, in non-wrapping text mode it is
1031 the beginning of the current line. In hex mode, hex_cursor is the
1032 offset of the cursor. In non-wrapping text mode, dpy_text_column is
1033 the number of columns that are hidden on the left side on the screen.
1035 In hex mode, dpy_start is updated by the view_fix_cursor_position()
1036 function in order to keep the other functions simple. In
1037 non-wrapping text mode dpy_start and dpy_text_column are normalized
1038 such that dpy_text_column < view_get_datacolumns().
1041 /* prototypes for functions used by view_moveto_bottom() */
1042 static void view_move_up (WView
*, offset_type
);
1043 static void view_moveto_bol (WView
*);
1046 view_scroll_to_cursor (WView
*view
)
1048 if (view
->hex_mode
) {
1049 const offset_type bytes
= view
->bytes_per_line
;
1050 const offset_type displaysize
= view
->data_area
.height
* bytes
;
1051 const offset_type cursor
= view
->hex_cursor
;
1052 offset_type topleft
= view
->dpy_start
;
1054 if (topleft
+ displaysize
<= cursor
)
1055 topleft
= offset_rounddown (cursor
, bytes
)
1056 - (displaysize
- bytes
);
1057 if (cursor
< topleft
)
1058 topleft
= offset_rounddown (cursor
, bytes
);
1059 view
->dpy_start
= topleft
;
1060 } else if (view
->text_wrap_mode
) {
1061 offset_type line
, col
, columns
;
1063 columns
= view
->data_area
.width
;
1064 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
+ view
->dpy_text_column
);
1066 col
= offset_rounddown (col
, columns
);
1067 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1068 view
->dpy_text_column
= 0;
1075 view_movement_fixups (WView
*view
, gboolean reset_search
)
1077 view_scroll_to_cursor (view
);
1079 view
->search_start
= view
->dpy_start
;
1080 view
->search_length
= 0;
1086 view_moveto_top (WView
*view
)
1088 view
->dpy_start
= 0;
1089 view
->hex_cursor
= 0;
1090 view
->dpy_text_column
= 0;
1091 view_movement_fixups (view
, TRUE
);
1095 view_moveto_bottom (WView
*view
)
1097 offset_type datalines
, lines_up
, filesize
, last_offset
;
1099 if (view
->growbuf_in_use
)
1100 view_growbuf_read_until (view
, OFFSETTYPE_MAX
);
1102 filesize
= view_get_filesize (view
);
1103 last_offset
= offset_doz(filesize
, 1);
1104 datalines
= view
->data_area
.height
;
1105 lines_up
= offset_doz(datalines
, 1);
1107 if (view
->hex_mode
) {
1108 view
->hex_cursor
= filesize
;
1109 view_move_up (view
, lines_up
);
1110 view
->hex_cursor
= last_offset
;
1112 view
->dpy_start
= last_offset
;
1113 view_moveto_bol (view
);
1114 view_move_up (view
, lines_up
);
1116 view_movement_fixups (view
, TRUE
);
1120 view_moveto_bol (WView
*view
)
1122 if (view
->hex_mode
) {
1123 view
->hex_cursor
-= view
->hex_cursor
% view
->bytes_per_line
;
1124 } else if (view
->text_wrap_mode
) {
1127 offset_type line
, column
;
1128 view_offset_to_coord (view
, &line
, &column
, view
->dpy_start
);
1129 view_coord_to_offset (view
, &(view
->dpy_start
), line
, 0);
1130 view
->dpy_text_column
= 0;
1132 view_movement_fixups (view
, TRUE
);
1136 view_moveto_eol (WView
*view
)
1138 if (view
->hex_mode
) {
1139 offset_type filesize
, bol
;
1141 bol
= offset_rounddown (view
->hex_cursor
, view
->bytes_per_line
);
1142 if (get_byte_indexed (view
, bol
, view
->bytes_per_line
- 1) != -1) {
1143 view
->hex_cursor
= bol
+ view
->bytes_per_line
- 1;
1145 filesize
= view_get_filesize (view
);
1146 view
->hex_cursor
= offset_doz(filesize
, 1);
1148 } else if (view
->text_wrap_mode
) {
1151 offset_type line
, col
;
1153 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1154 view_coord_to_offset (view
, &(view
->dpy_start
), line
, OFFSETTYPE_MAX
);
1156 view_movement_fixups (view
, FALSE
);
1160 view_moveto_offset (WView
*view
, offset_type offset
)
1162 if (view
->hex_mode
) {
1163 view
->hex_cursor
= offset
;
1164 view
->dpy_start
= offset
- offset
% view
->bytes_per_line
;
1166 view
->dpy_start
= offset
;
1168 view_movement_fixups (view
, TRUE
);
1172 view_moveto (WView
*view
, offset_type line
, offset_type col
)
1176 view_coord_to_offset (view
, &offset
, line
, col
);
1177 view_moveto_offset (view
, offset
);
1181 view_move_up (WView
*view
, offset_type lines
)
1183 if (view
->hex_mode
) {
1184 offset_type bytes
= lines
* view
->bytes_per_line
;
1185 if (view
->hex_cursor
>= bytes
) {
1186 view
->hex_cursor
-= bytes
;
1187 if (view
->hex_cursor
< view
->dpy_start
)
1188 view
->dpy_start
= offset_doz (view
->dpy_start
, bytes
);
1190 view
->hex_cursor
%= view
->bytes_per_line
;
1192 } else if (view
->text_wrap_mode
) {
1193 const screen_dimen width
= view
->data_area
.width
;
1194 offset_type i
, col
, line
, linestart
;
1196 for (i
= 0; i
< lines
; i
++) {
1197 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1200 } else if (line
>= 1) {
1201 view_coord_to_offset (view
, &linestart
, line
, 0);
1202 view_offset_to_coord (view
, &line
, &col
, linestart
- 1);
1204 /* if the only thing that would be displayed were a
1205 * single newline character, advance to the previous
1206 * part of the line. */
1207 if (col
> 0 && col
% width
== 0)
1214 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1217 offset_type line
, column
;
1219 view_offset_to_coord (view
, &line
, &column
, view
->dpy_start
);
1220 line
= offset_doz(line
, lines
);
1221 view_coord_to_offset (view
, &(view
->dpy_start
), line
, column
);
1223 view_movement_fixups (view
, (lines
!= 1));
1227 view_move_down (WView
*view
, offset_type lines
)
1229 if (view
->hex_mode
) {
1230 offset_type i
, limit
, last_byte
;
1232 last_byte
= view_get_filesize (view
);
1233 if (last_byte
>= (offset_type
) view
->bytes_per_line
)
1234 limit
= last_byte
- view
->bytes_per_line
;
1237 for (i
= 0; i
< lines
&& view
->hex_cursor
< limit
; i
++) {
1238 view
->hex_cursor
+= view
->bytes_per_line
;
1240 view
->dpy_start
+= view
->bytes_per_line
;
1243 } else if (view
->dpy_end
== view_get_filesize (view
)) {
1244 /* don't move further down. There's nothing more to see. */
1246 } else if (view
->text_wrap_mode
) {
1247 offset_type line
, col
, i
;
1249 for (i
= 0; i
< lines
; i
++) {
1250 offset_type new_offset
, chk_line
, chk_col
;
1252 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1253 col
+= view
->data_area
.width
;
1254 view_coord_to_offset (view
, &new_offset
, line
, col
);
1256 /* skip to the next line if the only thing that would be
1257 * displayed is the newline character. */
1258 view_offset_to_coord (view
, &chk_line
, &chk_col
, new_offset
);
1259 if (chk_line
== line
&& chk_col
== col
1260 && get_byte (view
, new_offset
) == '\n')
1263 view
->dpy_start
= new_offset
;
1267 offset_type line
, col
;
1269 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1271 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1273 view_movement_fixups (view
, (lines
!= 1));
1277 view_move_left (WView
*view
, offset_type columns
)
1279 if (view
->hex_mode
) {
1280 assert (columns
== 1);
1281 if (view
->hexview_in_text
|| !view
->hexedit_lownibble
) {
1282 if (view
->hex_cursor
> 0)
1285 if (!view
->hexview_in_text
)
1286 view
->hexedit_lownibble
= !view
->hexedit_lownibble
;
1287 } else if (view
->text_wrap_mode
) {
1290 if (view
->dpy_text_column
>= columns
)
1291 view
->dpy_text_column
-= columns
;
1293 view
->dpy_text_column
= 0;
1295 view_movement_fixups (view
, FALSE
);
1299 view_move_right (WView
*view
, offset_type columns
)
1301 if (view
->hex_mode
) {
1302 assert (columns
== 1);
1303 if (view
->hexview_in_text
|| view
->hexedit_lownibble
) {
1304 if (get_byte_indexed (view
, view
->hex_cursor
, 1) != -1)
1307 if (!view
->hexview_in_text
)
1308 view
->hexedit_lownibble
= !view
->hexedit_lownibble
;
1309 } else if (view
->text_wrap_mode
) {
1312 view
->dpy_text_column
+= columns
;
1314 view_movement_fixups (view
, FALSE
);
1317 /* {{{ Toggling of viewer modes }}} */
1320 view_toggle_hex_mode (WView
*view
)
1322 view
->hex_mode
= !view
->hex_mode
;
1324 if (view
->hex_mode
) {
1325 view
->hex_cursor
= view
->dpy_start
;
1327 offset_rounddown (view
->dpy_start
, view
->bytes_per_line
);
1328 view
->widget
.options
|= W_WANT_CURSOR
;
1330 view
->dpy_start
= view
->hex_cursor
;
1331 view_moveto_bol (view
);
1332 view
->widget
.options
&= ~W_WANT_CURSOR
;
1334 altered_hex_mode
= 1;
1335 view
->dpy_bbar_dirty
= TRUE
;
1340 view_toggle_hexedit_mode (WView
*view
)
1342 view
->hexedit_mode
= !view
->hexedit_mode
;
1343 view
->dpy_bbar_dirty
= TRUE
;
1348 view_toggle_wrap_mode (WView
*view
)
1350 view
->text_wrap_mode
= !view
->text_wrap_mode
;
1351 if (view
->text_wrap_mode
) {
1352 view_scroll_to_cursor (view
);
1356 view_offset_to_coord (view
, &line
, &(view
->dpy_text_column
), view
->dpy_start
);
1357 view_coord_to_offset (view
, &(view
->dpy_start
), line
, 0);
1359 view
->dpy_bbar_dirty
= TRUE
;
1364 view_toggle_nroff_mode (WView
*view
)
1366 view
->text_nroff_mode
= !view
->text_nroff_mode
;
1367 altered_nroff_flag
= 1;
1368 view
->dpy_bbar_dirty
= TRUE
;
1373 view_toggle_magic_mode (WView
*view
)
1375 char *filename
, *command
;
1377 altered_magic_flag
= 1;
1378 view
->magic_mode
= !view
->magic_mode
;
1379 filename
= g_strdup (view
->filename
);
1380 command
= g_strdup (view
->command
);
1383 view_load (view
, command
, filename
, 0);
1386 view
->dpy_bbar_dirty
= TRUE
;
1390 /* {{{ Miscellaneous functions }}} */
1393 view_done (WView
*view
)
1395 /* Save current file position */
1396 if (mcview_remember_file_position
&& view
->filename
!= NULL
) {
1398 offset_type line
, col
;
1400 canon_fname
= vfs_canon (view
->filename
);
1401 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1402 save_file_position (canon_fname
, line
+ 1, col
);
1403 g_free (canon_fname
);
1406 /* Write back the global viewer mode */
1407 default_hex_mode
= view
->hex_mode
;
1408 default_nroff_flag
= view
->text_nroff_mode
;
1409 default_magic_flag
= view
->magic_mode
;
1410 global_wrap_mode
= view
->text_wrap_mode
;
1412 /* Free memory used by the viewer */
1414 /* view->widget needs no destructor */
1416 g_free (view
->filename
), view
->filename
= NULL
;
1417 g_free (view
->command
), view
->command
= NULL
;
1419 view_close_datasource (view
);
1420 /* the growing buffer is freed with the datasource */
1422 if (view
->coord_cache
) {
1423 g_array_free (view
->coord_cache
, TRUE
), view
->coord_cache
= NULL
;
1426 view_hexedit_free_change_list (view
);
1427 /* FIXME: what about view->search_exp? */
1431 view_show_error (WView
*view
, const char *msg
)
1433 view_close_datasource (view
);
1434 if (view_is_in_panel (view
)) {
1435 view_set_datasource_string (view
, msg
);
1437 message (1, MSG_ERROR
, "%s", msg
);
1442 view_load_command_output (WView
*view
, const char *command
)
1446 view_close_datasource (view
);
1449 if ((fp
= popen (command
, "r")) == NULL
) {
1450 /* Avoid two messages. Message from stderr has priority. */
1452 if (!close_error_pipe (view_is_in_panel (view
) ? -1 : 1, NULL
))
1453 view_show_error (view
, _(" Cannot spawn child process "));
1457 /* First, check if filter produced any output */
1458 view_set_datasource_stdio_pipe (view
, fp
);
1459 if (get_byte (view
, 0) == -1) {
1460 view_close_datasource (view
);
1462 /* Avoid two messages. Message from stderr has priority. */
1464 if (!close_error_pipe (view_is_in_panel (view
) ? -1 : 1, NULL
))
1465 view_show_error (view
, _("Empty output from child filter"));
1472 view_load (WView
*view
, const char *command
, const char *file
,
1477 char tmp
[BUF_MEDIUM
];
1479 gboolean retval
= FALSE
;
1481 assert (view
->bytes_per_line
!= 0);
1484 /* Set up the state */
1485 view_set_datasource_none (view
);
1486 view
->filename
= g_strdup (file
);
1489 /* Clear the markers */
1491 for (i
= 0; i
< 10; i
++)
1494 if (!view_is_in_panel (view
)) {
1495 view
->dpy_text_column
= 0;
1498 if (command
&& (view
->magic_mode
|| file
== NULL
|| file
[0] == '\0')) {
1499 retval
= view_load_command_output (view
, command
);
1500 } else if (file
!= NULL
&& file
[0] != '\0') {
1502 if ((fd
= mc_open (file
, O_RDONLY
| O_NONBLOCK
)) == -1) {
1503 g_snprintf (tmp
, sizeof (tmp
), _(" Cannot open \"%s\"\n %s "),
1504 file
, unix_error_string (errno
));
1505 view_show_error (view
, tmp
);
1509 /* Make sure we are working with a regular file */
1510 if (mc_fstat (fd
, &st
) == -1) {
1512 g_snprintf (tmp
, sizeof (tmp
), _(" Cannot stat \"%s\"\n %s "),
1513 file
, unix_error_string (errno
));
1514 view_show_error (view
, tmp
);
1518 if (!S_ISREG (st
.st_mode
)) {
1520 view_show_error (view
, _(" Cannot view: not a regular file "));
1524 if (st
.st_size
== 0 || mc_lseek (fd
, 0, SEEK_SET
) == -1) {
1525 /* Must be one of those nice files that grow (/proc) */
1526 view_set_datasource_vfs_pipe (view
, fd
);
1528 type
= get_compression_type (fd
);
1530 if (view
->magic_mode
&& (type
!= COMPRESSION_NONE
)) {
1531 g_free (view
->filename
);
1532 view
->filename
= g_strconcat (file
, decompress_extension (type
), (char *) NULL
);
1534 view_set_datasource_file (view
, fd
, &st
);
1540 view
->command
= g_strdup (command
);
1541 view
->dpy_start
= 0;
1542 view
->search_start
= 0;
1543 view
->search_length
= 0;
1544 view
->dpy_text_column
= 0;
1545 view
->last_search
= 0; /* Start a new search */
1547 assert (view
->bytes_per_line
!= 0);
1548 if (mcview_remember_file_position
&& file
!= NULL
&& start_line
== 0) {
1552 canon_fname
= vfs_canon (file
);
1553 load_file_position (file
, &line
, &col
);
1554 g_free (canon_fname
);
1555 view_moveto (view
, offset_doz(line
, 1), col
);
1556 } else if (start_line
> 0) {
1557 view_moveto (view
, start_line
- 1, 0);
1560 view
->hexedit_lownibble
= FALSE
;
1561 view
->hexview_in_text
= FALSE
;
1562 view
->change_list
= NULL
;
1567 /* {{{ Display management }}} */
1570 view_update_bytes_per_line (WView
*view
)
1572 const screen_dimen cols
= view
->data_area
.width
;
1578 bytes
= ((cols
- 8) / 17) * 4;
1580 bytes
= ((cols
- 8) / 18) * 4;
1583 bytes
= 1; /* To avoid division by 0 */
1585 view
->bytes_per_line
= bytes
;
1586 view
->dirty
= max_dirt_limit
+ 1; /* To force refresh */
1590 view_percent (WView
*view
, offset_type p
)
1592 const screen_dimen top
= view
->status_area
.top
;
1593 const screen_dimen right
= view
->status_area
.left
+ view
->status_area
.width
;
1594 const screen_dimen height
= view
->status_area
.height
;
1596 offset_type filesize
;
1598 if (height
< 1 || right
< 4)
1600 if (view_may_still_grow (view
))
1602 filesize
= view_get_filesize (view
);
1604 if (filesize
== 0 || view
->dpy_end
== filesize
)
1606 else if (p
> (INT_MAX
/ 100))
1607 percent
= p
/ (filesize
/ 100);
1609 percent
= p
* 100 / filesize
;
1611 widget_move (view
, top
, right
- 4);
1612 printw (str_unconst ("%3d%%"), percent
);
1616 view_display_status (WView
*view
)
1618 const screen_dimen top
= view
->status_area
.top
;
1619 const screen_dimen left
= view
->status_area
.left
;
1620 const screen_dimen width
= view
->status_area
.width
;
1621 const screen_dimen height
= view
->status_area
.height
;
1622 const char *file_label
, *file_name
;
1623 screen_dimen file_label_width
;
1629 attrset (SELECTED_COLOR
);
1630 widget_move (view
, top
, left
);
1633 file_label
= _("File: %s");
1634 file_label_width
= strlen (file_label
) - 2;
1635 file_name
= view
->filename
? view
->filename
1636 : view
->command
? view
->command
1639 if (width
< file_label_width
+ 6)
1640 addstr ((char *) name_trunc (file_name
, width
));
1642 i
= (width
> 22 ? 22 : width
) - file_label_width
;
1643 printw (str_unconst (file_label
), name_trunc (file_name
, i
));
1645 widget_move (view
, top
, left
+ 24);
1646 /* FIXME: the format strings need to be changed when offset_type changes */
1648 printw (str_unconst (_("Offset 0x%08lx")), view
->hex_cursor
);
1650 offset_type line
, col
;
1651 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1652 printw (str_unconst (_("Line %lu Col %lu")),
1653 (unsigned long) line
+ 1,
1654 (unsigned long) (view
->text_wrap_mode
? col
: view
->dpy_text_column
));
1658 offset_type filesize
;
1659 filesize
= view_get_filesize (view
);
1660 widget_move (view
, top
, left
+ 43);
1661 if (!view_may_still_grow (view
)) {
1662 printw (str_unconst (_("%s bytes")), size_trunc (filesize
));
1664 printw (str_unconst (_(">= %s bytes")), size_trunc (filesize
));
1668 view_percent (view
, view
->hex_mode
1673 attrset (SELECTED_COLOR
);
1677 view_display_clean (WView
*view
)
1679 attrset (NORMAL_COLOR
);
1680 widget_erase ((Widget
*) view
);
1681 if (view
->dpy_frame_size
!= 0) {
1682 draw_double_box (view
->widget
.parent
, view
->widget
.y
,
1683 view
->widget
.x
, view
->widget
.lines
,
1696 view_count_backspaces (WView
*view
, off_t offset
)
1699 while (offset
>= 2 * backspaces
1700 && get_byte (view
, offset
- 2 * backspaces
) == '\b')
1706 view_display_ruler (WView
*view
)
1708 static const char ruler_chars
[] = "|----*----";
1709 const screen_dimen top
= view
->ruler_area
.top
;
1710 const screen_dimen left
= view
->ruler_area
.left
;
1711 const screen_dimen width
= view
->ruler_area
.width
;
1712 const screen_dimen height
= view
->ruler_area
.height
;
1713 const screen_dimen line_row
= (ruler
== RULER_TOP
) ? 0 : 1;
1714 const screen_dimen nums_row
= (ruler
== RULER_TOP
) ? 1 : 0;
1720 if (ruler
== RULER_NONE
|| height
< 1)
1723 attrset (MARKED_COLOR
);
1724 for (c
= 0; c
< width
; c
++) {
1725 cl
= view
->dpy_text_column
+ c
;
1726 if (line_row
< height
) {
1727 widget_move (view
, top
+ line_row
, left
+ c
);
1728 tty_print_char (ruler_chars
[cl
% 10]);
1731 if ((cl
!= 0) && (cl
% 10) == 0) {
1732 g_snprintf (r_buff
, sizeof (r_buff
), "%"OFFSETTYPE_PRId
, cl
);
1733 if (nums_row
< height
) {
1734 widget_move (view
, top
+ nums_row
, left
+ c
- 1);
1735 tty_print_string (r_buff
);
1739 attrset (NORMAL_COLOR
);
1743 view_display_hex (WView
*view
)
1744 /* FIXME: prevent any screen overflows */
1746 const screen_dimen top
= view
->data_area
.top
;
1747 const screen_dimen left
= view
->data_area
.left
;
1748 const screen_dimen height
= view
->data_area
.height
;
1749 const screen_dimen width
= view
->data_area
.width
;
1750 const screen_dimen text_start
= width
- view
->bytes_per_line
;
1751 const screen_dimen hex_start
= 9;
1753 screen_dimen row
, col
;
1756 mark_t boldflag
= MARK_NORMAL
;
1757 struct hexedit_change_node
*curr
= view
->change_list
;
1759 char hex_buff
[10]; /* A temporary buffer for sprintf and mvwaddstr */
1760 int bytes
; /* Number of bytes already printed on the line */
1762 view_display_clean (view
);
1764 /* Find the first displayable changed byte */
1765 from
= view
->dpy_start
;
1766 while (curr
&& (curr
->offset
< from
)) {
1770 for (row
= 0; get_byte (view
, from
) != -1 && row
< height
; row
++) {
1771 /* Print the hex offset */
1772 attrset (MARKED_COLOR
);
1773 g_snprintf (hex_buff
, sizeof (hex_buff
), "%08"OFFSETTYPE_PRIX
, from
);
1774 widget_move (view
, top
+ row
, left
);
1775 tty_print_string (hex_buff
);
1776 attrset (NORMAL_COLOR
);
1779 for (bytes
= 0; bytes
< view
->bytes_per_line
; bytes
++, from
++) {
1781 if ((c
= get_byte (view
, from
)) == -1)
1784 /* Save the cursor position for view_place_cursor() */
1785 if (from
== view
->hex_cursor
&& !view
->hexview_in_text
) {
1786 view
->cursor_row
= row
;
1787 view
->cursor_col
= col
;
1790 /* Determine the state of the current byte */
1792 /* The current cursor position */
1793 if (from
== view
->hex_cursor
) {
1794 boldflag
= MARK_CURSOR
;
1796 /* Changed bytes from the hex editor */
1797 } else if (curr
&& from
== curr
->offset
) {
1800 boldflag
= MARK_CHANGED
;
1802 /* Marked bytes from the search functions */
1803 } else if (view
->search_start
<= from
1804 && from
< view
->search_start
+ view
->search_length
) {
1805 boldflag
= MARK_SELECTED
;
1808 boldflag
= MARK_NORMAL
;
1811 /* Select the color for the hex number */
1813 boldflag
== MARK_NORMAL
? NORMAL_COLOR
:
1814 boldflag
== MARK_SELECTED
? MARKED_COLOR
:
1815 boldflag
== MARK_CHANGED
? VIEW_UNDERLINED_COLOR
:
1816 /* boldflag == MARK_CURSOR */
1817 view
->hexview_in_text
? MARKED_SELECTED_COLOR
:
1818 VIEW_UNDERLINED_COLOR
);
1820 /* Print the hex number */
1821 widget_move (view
, top
+ row
, left
+ col
);
1822 tty_print_char (hex_char
[c
/ 16]);
1823 tty_print_char (hex_char
[c
% 16]);
1826 /* Print the separator */
1827 attrset (NORMAL_COLOR
);
1828 if (bytes
!= view
->bytes_per_line
- 1) {
1829 tty_print_char (' ');
1832 /* After every four bytes, print a group separator */
1833 if (bytes
% 4 == 3) {
1834 if (view
->data_area
.width
>= 80) {
1835 tty_print_one_vline ();
1838 tty_print_char (' ');
1843 /* Select the color for the character; this differs from the
1844 * hex color when boldflag == MARK_CURSOR */
1846 boldflag
== MARK_NORMAL
? NORMAL_COLOR
:
1847 boldflag
== MARK_SELECTED
? MARKED_COLOR
:
1848 boldflag
== MARK_CHANGED
? VIEW_UNDERLINED_COLOR
:
1849 /* boldflag == MARK_CURSOR */
1850 view
->hexview_in_text
? VIEW_UNDERLINED_COLOR
:
1851 MARKED_SELECTED_COLOR
);
1853 c
= convert_to_display_c (c
);
1854 if (!is_printable (c
))
1857 /* Print corresponding character on the text side */
1858 widget_move (view
, top
+ row
, left
+ text_start
+ bytes
);
1861 /* Save the cursor position for view_place_cursor() */
1862 if (from
== view
->hex_cursor
&& view
->hexview_in_text
) {
1863 view
->cursor_row
= row
;
1864 view
->cursor_col
= text_start
+ bytes
;
1869 /* Be polite to the other functions */
1870 attrset (NORMAL_COLOR
);
1872 view_place_cursor (view
);
1873 view
->dpy_end
= from
;
1877 view_display_text (WView
* view
)
1879 const screen_dimen left
= view
->data_area
.left
;
1880 const screen_dimen top
= view
->data_area
.top
;
1881 const screen_dimen width
= view
->data_area
.width
;
1882 const screen_dimen height
= view
->data_area
.height
;
1883 screen_dimen row
, col
;
1886 struct hexedit_change_node
*curr
= view
->change_list
;
1888 view_display_clean (view
);
1889 view_display_ruler (view
);
1891 /* Find the first displayable changed byte */
1892 from
= view
->dpy_start
;
1893 while (curr
&& (curr
->offset
< from
)) {
1897 attrset (NORMAL_COLOR
);
1898 for (row
= 0, col
= 0; row
< height
&& (c
= get_byte (view
, from
)) != -1; from
++) {
1900 if (view
->text_nroff_mode
&& c
== '\b') {
1904 if ((c_next
= get_byte_indexed (view
, from
, 1)) != -1
1905 && is_printable (c_next
)
1907 && (c_prev
= get_byte (view
, from
- 1)) != -1
1908 && is_printable (c_prev
)
1909 && (c_prev
== c_next
|| c_prev
== '_'
1910 || (c_prev
== '+' && c_next
== 'o'))) {
1913 /* We're inside an nroff character sequence at the
1914 * beginning of the screen -- just skip the
1915 * backspace and continue with the next character. */
1922 if (c_prev
== '_' && (c_next
!= '_' || view_count_backspaces (view
, from
) == 1))
1923 attrset (VIEW_UNDERLINED_COLOR
);
1925 attrset (MARKED_COLOR
);
1930 if ((c
== '\n') || (col
>= width
&& view
->text_wrap_mode
)) {
1933 if (c
== '\n' || row
>= height
)
1941 offset_type line
, column
;
1942 view_offset_to_coord (view
, &line
, &column
, from
);
1943 col
+= (8 - column
% 8);
1944 if (view
->text_wrap_mode
&& col
>= width
&& width
!= 0) {
1951 if (view
->search_start
<= from
1952 && from
< view
->search_start
+ view
->search_length
) {
1953 attrset (SELECTED_COLOR
);
1956 if (col
>= view
->dpy_text_column
1957 && col
- view
->dpy_text_column
< width
) {
1958 widget_move (view
, top
+ row
, left
+ (col
- view
->dpy_text_column
));
1959 c
= convert_to_display_c (c
);
1960 if (!is_printable (c
))
1965 attrset (NORMAL_COLOR
);
1967 view
->dpy_end
= from
;
1970 /* Displays as much data from view->dpy_start as fits on the screen */
1972 display (WView
*view
)
1974 view_compute_areas (view
);
1975 if (view
->hex_mode
) {
1976 view_display_hex (view
);
1978 view_display_text (view
);
1980 view_display_status (view
);
1984 view_place_cursor (WView
*view
)
1986 const screen_dimen top
= view
->data_area
.top
;
1987 const screen_dimen left
= view
->data_area
.left
;
1990 col
= view
->cursor_col
;
1991 if (!view
->hexview_in_text
&& view
->hexedit_lownibble
)
1993 widget_move (&view
->widget
, top
+ view
->cursor_row
, left
+ col
);
1997 view_update (WView
*view
)
1999 static int dirt_limit
= 1;
2001 if (view
->dpy_bbar_dirty
) {
2002 view
->dpy_bbar_dirty
= FALSE
;
2004 buttonbar_redraw (view
->widget
.parent
);
2007 if (view
->dirty
> dirt_limit
) {
2008 /* Too many updates skipped -> force a update */
2011 /* Raise the update skipping limit */
2013 if (dirt_limit
> max_dirt_limit
)
2014 dirt_limit
= max_dirt_limit
;
2018 /* We have time to update the screen properly */
2024 /* We are busy -> skipping full update,
2025 only the status line is updated */
2026 view_display_status (view
);
2028 /* Here we had a refresh, if fast scrolling does not work
2029 restore the refresh, although this should not happen */
2033 /* {{{ Hex editor }}} */
2036 enqueue_change (struct hexedit_change_node
**head
,
2037 struct hexedit_change_node
*node
)
2039 /* chnode always either points to the head of the list or
2040 * to one of the ->next fields in the list. The value at
2041 * this location will be overwritten with the new node. */
2042 struct hexedit_change_node
**chnode
= head
;
2044 while (*chnode
!= NULL
&& (*chnode
)->offset
< node
->offset
)
2045 chnode
= &((*chnode
)->next
);
2047 node
->next
= *chnode
;
2052 view_handle_editkey (WView
*view
, int key
)
2054 struct hexedit_change_node
*node
;
2057 /* Has there been a change at this position? */
2058 node
= view
->change_list
;
2059 while (node
&& (node
->offset
!= view
->hex_cursor
))
2062 if (!view
->hexview_in_text
) {
2064 unsigned int hexvalue
= 0;
2066 if (key
>= '0' && key
<= '9')
2067 hexvalue
= 0 + (key
- '0');
2068 else if (key
>= 'A' && key
<= 'F')
2069 hexvalue
= 10 + (key
- 'A');
2070 else if (key
>= 'a' && key
<= 'f')
2071 hexvalue
= 10 + (key
- 'a');
2073 return MSG_NOT_HANDLED
;
2076 byte_val
= node
->value
;
2078 byte_val
= get_byte (view
, view
->hex_cursor
);
2080 if (view
->hexedit_lownibble
) {
2081 byte_val
= (byte_val
& 0xf0) | (hexvalue
);
2083 byte_val
= (byte_val
& 0x0f) | (hexvalue
<< 4);
2087 if (key
< 256 && (is_printable (key
) || (key
== '\n')))
2090 return MSG_NOT_HANDLED
;
2093 node
= g_new (struct hexedit_change_node
, 1);
2094 node
->offset
= view
->hex_cursor
;
2095 node
->value
= byte_val
;
2096 enqueue_change (&view
->change_list
, node
);
2098 node
->value
= byte_val
;
2102 view_move_right (view
, 1);
2107 view_hexedit_save_changes (WView
*view
)
2109 struct hexedit_change_node
*curr
, *next
;
2113 if (view
->change_list
== NULL
)
2117 assert (view
->filename
!= NULL
);
2118 fp
= mc_open (view
->filename
, O_WRONLY
);
2122 for (curr
= view
->change_list
; curr
!= NULL
; curr
= next
) {
2125 if (mc_lseek (fp
, curr
->offset
, SEEK_SET
) == -1
2126 || mc_write (fp
, &(curr
->value
), 1) != 1)
2129 /* delete the saved item from the change list */
2130 view
->change_list
= next
;
2132 view_set_byte (view
, curr
->offset
, curr
->value
);
2136 if (mc_close (fp
) == -1) {
2137 error
= g_strdup (strerror (errno
));
2138 message (D_ERROR
, _(" Save file "),
2139 _(" Error while closing the file: \n %s \n"
2140 " Data may have been written or not. "), error
);
2147 error
= g_strdup (strerror (errno
));
2148 text
= g_strdup_printf (_(" Cannot save file: \n %s "), error
);
2150 (void) mc_close (fp
);
2152 answer
= query_dialog (_(" Save file "), text
, D_ERROR
,
2153 2, _("&Retry"), _("&Cancel"));
2161 /* {{{ Miscellaneous functions }}} */
2164 view_ok_to_quit (WView
*view
)
2168 if (view
->change_list
== NULL
)
2171 r
= query_dialog (_("Quit"),
2172 _(" File was modified, Save with exit? "), D_NORMAL
, 3,
2173 _("&Cancel quit"), _("&Yes"), _("&No"));
2177 return view_hexedit_save_changes (view
);
2179 view_hexedit_free_change_list (view
);
2187 my_define (Dlg_head
*h
, int idx
, const char *text
, void (*fn
) (WView
*),
2190 buttonbar_set_label_data (h
, idx
, text
, (buttonbarfn
) fn
, view
);
2193 /* {{{ Searching }}} */
2195 /* Case insensitive search of text in data */
2197 icase_search_p (WView
*view
, char *text
, char *data
, int nothing
)
2201 const int direction
= view
->direction
;
2205 /* If we are searching backwards, reverse the string */
2206 if (direction
== -1) {
2207 g_strreverse (text
);
2208 g_strreverse (data
);
2211 q
= _icase_search (text
, data
, &lng
);
2213 if (direction
== -1) {
2214 g_strreverse (text
);
2215 g_strreverse (data
);
2220 view
->search_start
= q
- data
- lng
;
2222 view
->search_start
= strlen (data
) - (q
- data
);
2223 view
->search_length
= lng
;
2230 grow_string_buffer (char *text
, gulong
*size
)
2234 /* The grow steps */
2236 new = g_realloc (text
, *size
);
2244 get_line_at (WView
*view
, offset_type
*p
, offset_type
*skipped
)
2246 char *buffer
= NULL
;
2247 gulong buffer_size
= 0;
2248 offset_type usable_size
= 0;
2250 const int direction
= view
->direction
;
2251 offset_type pos
= *p
;
2257 if (pos
== 0 && direction
== -1)
2260 /* skip over all the possible zeros in the file */
2261 while ((ch
= get_byte (view
, pos
)) == 0) {
2262 if (pos
== 0 && direction
== -1)
2269 if (i
== 0 && (pos
!= 0 || direction
== -1)) {
2270 prev
= get_byte (view
, pos
- direction
);
2271 if ((prev
== -1) || (prev
== '\n'))
2275 for (i
= 1; ch
!= -1; ch
= get_byte (view
, pos
)) {
2276 if (i
>= usable_size
) {
2277 buffer
= grow_string_buffer (buffer
, &buffer_size
);
2278 usable_size
= buffer_size
- 2; /* prev & null terminator */
2283 if (pos
== 0 && direction
== -1)
2288 if (ch
== '\n' || ch
== '\0') {
2289 i
--; /* Strip newline/zero */
2298 /* If we are searching backwards, reverse the string */
2299 if (direction
== -1) {
2300 g_strreverse (buffer
+ 1);
2309 search_update_steps (WView
*view
)
2311 offset_type filesize
= view_get_filesize (view
);
2313 view
->update_steps
= 40000;
2314 else /* viewing a data stream, not a file */
2315 view
->update_steps
= filesize
/ 100;
2317 /* Do not update the percent display but every 20 ks */
2318 if (view
->update_steps
< 20000)
2319 view
->update_steps
= 20000;
2323 search (WView
*view
, char *text
,
2324 int (*search
) (WView
*, char *, char *, int))
2326 char *s
= NULL
; /* The line we read from the view buffer */
2327 offset_type p
, beginning
, search_start
;
2332 /* Used to keep track of where the line starts, when looking forward
2333 * is the index before transfering the line; the reverse case uses
2334 * the position returned after the line has been read */
2335 offset_type forward_line_start
;
2336 offset_type reverse_line_start
;
2340 d
= create_message (D_NORMAL
, _("Search"), _("Searching %s"), text
);
2344 found_len
= view
->search_length
;
2345 search_start
= view
->search_start
;
2347 if (view
->direction
== 1) {
2348 p
= search_start
+ ((found_len
) ? 1 : 0);
2350 p
= search_start
- ((found_len
&& search_start
>= 1) ? 1 : 0);
2354 /* Compute the percent steps */
2355 search_update_steps (view
);
2356 view
->update_activate
= 0;
2358 enable_interrupt_key ();
2359 for (;; g_free (s
)) {
2360 if (p
>= view
->update_activate
) {
2361 view
->update_activate
+= view
->update_steps
;
2363 view_percent (view
, p
);
2366 if (got_interrupt ())
2369 forward_line_start
= p
;
2370 s
= get_line_at (view
, &p
, &t
);
2371 reverse_line_start
= p
;
2376 search_status
= (*search
) (view
, text
, s
+ 1, match_normal
);
2377 if (search_status
< 0) {
2382 if (search_status
== 0)
2385 /* We found the string */
2387 /* Handle ^ and $ when regexp search starts at the middle of the line */
2388 if (*s
&& !view
->search_start
&& (search
== regexp_view_search
)) {
2389 if ((*text
== '^' && view
->direction
== 1)
2390 || (view
->direction
== -1 && text
[strlen (text
) - 1] == '$')
2395 /* Record the position used to continue the search */
2396 if (view
->direction
== 1)
2397 t
+= forward_line_start
;
2399 t
= reverse_line_start
? reverse_line_start
+ 3 : 0;
2400 view
->search_start
+= t
;
2402 if (t
!= beginning
) {
2403 view
->dpy_start
= t
;
2409 disable_interrupt_key ();
2415 message (0, _("Search"), _(" Search string not found "));
2416 view
->search_length
= 0;
2420 /* Search buffer (its size is len) in the complete buffer
2421 * returns the position where the block was found or INVALID_OFFSET
2424 block_search (WView
*view
, const char *buffer
, int len
)
2426 int direction
= view
->direction
;
2427 const char *d
= buffer
;
2431 enable_interrupt_key ();
2433 e
= view
->search_start
+ ((view
->search_length
) ? 1 : 0);
2435 e
= view
->search_start
2436 - ((view
->search_length
&& view
->search_start
>= 1) ? 1 : 0);
2438 search_update_steps (view
);
2439 view
->update_activate
= 0;
2441 if (direction
== -1) {
2442 for (d
+= len
- 1;; e
--) {
2443 if (e
<= view
->update_activate
) {
2444 view
->update_activate
-= view
->update_steps
;
2446 view_percent (view
, e
);
2449 if (got_interrupt ())
2452 b
= get_byte (view
, e
);
2456 disable_interrupt_key ();
2461 e
+= buffer
+ len
- 1 - d
;
2462 d
= buffer
+ len
- 1;
2468 while (get_byte (view
, e
) != -1) {
2469 if (e
>= view
->update_activate
) {
2470 view
->update_activate
+= view
->update_steps
;
2472 view_percent (view
, e
);
2475 if (got_interrupt ())
2478 b
= get_byte (view
, e
++);
2482 if (d
- buffer
== len
) {
2483 disable_interrupt_key ();
2492 disable_interrupt_key ();
2493 return INVALID_OFFSET
;
2497 * Search in the hex mode. Supported input:
2498 * - numbers (oct, dec, hex). Each of them matches one byte.
2499 * - strings in double quotes. Matches exactly without quotes.
2502 hex_search (WView
*view
, const char *text
)
2504 char *buffer
; /* Parsed search string */
2505 char *cur
; /* Current position in it */
2506 int block_len
; /* Length of the search string */
2507 offset_type pos
; /* Position of the string in the file */
2508 int parse_error
= 0;
2511 view
->search_length
= 0;
2515 /* buffer will never be longer that text */
2516 buffer
= g_new (char, strlen (text
));
2519 /* First convert the string to a stream of bytes */
2524 /* Skip leading spaces */
2525 if (*text
== ' ' || *text
== '\t') {
2530 /* %i matches octal, decimal, and hexadecimal numbers */
2531 if (sscanf (text
, "%i%n", &val
, &ptr
) > 0) {
2532 /* Allow signed and unsigned char in the user input */
2533 if (val
< -128 || val
> 255) {
2538 *cur
++ = (char) val
;
2543 /* Try quoted string, strip quotes */
2545 const char *next_quote
;
2548 next_quote
= strchr (text
, '"');
2550 memcpy (cur
, text
, next_quote
- text
);
2551 cur
+= next_quote
- text
;
2552 text
= next_quote
+ 1;
2562 block_len
= cur
- buffer
;
2564 /* No valid bytes in the user input */
2565 if (block_len
<= 0 || parse_error
) {
2566 message (0, _("Search"), _("Invalid hex search expression"));
2568 view
->search_length
= 0;
2572 /* Then start the search */
2573 pos
= block_search (view
, buffer
, block_len
);
2577 if (pos
== INVALID_OFFSET
) {
2578 message (0, _("Search"), _(" Search string not found "));
2579 view
->search_length
= 0;
2583 view
->search_start
= pos
;
2584 view
->search_length
= block_len
;
2585 /* Set the edit cursor to the search position, left nibble */
2586 view
->hex_cursor
= view
->search_start
;
2587 view
->hexedit_lownibble
= FALSE
;
2589 /* Adjust the file offset */
2590 view
->dpy_start
= pos
- pos
% view
->bytes_per_line
;
2594 regexp_view_search (WView
*view
, char *pattern
, char *string
,
2598 static char *old_pattern
= NULL
;
2599 static int old_type
;
2600 regmatch_t pmatch
[1];
2601 int i
, flags
= REG_ICASE
;
2603 if (old_pattern
== NULL
|| strcmp (old_pattern
, pattern
) != 0
2604 || old_type
!= match_type
) {
2605 if (old_pattern
!= NULL
) {
2607 g_free (old_pattern
);
2610 for (i
= 0; pattern
[i
] != '\0'; i
++) {
2611 if (isupper ((unsigned char) pattern
[i
])) {
2616 flags
|= REG_EXTENDED
;
2617 if (regcomp (&r
, pattern
, flags
)) {
2618 message (1, MSG_ERROR
, _(" Invalid regular expression "));
2621 old_pattern
= g_strdup (pattern
);
2622 old_type
= match_type
;
2624 if (regexec (&r
, string
, 1, pmatch
, 0) != 0)
2626 view
->search_length
= pmatch
[0].rm_eo
- pmatch
[0].rm_so
;
2627 view
->search_start
= pmatch
[0].rm_so
;
2632 do_regexp_search (WView
*view
)
2634 search (view
, view
->search_exp
, regexp_view_search
);
2635 /* Had a refresh here */
2641 do_normal_search (WView
*view
)
2644 hex_search (view
, view
->search_exp
);
2646 search (view
, view
->search_exp
, icase_search_p
);
2647 /* Had a refresh here */
2652 /* {{{ User-definable commands }}} */
2655 The functions in this section can be bound to hotkeys. They are all
2656 of the same type (taking a pointer to WView as parameter and
2657 returning void). TODO: In the not-too-distant future, these commands
2658 will become fully configurable, like they already are in the
2659 internal editor. By convention, all the function names end in
2664 view_help_cmd (void)
2666 interactive_display (NULL
, "[Internal File Viewer]");
2669 /* Toggle between hexview and hexedit mode */
2671 view_toggle_hexedit_mode_cmd (WView
*view
)
2673 view_toggle_hexedit_mode (view
);
2677 /* Toggle between wrapped and unwrapped view */
2679 view_toggle_wrap_mode_cmd (WView
*view
)
2681 view_toggle_wrap_mode (view
);
2685 /* Toggle between hex view and text view */
2687 view_toggle_hex_mode_cmd (WView
*view
)
2689 view_toggle_hex_mode (view
);
2694 view_moveto_line_cmd (WView
*view
)
2696 char *answer
, *answer_end
, prompt
[BUF_SMALL
];
2697 offset_type line
, col
;
2699 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
2701 g_snprintf (prompt
, sizeof (prompt
),
2702 _(" The current line number is %d.\n"
2703 " Enter the new line number:"), (int) (line
+ 1));
2704 answer
= input_dialog (_(" Goto line "), prompt
, "");
2705 if (answer
!= NULL
&& answer
[0] != '\0') {
2707 line
= strtoul (answer
, &answer_end
, 10);
2708 if (*answer_end
== '\0' && errno
== 0 && line
>= 1)
2709 view_moveto (view
, line
- 1, 0);
2717 view_moveto_addr_cmd (WView
*view
)
2719 char *line
, *error
, prompt
[BUF_SMALL
];
2722 g_snprintf (prompt
, sizeof (prompt
),
2723 _(" The current address is 0x%lx.\n"
2724 " Enter the new address:"), view
->hex_cursor
);
2725 line
= input_dialog (_(" Goto Address "), prompt
, "");
2727 if (*line
!= '\0') {
2728 addr
= strtoul (line
, &error
, 0);
2729 if ((*error
== '\0') && get_byte (view
, addr
) != -1) {
2730 view_moveto_offset (view
, addr
);
2740 view_hexedit_save_changes_cmd (WView
*view
)
2742 (void) view_hexedit_save_changes (view
);
2745 /* {{{ Searching }}} */
2748 regexp_search (WView
*view
, int direction
)
2753 defval
= (view
->search_exp
!= NULL
) ? view
->search_exp
: "";
2755 regexp
= input_dialog (_("Search"), _(" Enter regexp:"), defval
);
2756 if (regexp
== NULL
|| regexp
[0] == '\0')
2759 g_free (view
->search_exp
);
2760 view
->search_exp
= regexp
;
2763 view
->direction
= direction
;
2764 do_regexp_search (view
);
2765 view
->last_search
= do_regexp_search
;
2771 /* {{{ User-definable commands }}} */
2774 view_regexp_search_cmd (WView
*view
)
2776 regexp_search (view
, 1);
2781 view_normal_search_cmd (WView
*view
)
2783 char *defval
, *exp
= NULL
;
2786 SEARCH_DLG_HEIGHT
= 8,
2787 SEARCH_DLG_WIDTH
= 58
2790 static int replace_backwards
;
2791 int treplace_backwards
= replace_backwards
;
2793 static QuickWidget quick_widgets
[] = {
2794 {quick_button
, 6, 10, 5, SEARCH_DLG_HEIGHT
, N_("&Cancel"), 0,
2797 {quick_button
, 2, 10, 5, SEARCH_DLG_HEIGHT
, N_("&OK"), 0, B_ENTER
,
2799 {quick_checkbox
, 3, SEARCH_DLG_WIDTH
, 4, SEARCH_DLG_HEIGHT
,
2800 N_("&Backwards"), 0, 0,
2802 {quick_input
, 3, SEARCH_DLG_WIDTH
, 3, SEARCH_DLG_HEIGHT
, "", 52, 0,
2803 0, 0, N_("Search")},
2804 {quick_label
, 2, SEARCH_DLG_WIDTH
, 2, SEARCH_DLG_HEIGHT
,
2805 N_(" Enter search string:"), 0, 0,
2809 static QuickDialog Quick_input
= {
2810 SEARCH_DLG_WIDTH
, SEARCH_DLG_HEIGHT
, -1, 0, N_("Search"),
2811 "[Input Line Keys]", quick_widgets
, 0
2814 defval
= g_strdup ((view
->search_exp
!= NULL
) ? view
->search_exp
: "");
2815 convert_to_display (defval
);
2817 quick_widgets
[2].result
= &treplace_backwards
;
2818 quick_widgets
[3].str_result
= &exp
;
2819 quick_widgets
[3].text
= defval
;
2821 if (quick_dialog (&Quick_input
) == B_CANCEL
)
2824 replace_backwards
= treplace_backwards
;
2826 if (exp
== NULL
|| exp
[0] == '\0')
2829 convert_from_input (exp
);
2831 g_free (view
->search_exp
);
2832 view
->search_exp
= exp
;
2835 view
->direction
= replace_backwards
? -1 : 1;
2836 do_normal_search (view
);
2837 view
->last_search
= do_normal_search
;
2845 view_toggle_magic_mode_cmd (WView
*view
)
2847 view_toggle_magic_mode (view
);
2852 view_toggle_nroff_mode_cmd (WView
*view
)
2854 view_toggle_nroff_mode (view
);
2859 view_quit_cmd (WView
*view
)
2861 if (view_ok_to_quit (view
))
2862 dlg_stop (view
->widget
.parent
);
2865 /* {{{ Miscellaneous functions }}} */
2867 /* Define labels and handlers for functional keys */
2869 view_labels (WView
*view
)
2871 Dlg_head
*h
= view
->widget
.parent
;
2873 buttonbar_set_label (h
, 1, gettext_ui("ButtonBar|Help"), view_help_cmd
);
2875 my_define (h
, 10, gettext_ui("ButtonBar|Quit"), view_quit_cmd
, view
);
2876 my_define (h
, 4, view
->hex_mode
2877 ? gettext_ui("ButtonBar|Ascii")
2878 : gettext_ui("ButtonBar|Hex"),
2879 view_toggle_hex_mode_cmd
, view
);
2880 my_define (h
, 5, view
->hex_mode
2881 ? gettext_ui("ButtonBar|Goto")
2882 : gettext_ui("ButtonBar|Line"),
2883 view
->hex_mode
? view_moveto_addr_cmd
: view_moveto_line_cmd
, view
);
2885 if (view
->hex_mode
) {
2886 if (view
->hexedit_mode
) {
2887 my_define (h
, 2, gettext_ui("ButtonBar|View"),
2888 view_toggle_hexedit_mode_cmd
, view
);
2889 } else if (view
->datasource
== DS_FILE
) {
2890 my_define (h
, 2, gettext_ui("ButtonBar|Edit"),
2891 view_toggle_hexedit_mode_cmd
, view
);
2893 my_define (h
, 2, "", NULL
, view
);
2895 my_define (h
, 6, gettext_ui("ButtonBar|Save"),
2896 view_hexedit_save_changes_cmd
, view
);
2898 my_define (h
, 2, view
->text_wrap_mode
2899 ? gettext_ui("ButtonBar|UnWrap")
2900 : gettext_ui("ButtonBar|Wrap"),
2901 view_toggle_wrap_mode_cmd
, view
);
2902 my_define (h
, 6, gettext_ui("ButtonBar|RxSrch"),
2903 view_regexp_search_cmd
, view
);
2906 my_define (h
, 7, view
->hex_mode
2907 ? gettext_ui("ButtonBar|HxSrch")
2908 : gettext_ui("ButtonBar|Search"),
2909 view_normal_search_cmd
, view
);
2910 my_define (h
, 8, view
->magic_mode
2911 ? gettext_ui("ButtonBar|Raw")
2912 : gettext_ui("ButtonBar|Parse"),
2913 view_toggle_magic_mode_cmd
, view
);
2915 /* don't override the key to access the main menu */
2916 if (!view_is_in_panel (view
)) {
2917 my_define (h
, 9, view
->text_nroff_mode
2918 ? gettext_ui("ButtonBar|Unform")
2919 : gettext_ui("ButtonBar|Format"),
2920 view_toggle_nroff_mode_cmd
, view
);
2921 my_define (h
, 3, gettext_ui("ButtonBar|Quit"), view_quit_cmd
, view
);
2925 /* {{{ Event handling }}} */
2927 /* Check for left and right arrows, possibly with modifiers */
2929 check_left_right_keys (WView
*view
, int c
)
2931 if (c
== KEY_LEFT
) {
2932 view_move_left (view
, 1);
2936 if (c
== KEY_RIGHT
) {
2937 view_move_right (view
, 1);
2941 /* Ctrl with arrows moves by 10 postions in the unwrap mode */
2942 if (view
->hex_mode
|| view
->text_wrap_mode
)
2943 return MSG_NOT_HANDLED
;
2945 if (c
== (KEY_M_CTRL
| KEY_LEFT
)) {
2946 if (view
->dpy_text_column
>= 10)
2947 view
->dpy_text_column
-= 10;
2949 view
->dpy_text_column
= 0;
2954 if (c
== (KEY_M_CTRL
| KEY_RIGHT
)) {
2955 if (view
->dpy_text_column
<= OFFSETTYPE_MAX
- 10)
2956 view
->dpy_text_column
+= 10;
2958 view
->dpy_text_column
= OFFSETTYPE_MAX
;
2963 return MSG_NOT_HANDLED
;
2966 /* {{{ User-definable commands }}} */
2969 view_continue_search_cmd (WView
*view
)
2971 if (view
->last_search
) {
2972 view
->last_search (view
);
2974 /* if not... then ask for an expression */
2975 view_normal_search_cmd (view
);
2980 view_toggle_ruler_cmd (WView
*view
)
2982 static const enum ruler_type next
[3] = {
2988 assert ((size_t) ruler
< 3);
2989 ruler
= next
[(size_t) ruler
];
2993 /* {{{ Event handling }}} */
2995 static void view_cmk_move_up (void *w
, int n
) {
2996 view_move_up ((WView
*) w
, n
);
2998 static void view_cmk_move_down (void *w
, int n
) {
2999 view_move_down ((WView
*) w
, n
);
3001 static void view_cmk_moveto_top (void *w
, int n
) {
3003 view_moveto_top ((WView
*) w
);
3005 static void view_cmk_moveto_bottom (void *w
, int n
) {
3007 view_moveto_bottom ((WView
*) w
);
3012 view_handle_key (WView
*view
, int c
)
3014 c
= convert_from_input_c (c
);
3016 if (view
->hex_mode
) {
3019 view
->hexview_in_text
= !view
->hexview_in_text
;
3024 view_moveto_bol (view
);
3029 view_move_left (view
, 1);
3033 view_moveto_eol (view
);
3037 view_move_right (view
, 1);
3041 if (view
->hexedit_mode
3042 && view_handle_editkey (view
, c
) == MSG_HANDLED
)
3046 if (check_left_right_keys (view
, c
))
3049 if (check_movement_keys (c
, view
->data_area
.height
+ 1, view
,
3050 view_cmk_move_up
, view_cmk_move_down
,
3051 view_cmk_moveto_top
, view_cmk_moveto_bottom
))
3057 regexp_search (view
, -1);
3061 regexp_search (view
, 1);
3064 /* Continue search */
3069 view_continue_search_cmd (view
);
3074 view_toggle_ruler_cmd (view
);
3078 view_move_left (view
, 1);
3084 view_move_down (view
, 1);
3088 view_move_down (view
, (view
->data_area
.height
+ 1) / 2);
3092 view_move_up (view
, (view
->data_area
.height
+ 1) / 2);
3097 view_move_up (view
, 1);
3101 view_move_right (view
, 1);
3106 view_move_down (view
, view
->data_area
.height
);
3113 /* Unlike Ctrl-O, run a new shell if the subshell is not running. */
3119 view_move_up (view
, view
->data_area
.height
);
3123 view_move_up (view
, 2);
3127 view_move_down (view
, 2);
3131 view
->marks
[view
->marker
] = view
->dpy_start
;
3135 view
->dpy_start
= view
->marks
[view
->marker
];
3139 /* Use to indicate parent that we want to see the next/previous file */
3140 /* Does not work in panel mode */
3143 if (!view_is_in_panel (view
))
3144 view
->move_dir
= c
== XCTRL ('f') ? 1 : -1;
3149 if (view_ok_to_quit (view
))
3150 view
->want_to_quit
= TRUE
;
3155 do_select_codepage ();
3159 #endif /* HAVE_CHARSET */
3161 #ifdef MC_ENABLE_DEBUGGING_CODE
3162 case 't': /* mnemonic: "test" */
3163 view_ccache_dump (view
);
3167 if (c
>= '0' && c
<= '9')
3168 view
->marker
= c
- '0';
3171 return MSG_NOT_HANDLED
;
3176 view_event (WView
*view
, Gpm_Event
*event
, int *result
)
3180 *result
= MOU_NORMAL
;
3182 /* We are not interested in the release events */
3183 if (!(event
->type
& (GPM_DOWN
| GPM_DRAG
)))
3187 if ((event
->buttons
& GPM_B_UP
) && (event
->type
& GPM_DOWN
)) {
3188 view_move_up (view
, 2);
3191 if ((event
->buttons
& GPM_B_DOWN
) && (event
->type
& GPM_DOWN
)) {
3192 view_move_down (view
, 2);
3199 /* Scrolling left and right */
3200 if (!view
->text_wrap_mode
) {
3201 if (x
< view
->data_area
.width
* 1/4) {
3202 view_move_left (view
, 1);
3204 } else if (x
< view
->data_area
.width
* 3/4) {
3205 /* ignore the click */
3207 view_move_right (view
, 1);
3212 /* Scrolling up and down */
3213 if (y
< view
->data_area
.top
+ view
->data_area
.height
* 1/3) {
3214 if (mouse_move_pages_viewer
)
3215 view_move_up (view
, view
->data_area
.height
/ 2);
3217 view_move_up (view
, 1);
3219 } else if (y
< view
->data_area
.top
+ view
->data_area
.height
* 2/3) {
3220 /* ignore the click */
3222 if (mouse_move_pages_viewer
)
3223 view_move_down (view
, view
->data_area
.height
/ 2);
3225 view_move_down (view
, 1);
3232 *result
= MOU_REPEAT
;
3236 /* Real view only */
3238 real_view_event (Gpm_Event
*event
, void *x
)
3240 WView
*view
= (WView
*) x
;
3243 if (view_event (view
, event
, &result
))
3249 view_adjust_size (Dlg_head
*h
)
3254 /* Look up the viewer and the buttonbar, we assume only two widgets here */
3255 view
= (WView
*) find_widget_type (h
, view_callback
);
3256 bar
= find_buttonbar (h
);
3257 widget_set_size (&view
->widget
, 0, 0, LINES
- 1, COLS
);
3258 widget_set_size ((Widget
*) bar
, LINES
- 1, 0, 1, COLS
);
3260 view_compute_areas (view
);
3261 view_update_bytes_per_line (view
);
3264 /* Callback for the view dialog */
3266 view_dialog_callback (Dlg_head
*h
, dlg_msg_t msg
, int parm
)
3270 view_adjust_size (h
);
3274 return default_dlg_callback (h
, msg
, parm
);
3278 /* {{{ External interface }}} */
3280 /* Real view only */
3282 mc_internal_viewer (const char *command
, const char *file
,
3283 int *move_dir_p
, int start_line
)
3290 /* Create dialog and widgets, put them on the dialog */
3292 create_dlg (0, 0, LINES
, COLS
, NULL
, view_dialog_callback
,
3293 "[Internal File Viewer]", NULL
, DLG_WANT_TAB
);
3295 wview
= view_new (0, 0, COLS
, LINES
- 1, 0);
3297 bar
= buttonbar_new (1);
3299 add_widget (view_dlg
, bar
);
3300 add_widget (view_dlg
, wview
);
3302 succeeded
= view_load (wview
, command
, file
, start_line
);
3306 *move_dir_p
= wview
->move_dir
;
3311 destroy_dlg (view_dlg
);
3316 /* {{{ Miscellaneous functions }}} */
3321 WView
*view
= (WView
*) v
;
3324 /* If the user is busy typing, wait until he finishes to update the
3327 if (!hook_present (idle_hook
, view_hook
))
3328 add_hook (&idle_hook
, view_hook
, v
);
3332 delete_hook (&idle_hook
, view_hook
);
3334 if (get_current_type () == view_listing
)
3335 panel
= current_panel
;
3336 else if (get_other_type () == view_listing
)
3337 panel
= other_panel
;
3341 view_load (view
, 0, panel
->dir
.list
[panel
->selected
].fname
, 0);
3345 /* {{{ Event handling }}} */
3348 view_callback (Widget
*w
, widget_msg_t msg
, int parm
)
3350 WView
*view
= (WView
*) w
;
3352 Dlg_head
*h
= view
->widget
.parent
;
3354 view_compute_areas (view
);
3355 view_update_bytes_per_line (view
);
3359 if (view_is_in_panel (view
))
3360 add_hook (&select_file_hook
, view_hook
, view
);
3362 view
->dpy_bbar_dirty
= TRUE
;
3371 view_place_cursor (view
);
3375 i
= view_handle_key ((WView
*) view
, parm
);
3376 if (view
->want_to_quit
&& !view_is_in_panel (view
))
3384 view
->dpy_bbar_dirty
= TRUE
;
3388 case WIDGET_DESTROY
:
3390 if (view_is_in_panel (view
))
3391 delete_hook (&select_file_hook
, view_hook
);
3395 return default_proc (msg
, parm
);
3399 /* {{{ External interface }}} */
3402 view_new (int y
, int x
, int cols
, int lines
, int is_panel
)
3404 WView
*view
= g_new0 (WView
, 1);
3407 init_widget (&view
->widget
, y
, x
, lines
, cols
,
3411 view
->filename
= NULL
;
3412 view
->command
= NULL
;
3414 view_set_datasource_none (view
);
3416 view
->growbuf_in_use
= FALSE
;
3417 /* leave the other growbuf fields uninitialized */
3419 view
->hex_mode
= FALSE
;
3420 view
->hexedit_mode
= FALSE
;
3421 view
->hexview_in_text
= FALSE
;
3422 view
->text_nroff_mode
= FALSE
;
3423 view
->text_wrap_mode
= FALSE
;
3424 view
->magic_mode
= FALSE
;
3426 view
->hexedit_lownibble
= FALSE
;
3427 view
->coord_cache
= NULL
;
3429 view
->dpy_frame_size
= is_panel
? 1 : 0;
3430 view
->dpy_start
= 0;
3431 view
->dpy_text_column
= 0;
3433 view
->hex_cursor
= 0;
3434 view
->cursor_col
= 0;
3435 view
->cursor_row
= 0;
3436 view
->change_list
= NULL
;
3438 /* {status,ruler,data}_area are left uninitialized */
3441 view
->dpy_bbar_dirty
= TRUE
;
3442 view
->bytes_per_line
= 1;
3444 view
->search_start
= 0;
3445 view
->search_length
= 0;
3446 view
->search_exp
= NULL
;
3447 view
->direction
= 1; /* forward */
3448 view
->last_search
= 0; /* it's a function */
3450 view
->want_to_quit
= FALSE
;
3452 for (i
= 0; i
< sizeof(view
->marks
) / sizeof(view
->marks
[0]); i
++)
3456 view
->update_steps
= 0;
3457 view
->update_activate
= 0;
3459 if (default_hex_mode
)
3460 view_toggle_hex_mode (view
);
3461 if (default_nroff_flag
)
3462 view_toggle_nroff_mode (view
);
3463 if (global_wrap_mode
)
3464 view_toggle_wrap_mode (view
);
3465 if (default_magic_flag
)
3466 view_toggle_magic_mode (view
);