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>
46 #include <mhl/types.h>
50 #include "cmd.h" /* For view_other_cmd */
51 #include "dialog.h" /* Needed by widget.h */
52 #include "widget.h" /* Needed for buttonbar_new */
56 #include "key.h" /* For mi_getch() */
59 #include "wtools.h" /* For query_set_sel() */
61 #include "panel.h" /* Needed for current_panel and other_panel */
64 #include "main.h" /* slow_terminal */
68 #include "selcodepage.h"
70 /* Block size for reading files in parts */
71 #define VIEW_PAGE_SIZE ((size_t) 8192)
72 #define VIEW_COORD_CACHE_GRANUL 1024
74 typedef unsigned char byte
;
76 /* Offset in bytes into a file */
77 typedef unsigned long offset_type
;
78 #define INVALID_OFFSET ((offset_type) -1)
79 #define OFFSETTYPE_MAX (~((offset_type) 0))
80 #define OFFSETTYPE_PRIX "lX"
81 #define OFFSETTYPE_PRId "lu"
83 /* A width or height on the screen */
84 typedef unsigned int screen_dimen
;
86 /* A cache entry for mapping offsets into line/column pairs and vice versa.
87 * cc_offset, cc_line, and cc_column are the 0-based values of the offset,
88 * line and column of that cache entry. cc_nroff_column is the column
89 * corresponding to cc_offset in nroff mode.
91 struct coord_cache_entry
{
92 offset_type cc_offset
;
94 offset_type cc_column
;
95 offset_type cc_nroff_column
;
98 /* A node for building a change list on change_list */
99 struct hexedit_change_node
{
100 struct hexedit_change_node
*next
;
105 /* data sources of the view */
107 DS_NONE
, /* No data available */
108 DS_STDIO_PIPE
, /* Data comes from a pipe using popen/pclose */
109 DS_VFS_PIPE
, /* Data comes from a piped-in VFS file */
110 DS_FILE
, /* Data comes from a VFS file */
111 DS_STRING
/* Data comes from a string in memory */
115 screen_dimen top
, left
;
116 screen_dimen height
, width
;
122 char *filename
; /* Name of the file */
123 char *command
; /* Command used to pipe data in */
125 enum view_ds datasource
; /* Where the displayed data comes from */
127 /* stdio pipe data source */
128 FILE *ds_stdio_pipe
; /* Output of a shell command */
130 /* vfs pipe data source */
131 int ds_vfs_pipe
; /* Non-seekable vfs file descriptor */
133 /* vfs file data source */
134 int ds_file_fd
; /* File with random access */
135 off_t ds_file_filesize
; /* Size of the file */
136 off_t ds_file_offset
; /* Offset of the currently loaded data */
137 byte
*ds_file_data
; /* Currently loaded data */
138 size_t ds_file_datalen
; /* Number of valid bytes in file_data */
139 size_t ds_file_datasize
; /* Number of allocated bytes in file_data */
141 /* string data source */
142 byte
*ds_string_data
; /* The characters of the string */
143 size_t ds_string_len
; /* The length of the string */
145 /* Growing buffers information */
146 bool growbuf_in_use
; /* Use the growing buffers? */
147 byte
**growbuf_blockptr
; /* Pointer to the block pointers */
148 size_t growbuf_blocks
; /* The number of blocks in *block_ptr */
149 size_t growbuf_lastindex
; /* Number of bytes in the last page of the
151 bool growbuf_finished
; /* TRUE when all data has been read. */
154 bool hex_mode
; /* Hexview or Hexedit */
155 bool hexedit_mode
; /* Hexedit */
156 bool hexview_in_text
; /* Is the hexview cursor in the text area? */
157 bool text_nroff_mode
; /* Nroff-style highlighting */
158 bool text_wrap_mode
; /* Wrap text lines to fit them on the screen */
159 bool magic_mode
; /* Preprocess the file using external programs */
161 /* Additional editor state */
162 bool hexedit_lownibble
; /* Are we editing the last significant nibble? */
163 GArray
*coord_cache
; /* Cache for mapping offsets to cursor positions */
165 /* Display information */
166 screen_dimen dpy_frame_size
;/* Size of the frame surrounding the real viewer */
167 offset_type dpy_start
; /* Offset of the displayed data */
168 offset_type dpy_end
; /* Offset after the displayed data */
169 offset_type dpy_text_column
;/* Number of skipped columns in non-wrap
171 offset_type hex_cursor
; /* Hexview cursor position in file */
172 screen_dimen cursor_col
; /* Cursor column */
173 screen_dimen cursor_row
; /* Cursor row */
174 struct hexedit_change_node
*change_list
; /* Linked list of changes */
175 struct area status_area
; /* Where the status line is displayed */
176 struct area ruler_area
; /* Where the ruler is displayed */
177 struct area data_area
; /* Where the data is displayed */
179 int dirty
; /* Number of skipped updates */
180 bool dpy_bbar_dirty
; /* Does the button bar need to be updated? */
183 int bytes_per_line
; /* Number of bytes per line in hex mode */
185 /* Search variables */
186 offset_type search_start
; /* First character to start searching from */
187 offset_type search_length
; /* Length of found string or 0 if none was found */
188 char *search_exp
; /* The search expression */
189 int direction
; /* 1= forward; -1 backward */
190 void (*last_search
)(WView
*);
191 /* Pointer to the last search command */
192 bool want_to_quit
; /* Prepare for cleanup ... */
195 int marker
; /* mark to use */
196 offset_type marks
[10]; /* 10 marks: 0..9 */
198 int move_dir
; /* return value from widget:
200 * -1 view previous file
204 offset_type update_steps
; /* The number of bytes between percent
206 offset_type update_activate
;/* Last point where we updated the status */
210 /* {{{ Global Variables }}} */
212 /* Maxlimit for skipping updates */
213 int max_dirt_limit
= 10;
215 /* If set, show a ruler */
216 static enum ruler_type
{
220 } ruler
= RULER_NONE
;
222 /* Scrolling is done in pages or line increments */
223 int mouse_move_pages_viewer
= 1;
225 /* wrap mode default */
226 int global_wrap_mode
= 1;
228 int default_hex_mode
= 0;
229 int default_magic_flag
= 1;
230 int default_nroff_flag
= 1;
231 int altered_hex_mode
= 0;
232 int altered_magic_flag
= 0;
233 int altered_nroff_flag
= 0;
235 static const char hex_char
[] = "0123456789ABCDEF";
237 int mcview_remember_file_position
= FALSE
;
239 /* {{{ Function Prototypes }}} */
241 /* Our widget callback */
242 static cb_ret_t
view_callback (Widget
*, widget_msg_t
, int);
244 static int regexp_view_search (WView
* view
, char *pattern
, char *string
,
246 static void view_labels (WView
* view
);
248 static void view_init_growbuf (WView
*);
249 static void view_place_cursor (WView
*view
);
250 static void display (WView
*);
251 static void view_done (WView
*);
253 /* {{{ Helper Functions }}} */
255 /* difference or zero */
256 static inline screen_dimen
257 dimen_doz (screen_dimen a
, screen_dimen b
)
259 return (a
>= b
) ? a
- b
: 0;
262 static inline screen_dimen
263 dimen_min (screen_dimen a
, screen_dimen b
)
265 return (a
< b
) ? a
: b
;
268 static inline offset_type
269 offset_doz (offset_type a
, offset_type b
)
271 return (a
>= b
) ? a
- b
: 0;
274 static inline offset_type
275 offset_rounddown (offset_type a
, offset_type b
)
281 /* {{{ Simple Primitive Functions for WView }}} */
284 view_is_in_panel (WView
*view
)
286 return (view
->dpy_frame_size
!= 0);
290 view_compute_areas (WView
*view
)
292 struct area view_area
;
293 screen_dimen height
, rest
, y
;
295 /* The viewer is surrounded by a frame of size view->dpy_frame_size.
296 * Inside that frame, there are: The status line (at the top),
297 * the data area and an optional ruler, which is shown above or
298 * below the data area. */
300 view_area
.top
= view
->dpy_frame_size
;
301 view_area
.left
= view
->dpy_frame_size
;
302 view_area
.height
= dimen_doz(view
->widget
.lines
, 2 * view
->dpy_frame_size
);
303 view_area
.width
= dimen_doz(view
->widget
.cols
, 2 * view
->dpy_frame_size
);
305 /* Most coordinates of the areas equal those of the whole viewer */
306 view
->status_area
= view_area
;
307 view
->ruler_area
= view_area
;
308 view
->data_area
= view_area
;
310 /* Compute the heights of the areas */
311 rest
= view_area
.height
;
313 height
= dimen_min(rest
, 1);
314 view
->status_area
.height
= height
;
317 height
= dimen_min(rest
, (ruler
== RULER_NONE
|| view
->hex_mode
) ? 0 : 2);
318 view
->ruler_area
.height
= height
;
321 view
->data_area
.height
= rest
;
323 /* Compute the position of the areas */
326 view
->status_area
.top
= y
;
327 y
+= view
->status_area
.height
;
329 if (ruler
== RULER_TOP
) {
330 view
->ruler_area
.top
= y
;
331 y
+= view
->ruler_area
.height
;
334 view
->data_area
.top
= y
;
335 y
+= view
->data_area
.height
;
337 if (ruler
== RULER_BOTTOM
) {
338 view
->ruler_area
.top
= y
;
339 y
+= view
->ruler_area
.height
;
344 view_hexedit_free_change_list (WView
*view
)
346 struct hexedit_change_node
*curr
, *next
;
348 for (curr
= view
->change_list
; curr
!= NULL
; curr
= next
) {
352 view
->change_list
= NULL
;
356 /* {{{ Growing buffer }}} */
359 view_init_growbuf (WView
*view
)
361 view
->growbuf_in_use
= TRUE
;
362 view
->growbuf_blockptr
= NULL
;
363 view
->growbuf_blocks
= 0;
364 view
->growbuf_lastindex
= VIEW_PAGE_SIZE
;
365 view
->growbuf_finished
= FALSE
;
369 view_growbuf_free (WView
*view
)
373 assert (view
->growbuf_in_use
);
375 for (i
= 0; i
< view
->growbuf_blocks
; i
++)
376 g_free (view
->growbuf_blockptr
[i
]);
377 g_free (view
->growbuf_blockptr
);
378 view
->growbuf_blockptr
= NULL
;
379 view
->growbuf_in_use
= FALSE
;
383 view_growbuf_filesize (WView
*view
)
385 assert(view
->growbuf_in_use
);
387 if (view
->growbuf_blocks
== 0)
390 return ((offset_type
) view
->growbuf_blocks
- 1) * VIEW_PAGE_SIZE
391 + view
->growbuf_lastindex
;
394 /* Copies the output from the pipe to the growing buffer, until either
395 * the end-of-pipe is reached or the interval [0..ofs) of the growing
396 * buffer is completely filled. */
398 view_growbuf_read_until (WView
*view
, offset_type ofs
)
405 assert (view
->growbuf_in_use
);
407 if (view
->growbuf_finished
)
411 while (view_growbuf_filesize (view
) < ofs
|| short_read
) {
412 if (view
->growbuf_lastindex
== VIEW_PAGE_SIZE
) {
413 /* Append a new block to the growing buffer */
414 byte
*newblock
= g_try_malloc (VIEW_PAGE_SIZE
);
415 byte
**newblocks
= g_try_malloc (sizeof (*newblocks
) * (view
->growbuf_blocks
+ 1));
416 if (!newblock
|| !newblocks
) {
421 memcpy (newblocks
, view
->growbuf_blockptr
, sizeof (*newblocks
) * view
->growbuf_blocks
);
422 g_free (view
->growbuf_blockptr
);
423 view
->growbuf_blockptr
= newblocks
;
424 view
->growbuf_blockptr
[view
->growbuf_blocks
++] = newblock
;
425 view
->growbuf_lastindex
= 0;
427 p
= view
->growbuf_blockptr
[view
->growbuf_blocks
- 1] + view
->growbuf_lastindex
;
428 bytesfree
= VIEW_PAGE_SIZE
- view
->growbuf_lastindex
;
430 if (view
->datasource
== DS_STDIO_PIPE
) {
431 nread
= fread (p
, 1, bytesfree
, view
->ds_stdio_pipe
);
433 view
->growbuf_finished
= TRUE
;
434 (void) pclose (view
->ds_stdio_pipe
);
436 close_error_pipe (D_NORMAL
, NULL
);
437 view
->ds_stdio_pipe
= NULL
;
441 assert (view
->datasource
== DS_VFS_PIPE
);
443 nread
= mc_read (view
->ds_vfs_pipe
, p
, bytesfree
);
444 } while (nread
== -1 && errno
== EINTR
);
445 if (nread
== -1 || nread
== 0) {
446 view
->growbuf_finished
= TRUE
;
447 (void) mc_close (view
->ds_vfs_pipe
);
448 view
->ds_vfs_pipe
= -1;
452 short_read
= ((size_t)nread
< bytesfree
);
453 view
->growbuf_lastindex
+= nread
;
458 get_byte_growing_buffer (WView
*view
, offset_type byte_index
)
460 offset_type pageno
= byte_index
/ VIEW_PAGE_SIZE
;
461 offset_type pageindex
= byte_index
% VIEW_PAGE_SIZE
;
463 assert (view
->growbuf_in_use
);
465 if ((size_t) pageno
!= pageno
)
468 view_growbuf_read_until (view
, byte_index
+ 1);
469 if (view
->growbuf_blocks
== 0)
471 if (pageno
< view
->growbuf_blocks
- 1)
472 return view
->growbuf_blockptr
[pageno
][pageindex
];
473 if (pageno
== view
->growbuf_blocks
- 1 && pageindex
< view
->growbuf_lastindex
)
474 return view
->growbuf_blockptr
[pageno
][pageindex
];
478 /* {{{ Data sources }}} */
481 The data source provides the viewer with data from either a file, a
482 string or the output of a command. The get_byte() function can be
483 used to get the value of a byte at a specific offset. If the offset
484 is out of range, -1 is returned. The function get_byte_indexed(a,b)
485 returns the byte at the offset a+b, or -1 if a+b is out of range.
487 The view_set_byte() function has the effect that later calls to
488 get_byte() will return the specified byte for this offset. This
489 function is designed only for use by the hexedit component after
490 saving its changes. Inspect the source before you want to use it for
493 The view_get_filesize() function returns the current size of the
494 data source. If the growing buffer is used, this size may increase
495 later on. Use the view_may_still_grow() function when you want to
496 know if the size can change later.
500 view_get_filesize (WView
*view
)
502 switch (view
->datasource
) {
507 return view_growbuf_filesize (view
);
509 return view
->ds_file_filesize
;
511 return view
->ds_string_len
;
513 assert(!"Unknown datasource type");
519 view_may_still_grow (WView
*view
)
521 return (view
->growbuf_in_use
&& !view
->growbuf_finished
);
524 /* returns TRUE if the idx lies in the half-open interval
525 * [offset; offset + size), FALSE otherwise.
528 already_loaded (offset_type offset
, offset_type idx
, size_t size
)
530 return (offset
<= idx
&& idx
- offset
< size
);
534 view_file_load_data (WView
*view
, offset_type byte_index
)
536 offset_type blockoffset
;
540 assert (view
->datasource
== DS_FILE
);
542 if (already_loaded (view
->ds_file_offset
, byte_index
, view
->ds_file_datalen
))
545 if (byte_index
>= view
->ds_file_filesize
)
548 blockoffset
= offset_rounddown (byte_index
, view
->ds_file_datasize
);
549 if (mc_lseek (view
->ds_file_fd
, blockoffset
, SEEK_SET
) == -1)
553 while (bytes_read
< view
->ds_file_datasize
) {
554 res
= mc_read (view
->ds_file_fd
, view
->ds_file_data
+ bytes_read
, view
->ds_file_datasize
- bytes_read
);
559 bytes_read
+= (size_t) res
;
561 view
->ds_file_offset
= blockoffset
;
562 if (bytes_read
> view
->ds_file_filesize
- view
->ds_file_offset
) {
563 /* the file has grown in the meantime -- stick to the old size */
564 view
->ds_file_datalen
= view
->ds_file_filesize
- view
->ds_file_offset
;
566 view
->ds_file_datalen
= bytes_read
;
571 view
->ds_file_datalen
= 0;
575 get_byte_none (WView
*view
, offset_type byte_index
)
577 assert (view
->datasource
== DS_NONE
);
584 get_byte_file (WView
*view
, offset_type byte_index
)
586 assert (view
->datasource
== DS_FILE
);
588 view_file_load_data (view
, byte_index
);
589 if (already_loaded(view
->ds_file_offset
, byte_index
, view
->ds_file_datalen
))
590 return view
->ds_file_data
[byte_index
- view
->ds_file_offset
];
595 get_byte_string (WView
*view
, offset_type byte_index
)
597 assert (view
->datasource
== DS_STRING
);
598 if (byte_index
< view
->ds_string_len
)
599 return view
->ds_string_data
[byte_index
];
604 get_byte (WView
*view
, offset_type offset
)
606 switch (view
->datasource
) {
609 return get_byte_growing_buffer (view
, offset
);
611 return get_byte_file (view
, offset
);
613 return get_byte_string (view
, offset
);
615 return get_byte_none (view
, offset
);
617 assert(!"Unknown datasource type");
622 get_byte_indexed (WView
*view
, offset_type base
, offset_type ofs
)
624 if (base
<= OFFSETTYPE_MAX
- ofs
)
625 return get_byte (view
, base
+ ofs
);
630 view_set_byte (WView
*view
, offset_type offset
, byte b
)
633 assert (offset
< view_get_filesize (view
));
634 assert (view
->datasource
== DS_FILE
);
635 view
->ds_file_datalen
= 0; /* just force reloading */
639 view_set_datasource_none (WView
*view
)
641 view
->datasource
= DS_NONE
;
645 view_set_datasource_vfs_pipe (WView
*view
, int fd
)
648 view
->datasource
= DS_VFS_PIPE
;
649 view
->ds_vfs_pipe
= fd
;
651 view_init_growbuf (view
);
655 view_set_datasource_stdio_pipe (WView
*view
, FILE *fp
)
658 view
->datasource
= DS_STDIO_PIPE
;
659 view
->ds_stdio_pipe
= fp
;
661 view_init_growbuf (view
);
665 view_set_datasource_string (WView
*view
, const char *s
)
667 view
->datasource
= DS_STRING
;
668 view
->ds_string_data
= (byte
*) g_strdup (s
);
669 view
->ds_string_len
= strlen (s
);
673 view_set_datasource_file (WView
*view
, int fd
, const struct stat
*st
)
675 view
->datasource
= DS_FILE
;
676 view
->ds_file_fd
= fd
;
677 view
->ds_file_filesize
= st
->st_size
;
678 view
->ds_file_offset
= 0;
679 view
->ds_file_data
= g_malloc (4096);
680 view
->ds_file_datalen
= 0;
681 view
->ds_file_datasize
= 4096;
685 view_close_datasource (WView
*view
)
687 switch (view
->datasource
) {
691 if (view
->ds_stdio_pipe
!= NULL
) {
692 (void) pclose (view
->ds_stdio_pipe
);
694 close_error_pipe (D_NORMAL
, NULL
);
695 view
->ds_stdio_pipe
= NULL
;
697 view_growbuf_free (view
);
700 if (view
->ds_vfs_pipe
!= -1) {
701 (void) mc_close (view
->ds_vfs_pipe
);
702 view
->ds_vfs_pipe
= -1;
704 view_growbuf_free (view
);
707 (void) mc_close (view
->ds_file_fd
);
708 view
->ds_file_fd
= -1;
709 g_free (view
->ds_file_data
);
710 view
->ds_file_data
= NULL
;
713 g_free (view
->ds_string_data
);
714 view
->ds_string_data
= NULL
;
717 assert (!"Unknown datasource type");
719 view
->datasource
= DS_NONE
;
722 /* {{{ The Coordinate Cache }}} */
725 This cache provides you with a fast lookup to map file offsets into
726 line/column pairs and vice versa. The interface to the mapping is
727 provided by the functions view_coord_to_offset() and
728 view_offset_to_coord().
730 The cache is implemented as a simple sorted array holding entries
731 that map some of the offsets to their line/column pair. Entries that
732 are not cached themselves are interpolated (exactly) from their
733 neighbor entries. The algorithm used for determining the line/column
734 for a specific offset needs to be kept synchronized with the one used
744 coord_cache_entry_less (const struct coord_cache_entry
*a
,
745 const struct coord_cache_entry
*b
, enum ccache_type crit
,
748 if (crit
== CCACHE_OFFSET
)
749 return (a
->cc_offset
< b
->cc_offset
);
751 if (a
->cc_line
< b
->cc_line
)
754 if (a
->cc_line
== b
->cc_line
) {
756 return (a
->cc_nroff_column
< b
->cc_nroff_column
);
758 return (a
->cc_column
< b
->cc_column
);
764 #ifdef MC_ENABLE_DEBUGGING_CODE
765 static void view_coord_to_offset (WView
*, offset_type
*, offset_type
, offset_type
);
766 static void view_offset_to_coord (WView
*, offset_type
*, offset_type
*, offset_type
);
769 view_ccache_dump (WView
*view
)
772 offset_type offset
, line
, column
, nextline_offset
, filesize
;
774 const struct coord_cache_entry
*cache
;
776 assert (view
->coord_cache
!= NULL
);
778 filesize
= view_get_filesize (view
);
779 cache
= &(g_array_index (view
->coord_cache
, struct coord_cache_entry
, 0));
781 f
= fopen("mcview-ccache.out", "w");
784 (void)setvbuf(f
, NULL
, _IONBF
, 0);
787 for (i
= 0; i
< view
->coord_cache
->len
; i
++) {
790 "offset %8"OFFSETTYPE_PRId
" "
791 "line %8"OFFSETTYPE_PRId
" "
792 "column %8"OFFSETTYPE_PRId
" "
793 "nroff_column %8"OFFSETTYPE_PRId
"\n",
794 (unsigned int) i
, cache
[i
].cc_offset
, cache
[i
].cc_line
,
795 cache
[i
].cc_column
, cache
[i
].cc_nroff_column
);
797 (void)fprintf (f
, "\n");
799 /* offset -> line/column translation */
800 for (offset
= 0; offset
< filesize
; offset
++) {
801 view_offset_to_coord (view
, &line
, &column
, offset
);
803 "offset %8"OFFSETTYPE_PRId
" "
804 "line %8"OFFSETTYPE_PRId
" "
805 "column %8"OFFSETTYPE_PRId
"\n",
806 offset
, line
, column
);
809 /* line/column -> offset translation */
810 for (line
= 0; TRUE
; line
++) {
811 view_coord_to_offset (view
, &nextline_offset
, line
+ 1, 0);
812 (void)fprintf (f
, "nextline_offset %8"OFFSETTYPE_PRId
"\n",
815 for (column
= 0; TRUE
; column
++) {
816 view_coord_to_offset (view
, &offset
, line
, column
);
817 if (offset
>= nextline_offset
)
820 (void)fprintf (f
, "line %8"OFFSETTYPE_PRId
" column %8"OFFSETTYPE_PRId
" offset %8"OFFSETTYPE_PRId
"\n",
821 line
, column
, offset
);
824 if (nextline_offset
>= filesize
- 1)
833 is_nroff_sequence (WView
*view
, offset_type offset
)
837 /* The following commands are ordered to speed up the calculation. */
839 c1
= get_byte_indexed (view
, offset
, 1);
840 if (c1
== -1 || c1
!= '\b')
843 c0
= get_byte_indexed (view
, offset
, 0);
844 if (c0
== -1 || !is_printable(c0
))
847 c2
= get_byte_indexed (view
, offset
, 2);
848 if (c2
== -1 || !is_printable(c2
))
851 return (c0
== c2
|| c0
== '_' || (c0
== '+' && c2
== 'o'));
854 /* Find and return the index of the last cache entry that is
855 * smaller than ''coord'', according to the criterion ''sort_by''. */
857 view_ccache_find (WView
*view
, const struct coord_cache_entry
*cache
,
858 const struct coord_cache_entry
*coord
, enum ccache_type sort_by
)
860 guint base
, i
, limit
;
862 limit
= view
->coord_cache
->len
;
867 i
= base
+ limit
/ 2;
868 if (coord_cache_entry_less (coord
, &cache
[i
], sort_by
, view
->text_nroff_mode
)) {
869 /* continue the search in the lower half of the cache */
871 /* continue the search in the upper half of the cache */
874 limit
= (limit
+ 1) / 2;
879 /* Look up the missing components of ''coord'', which are given by
880 * ''lookup_what''. The function returns the smallest value that
881 * matches the existing components of ''coord''.
884 view_ccache_lookup (WView
*view
, struct coord_cache_entry
*coord
,
885 enum ccache_type lookup_what
)
888 struct coord_cache_entry
*cache
, current
, next
, entry
;
889 enum ccache_type sorter
;
897 if (!view
->coord_cache
) {
898 view
->coord_cache
= g_array_new (FALSE
, FALSE
, sizeof(struct coord_cache_entry
));
899 current
.cc_offset
= 0;
901 current
.cc_column
= 0;
902 current
.cc_nroff_column
= 0;
903 g_array_append_val (view
->coord_cache
, current
);
906 sorter
= (lookup_what
== CCACHE_OFFSET
) ? CCACHE_LINECOL
: CCACHE_OFFSET
;
909 /* find the two neighbor entries in the cache */
910 cache
= &(g_array_index (view
->coord_cache
, struct coord_cache_entry
, 0));
911 i
= view_ccache_find (view
, cache
, coord
, sorter
);
912 /* now i points to the lower neighbor in the cache */
915 if (i
+ 1 < view
->coord_cache
->len
)
916 limit
= cache
[i
+ 1].cc_offset
;
918 limit
= current
.cc_offset
+ VIEW_COORD_CACHE_GRANUL
;
921 nroff_state
= NROFF_START
;
922 for (; current
.cc_offset
< limit
; current
= next
) {
925 if ((c
= get_byte (view
, current
.cc_offset
)) == -1)
928 if (!coord_cache_entry_less (¤t
, coord
, sorter
, view
->text_nroff_mode
)) {
929 if (lookup_what
== CCACHE_OFFSET
930 && view
->text_nroff_mode
931 && nroff_state
!= NROFF_START
) {
932 /* don't break here */
938 /* Provide useful default values for ''next'' */
939 next
.cc_offset
= current
.cc_offset
+ 1;
940 next
.cc_line
= current
.cc_line
;
941 next
.cc_column
= current
.cc_column
+ 1;
942 next
.cc_nroff_column
= current
.cc_nroff_column
+ 1;
944 /* and override some of them as necessary. */
946 nextc
= get_byte_indexed(view
, current
.cc_offset
, 1);
948 /* Ignore '\r' if it is followed by '\r' or '\n'. If it is
949 * followed by anything else, it is a Mac line ending and
950 * produces a line break.
952 if (nextc
== '\r' || nextc
== '\n') {
953 next
.cc_column
= current
.cc_column
;
954 next
.cc_nroff_column
= current
.cc_nroff_column
;
956 next
.cc_line
= current
.cc_line
+ 1;
958 next
.cc_nroff_column
= 0;
961 } else if (nroff_state
== NROFF_BACKSPACE
) {
962 next
.cc_nroff_column
= current
.cc_nroff_column
- 1;
964 } else if (c
== '\t') {
965 next
.cc_column
= offset_rounddown (current
.cc_column
, 8) + 8;
966 next
.cc_nroff_column
=
967 offset_rounddown (current
.cc_nroff_column
, 8) + 8;
969 } else if (c
== '\n') {
970 next
.cc_line
= current
.cc_line
+ 1;
972 next
.cc_nroff_column
= 0;
975 /* Use all default values from above */
978 switch (nroff_state
) {
980 case NROFF_CONTINUATION
:
981 if (is_nroff_sequence (view
, current
.cc_offset
))
982 nroff_state
= NROFF_BACKSPACE
;
984 nroff_state
= NROFF_START
;
986 case NROFF_BACKSPACE
:
987 nroff_state
= NROFF_CONTINUATION
;
991 /* Cache entries must guarantee that for each i < j,
992 * line[i] <= line[j] and column[i] < column[j]. In the case of
993 * nroff sequences and '\r' characters, this is not guaranteed,
994 * so we cannot save them. */
995 if (nroff_state
== NROFF_START
&& c
!= '\r')
999 if (i
+ 1 == view
->coord_cache
->len
&& entry
.cc_offset
!= cache
[i
].cc_offset
) {
1000 g_array_append_val (view
->coord_cache
, entry
);
1004 if (lookup_what
== CCACHE_OFFSET
) {
1005 coord
->cc_offset
= current
.cc_offset
;
1007 coord
->cc_line
= current
.cc_line
;
1008 coord
->cc_column
= current
.cc_column
;
1009 coord
->cc_nroff_column
= current
.cc_nroff_column
;
1014 view_coord_to_offset (WView
*view
, offset_type
*ret_offset
,
1015 offset_type line
, offset_type column
)
1017 struct coord_cache_entry coord
;
1019 coord
.cc_line
= line
;
1020 coord
.cc_column
= column
;
1021 coord
.cc_nroff_column
= column
;
1022 view_ccache_lookup (view
, &coord
, CCACHE_OFFSET
);
1023 *ret_offset
= coord
.cc_offset
;
1027 view_offset_to_coord (WView
*view
, offset_type
*ret_line
,
1028 offset_type
*ret_column
, offset_type offset
)
1030 struct coord_cache_entry coord
;
1032 coord
.cc_offset
= offset
;
1033 view_ccache_lookup (view
, &coord
, CCACHE_LINECOL
);
1034 *ret_line
= coord
.cc_line
;
1035 *ret_column
= (view
->text_nroff_mode
)
1036 ? coord
.cc_nroff_column
1040 /* {{{ Cursor Movement }}} */
1043 The following variables have to do with the current position and are
1044 updated by the cursor movement functions.
1046 In hex view and wrapped text view mode, dpy_start marks the offset of
1047 the top-left corner on the screen, in non-wrapping text mode it is
1048 the beginning of the current line. In hex mode, hex_cursor is the
1049 offset of the cursor. In non-wrapping text mode, dpy_text_column is
1050 the number of columns that are hidden on the left side on the screen.
1052 In hex mode, dpy_start is updated by the view_fix_cursor_position()
1053 function in order to keep the other functions simple. In
1054 non-wrapping text mode dpy_start and dpy_text_column are normalized
1055 such that dpy_text_column < view_get_datacolumns().
1058 /* prototypes for functions used by view_moveto_bottom() */
1059 static void view_move_up (WView
*, offset_type
);
1060 static void view_moveto_bol (WView
*);
1063 view_scroll_to_cursor (WView
*view
)
1065 if (view
->hex_mode
) {
1066 const offset_type bytes
= view
->bytes_per_line
;
1067 const offset_type displaysize
= view
->data_area
.height
* bytes
;
1068 const offset_type cursor
= view
->hex_cursor
;
1069 offset_type topleft
= view
->dpy_start
;
1071 if (topleft
+ displaysize
<= cursor
)
1072 topleft
= offset_rounddown (cursor
, bytes
)
1073 - (displaysize
- bytes
);
1074 if (cursor
< topleft
)
1075 topleft
= offset_rounddown (cursor
, bytes
);
1076 view
->dpy_start
= topleft
;
1077 } else if (view
->text_wrap_mode
) {
1078 offset_type line
, col
, columns
;
1080 columns
= view
->data_area
.width
;
1081 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
+ view
->dpy_text_column
);
1083 col
= offset_rounddown (col
, columns
);
1084 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1085 view
->dpy_text_column
= 0;
1092 view_movement_fixups (WView
*view
, bool reset_search
)
1094 view_scroll_to_cursor (view
);
1096 view
->search_start
= view
->dpy_start
;
1097 view
->search_length
= 0;
1103 view_moveto_top (WView
*view
)
1105 view
->dpy_start
= 0;
1106 view
->hex_cursor
= 0;
1107 view
->dpy_text_column
= 0;
1108 view_movement_fixups (view
, TRUE
);
1112 view_moveto_bottom (WView
*view
)
1114 offset_type datalines
, lines_up
, filesize
, last_offset
;
1116 if (view
->growbuf_in_use
)
1117 view_growbuf_read_until (view
, OFFSETTYPE_MAX
);
1119 filesize
= view_get_filesize (view
);
1120 last_offset
= offset_doz(filesize
, 1);
1121 datalines
= view
->data_area
.height
;
1122 lines_up
= offset_doz(datalines
, 1);
1124 if (view
->hex_mode
) {
1125 view
->hex_cursor
= filesize
;
1126 view_move_up (view
, lines_up
);
1127 view
->hex_cursor
= last_offset
;
1129 view
->dpy_start
= last_offset
;
1130 view_moveto_bol (view
);
1131 view_move_up (view
, lines_up
);
1133 view_movement_fixups (view
, TRUE
);
1137 view_moveto_bol (WView
*view
)
1139 if (view
->hex_mode
) {
1140 view
->hex_cursor
-= view
->hex_cursor
% view
->bytes_per_line
;
1141 } else if (view
->text_wrap_mode
) {
1144 offset_type line
, column
;
1145 view_offset_to_coord (view
, &line
, &column
, view
->dpy_start
);
1146 view_coord_to_offset (view
, &(view
->dpy_start
), line
, 0);
1147 view
->dpy_text_column
= 0;
1149 view_movement_fixups (view
, TRUE
);
1153 view_moveto_eol (WView
*view
)
1155 if (view
->hex_mode
) {
1156 offset_type filesize
, bol
;
1158 bol
= offset_rounddown (view
->hex_cursor
, view
->bytes_per_line
);
1159 if (get_byte_indexed (view
, bol
, view
->bytes_per_line
- 1) != -1) {
1160 view
->hex_cursor
= bol
+ view
->bytes_per_line
- 1;
1162 filesize
= view_get_filesize (view
);
1163 view
->hex_cursor
= offset_doz(filesize
, 1);
1165 } else if (view
->text_wrap_mode
) {
1168 offset_type line
, col
;
1170 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1171 view_coord_to_offset (view
, &(view
->dpy_start
), line
, OFFSETTYPE_MAX
);
1173 view_movement_fixups (view
, FALSE
);
1177 view_moveto_offset (WView
*view
, offset_type offset
)
1179 if (view
->hex_mode
) {
1180 view
->hex_cursor
= offset
;
1181 view
->dpy_start
= offset
- offset
% view
->bytes_per_line
;
1183 view
->dpy_start
= offset
;
1185 view_movement_fixups (view
, TRUE
);
1189 view_moveto (WView
*view
, offset_type line
, offset_type col
)
1193 view_coord_to_offset (view
, &offset
, line
, col
);
1194 view_moveto_offset (view
, offset
);
1198 view_move_up (WView
*view
, offset_type lines
)
1200 if (view
->hex_mode
) {
1201 offset_type bytes
= lines
* view
->bytes_per_line
;
1202 if (view
->hex_cursor
>= bytes
) {
1203 view
->hex_cursor
-= bytes
;
1204 if (view
->hex_cursor
< view
->dpy_start
)
1205 view
->dpy_start
= offset_doz (view
->dpy_start
, bytes
);
1207 view
->hex_cursor
%= view
->bytes_per_line
;
1209 } else if (view
->text_wrap_mode
) {
1210 const screen_dimen width
= view
->data_area
.width
;
1211 offset_type i
, col
, line
, linestart
;
1213 for (i
= 0; i
< lines
; i
++) {
1214 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1217 } else if (line
>= 1) {
1218 view_coord_to_offset (view
, &linestart
, line
, 0);
1219 view_offset_to_coord (view
, &line
, &col
, linestart
- 1);
1221 /* if the only thing that would be displayed were a
1222 * single newline character, advance to the previous
1223 * part of the line. */
1224 if (col
> 0 && col
% width
== 0)
1231 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1234 offset_type line
, column
;
1236 view_offset_to_coord (view
, &line
, &column
, view
->dpy_start
);
1237 line
= offset_doz(line
, lines
);
1238 view_coord_to_offset (view
, &(view
->dpy_start
), line
, column
);
1240 view_movement_fixups (view
, (lines
!= 1));
1244 view_move_down (WView
*view
, offset_type lines
)
1246 if (view
->hex_mode
) {
1247 offset_type i
, limit
, last_byte
;
1249 last_byte
= view_get_filesize (view
);
1250 if (last_byte
>= (offset_type
) view
->bytes_per_line
)
1251 limit
= last_byte
- view
->bytes_per_line
;
1254 for (i
= 0; i
< lines
&& view
->hex_cursor
< limit
; i
++) {
1255 view
->hex_cursor
+= view
->bytes_per_line
;
1257 view
->dpy_start
+= view
->bytes_per_line
;
1260 } else if (view
->dpy_end
== view_get_filesize (view
)) {
1261 /* don't move further down. There's nothing more to see. */
1263 } else if (view
->text_wrap_mode
) {
1264 offset_type line
, col
, i
;
1266 for (i
= 0; i
< lines
; i
++) {
1267 offset_type new_offset
, chk_line
, chk_col
;
1269 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1270 col
+= view
->data_area
.width
;
1271 view_coord_to_offset (view
, &new_offset
, line
, col
);
1273 /* skip to the next line if the only thing that would be
1274 * displayed is the newline character. */
1275 view_offset_to_coord (view
, &chk_line
, &chk_col
, new_offset
);
1276 if (chk_line
== line
&& chk_col
== col
1277 && get_byte (view
, new_offset
) == '\n')
1280 view
->dpy_start
= new_offset
;
1284 offset_type line
, col
;
1286 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1288 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1290 view_movement_fixups (view
, (lines
!= 1));
1294 view_move_left (WView
*view
, offset_type columns
)
1296 if (view
->hex_mode
) {
1297 assert (columns
== 1);
1298 if (view
->hexview_in_text
|| !view
->hexedit_lownibble
) {
1299 if (view
->hex_cursor
> 0)
1302 if (!view
->hexview_in_text
)
1303 view
->hexedit_lownibble
= !view
->hexedit_lownibble
;
1304 } else if (view
->text_wrap_mode
) {
1307 if (view
->dpy_text_column
>= columns
)
1308 view
->dpy_text_column
-= columns
;
1310 view
->dpy_text_column
= 0;
1312 view_movement_fixups (view
, FALSE
);
1316 view_move_right (WView
*view
, offset_type columns
)
1318 if (view
->hex_mode
) {
1319 assert (columns
== 1);
1320 if (view
->hexview_in_text
|| view
->hexedit_lownibble
) {
1321 if (get_byte_indexed (view
, view
->hex_cursor
, 1) != -1)
1324 if (!view
->hexview_in_text
)
1325 view
->hexedit_lownibble
= !view
->hexedit_lownibble
;
1326 } else if (view
->text_wrap_mode
) {
1329 view
->dpy_text_column
+= columns
;
1331 view_movement_fixups (view
, FALSE
);
1334 /* {{{ Toggling of viewer modes }}} */
1337 view_toggle_hex_mode (WView
*view
)
1339 view
->hex_mode
= !view
->hex_mode
;
1341 if (view
->hex_mode
) {
1342 view
->hex_cursor
= view
->dpy_start
;
1344 offset_rounddown (view
->dpy_start
, view
->bytes_per_line
);
1345 view
->widget
.options
|= W_WANT_CURSOR
;
1347 view
->dpy_start
= view
->hex_cursor
;
1348 view_moveto_bol (view
);
1349 view
->widget
.options
&= ~W_WANT_CURSOR
;
1351 altered_hex_mode
= 1;
1352 view
->dpy_bbar_dirty
= TRUE
;
1357 view_toggle_hexedit_mode (WView
*view
)
1359 view
->hexedit_mode
= !view
->hexedit_mode
;
1360 view
->dpy_bbar_dirty
= TRUE
;
1365 view_toggle_wrap_mode (WView
*view
)
1367 view
->text_wrap_mode
= !view
->text_wrap_mode
;
1368 if (view
->text_wrap_mode
) {
1369 view_scroll_to_cursor (view
);
1373 view_offset_to_coord (view
, &line
, &(view
->dpy_text_column
), view
->dpy_start
);
1374 view_coord_to_offset (view
, &(view
->dpy_start
), line
, 0);
1376 view
->dpy_bbar_dirty
= TRUE
;
1381 view_toggle_nroff_mode (WView
*view
)
1383 view
->text_nroff_mode
= !view
->text_nroff_mode
;
1384 altered_nroff_flag
= 1;
1385 view
->dpy_bbar_dirty
= TRUE
;
1390 view_toggle_magic_mode (WView
*view
)
1392 char *filename
, *command
;
1394 altered_magic_flag
= 1;
1395 view
->magic_mode
= !view
->magic_mode
;
1396 filename
= g_strdup (view
->filename
);
1397 command
= g_strdup (view
->command
);
1400 view_load (view
, command
, filename
, 0);
1403 view
->dpy_bbar_dirty
= TRUE
;
1407 /* {{{ Miscellaneous functions }}} */
1410 view_done (WView
*view
)
1412 /* Save current file position */
1413 if (mcview_remember_file_position
&& view
->filename
!= NULL
) {
1415 offset_type line
, col
;
1417 canon_fname
= vfs_canon (view
->filename
);
1418 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1419 save_file_position (canon_fname
, line
+ 1, col
);
1420 g_free (canon_fname
);
1423 /* Write back the global viewer mode */
1424 default_hex_mode
= view
->hex_mode
;
1425 default_nroff_flag
= view
->text_nroff_mode
;
1426 default_magic_flag
= view
->magic_mode
;
1427 global_wrap_mode
= view
->text_wrap_mode
;
1429 /* Free memory used by the viewer */
1431 /* view->widget needs no destructor */
1433 g_free (view
->filename
), view
->filename
= NULL
;
1434 g_free (view
->command
), view
->command
= NULL
;
1436 view_close_datasource (view
);
1437 /* the growing buffer is freed with the datasource */
1439 if (view
->coord_cache
) {
1440 g_array_free (view
->coord_cache
, TRUE
), view
->coord_cache
= NULL
;
1443 view_hexedit_free_change_list (view
);
1444 /* FIXME: what about view->search_exp? */
1448 view_show_error (WView
*view
, const char *msg
)
1450 view_close_datasource (view
);
1451 if (view_is_in_panel (view
)) {
1452 view_set_datasource_string (view
, msg
);
1454 message (D_ERROR
, MSG_ERROR
, "%s", msg
);
1459 view_load_command_output (WView
*view
, const char *command
)
1463 view_close_datasource (view
);
1466 if ((fp
= popen (command
, "r")) == NULL
) {
1467 /* Avoid two messages. Message from stderr has priority. */
1469 if (!close_error_pipe (view_is_in_panel (view
) ? -1 : D_ERROR
, NULL
))
1470 view_show_error (view
, _(" Cannot spawn child process "));
1474 /* First, check if filter produced any output */
1475 view_set_datasource_stdio_pipe (view
, fp
);
1476 if (get_byte (view
, 0) == -1) {
1477 view_close_datasource (view
);
1479 /* Avoid two messages. Message from stderr has priority. */
1481 if (!close_error_pipe (view_is_in_panel (view
) ? -1 : D_ERROR
, NULL
))
1482 view_show_error (view
, _("Empty output from child filter"));
1489 view_load (WView
*view
, const char *command
, const char *file
,
1494 char tmp
[BUF_MEDIUM
];
1496 bool retval
= FALSE
;
1498 assert (view
->bytes_per_line
!= 0);
1501 /* Set up the state */
1502 view_set_datasource_none (view
);
1503 view
->filename
= g_strdup (file
);
1506 /* Clear the markers */
1508 for (i
= 0; i
< 10; i
++)
1511 if (!view_is_in_panel (view
)) {
1512 view
->dpy_text_column
= 0;
1515 if (command
&& (view
->magic_mode
|| file
== NULL
|| file
[0] == '\0')) {
1516 retval
= view_load_command_output (view
, command
);
1517 } else if (file
!= NULL
&& file
[0] != '\0') {
1519 if ((fd
= mc_open (file
, O_RDONLY
| O_NONBLOCK
)) == -1) {
1520 g_snprintf (tmp
, sizeof (tmp
), _(" Cannot open \"%s\"\n %s "),
1521 file
, unix_error_string (errno
));
1522 view_show_error (view
, tmp
);
1526 /* Make sure we are working with a regular file */
1527 if (mc_fstat (fd
, &st
) == -1) {
1529 g_snprintf (tmp
, sizeof (tmp
), _(" Cannot stat \"%s\"\n %s "),
1530 file
, unix_error_string (errno
));
1531 view_show_error (view
, tmp
);
1535 if (!S_ISREG (st
.st_mode
)) {
1537 view_show_error (view
, _(" Cannot view: not a regular file "));
1541 if (st
.st_size
== 0 || mc_lseek (fd
, 0, SEEK_SET
) == -1) {
1542 /* Must be one of those nice files that grow (/proc) */
1543 view_set_datasource_vfs_pipe (view
, fd
);
1545 type
= get_compression_type (fd
);
1547 if (view
->magic_mode
&& (type
!= COMPRESSION_NONE
)) {
1548 g_free (view
->filename
);
1549 view
->filename
= g_strconcat (file
, decompress_extension (type
), (char *) NULL
);
1551 view_set_datasource_file (view
, fd
, &st
);
1557 view
->command
= g_strdup (command
);
1558 view
->dpy_start
= 0;
1559 view
->search_start
= 0;
1560 view
->search_length
= 0;
1561 view
->dpy_text_column
= 0;
1562 view
->last_search
= 0; /* Start a new search */
1564 assert (view
->bytes_per_line
!= 0);
1565 if (mcview_remember_file_position
&& file
!= NULL
&& start_line
== 0) {
1569 canon_fname
= vfs_canon (file
);
1570 load_file_position (file
, &line
, &col
);
1571 g_free (canon_fname
);
1572 view_moveto (view
, offset_doz(line
, 1), col
);
1573 } else if (start_line
> 0) {
1574 view_moveto (view
, start_line
- 1, 0);
1577 view
->hexedit_lownibble
= FALSE
;
1578 view
->hexview_in_text
= FALSE
;
1579 view
->change_list
= NULL
;
1584 /* {{{ Display management }}} */
1587 view_update_bytes_per_line (WView
*view
)
1589 const screen_dimen cols
= view
->data_area
.width
;
1595 bytes
= 4 * ((cols
- 8) / ((cols
< 80) ? 17 : 18));
1598 view
->bytes_per_line
= bytes
;
1599 view
->dirty
= max_dirt_limit
+ 1; /* To force refresh */
1603 view_percent (WView
*view
, offset_type p
)
1605 const screen_dimen top
= view
->status_area
.top
;
1606 const screen_dimen right
= view
->status_area
.left
+ view
->status_area
.width
;
1607 const screen_dimen height
= view
->status_area
.height
;
1609 offset_type filesize
;
1611 if (height
< 1 || right
< 4)
1613 if (view_may_still_grow (view
))
1615 filesize
= view_get_filesize (view
);
1617 if (filesize
== 0 || view
->dpy_end
== filesize
)
1619 else if (p
> (INT_MAX
/ 100))
1620 percent
= p
/ (filesize
/ 100);
1622 percent
= p
* 100 / filesize
;
1624 widget_move (view
, top
, right
- 4);
1625 tty_printf ("%3d%%", percent
);
1629 view_display_status (WView
*view
)
1631 const screen_dimen top
= view
->status_area
.top
;
1632 const screen_dimen left
= view
->status_area
.left
;
1633 const screen_dimen width
= view
->status_area
.width
;
1634 const screen_dimen height
= view
->status_area
.height
;
1635 const char *file_label
, *file_name
;
1636 screen_dimen file_label_width
;
1642 tty_setcolor (SELECTED_COLOR
);
1643 widget_move (view
, top
, left
);
1646 file_label
= _("File: %s");
1647 file_label_width
= strlen (file_label
) - 2;
1648 file_name
= view
->filename
? view
->filename
1649 : view
->command
? view
->command
1652 if (width
< file_label_width
+ 6)
1653 addstr ((char *) name_trunc (file_name
, width
));
1655 i
= (width
> 22 ? 22 : width
) - file_label_width
;
1656 tty_printf (file_label
, name_trunc (file_name
, i
));
1658 widget_move (view
, top
, left
+ 24);
1659 /* FIXME: the format strings need to be changed when offset_type changes */
1661 tty_printf (_("Offset 0x%08lx"), (unsigned long) view
->hex_cursor
);
1663 offset_type line
, col
;
1664 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1665 tty_printf (_("Line %lu Col %lu"),
1666 (unsigned long) line
+ 1,
1667 (unsigned long) (view
->text_wrap_mode
? col
: view
->dpy_text_column
));
1671 offset_type filesize
;
1672 filesize
= view_get_filesize (view
);
1673 widget_move (view
, top
, left
+ 43);
1674 if (!view_may_still_grow (view
)) {
1675 tty_printf (_("%s bytes"), size_trunc (filesize
));
1677 tty_printf (_(">= %s bytes"), size_trunc (filesize
));
1681 view_percent (view
, view
->hex_mode
1686 tty_setcolor (SELECTED_COLOR
);
1690 view_display_clean (WView
*view
)
1692 tty_setcolor (NORMAL_COLOR
);
1693 widget_erase ((Widget
*) view
);
1694 if (view
->dpy_frame_size
!= 0) {
1695 draw_double_box (view
->widget
.parent
, view
->widget
.y
,
1696 view
->widget
.x
, view
->widget
.lines
,
1709 view_count_backspaces (WView
*view
, off_t offset
)
1712 while (offset
>= 2 * backspaces
1713 && get_byte (view
, offset
- 2 * backspaces
) == '\b')
1719 view_display_ruler (WView
*view
)
1721 static const char ruler_chars
[] = "|----*----";
1722 const screen_dimen top
= view
->ruler_area
.top
;
1723 const screen_dimen left
= view
->ruler_area
.left
;
1724 const screen_dimen width
= view
->ruler_area
.width
;
1725 const screen_dimen height
= view
->ruler_area
.height
;
1726 const screen_dimen line_row
= (ruler
== RULER_TOP
) ? 0 : 1;
1727 const screen_dimen nums_row
= (ruler
== RULER_TOP
) ? 1 : 0;
1733 if (ruler
== RULER_NONE
|| height
< 1)
1736 tty_setcolor (MARKED_COLOR
);
1737 for (c
= 0; c
< width
; c
++) {
1738 cl
= view
->dpy_text_column
+ c
;
1739 if (line_row
< height
) {
1740 widget_move (view
, top
+ line_row
, left
+ c
);
1741 tty_print_char (ruler_chars
[cl
% 10]);
1744 if ((cl
!= 0) && (cl
% 10) == 0) {
1745 g_snprintf (r_buff
, sizeof (r_buff
), "%"OFFSETTYPE_PRId
, cl
);
1746 if (nums_row
< height
) {
1747 widget_move (view
, top
+ nums_row
, left
+ c
- 1);
1748 tty_print_string (r_buff
);
1752 attrset (NORMAL_COLOR
);
1756 view_display_hex (WView
*view
)
1758 const screen_dimen top
= view
->data_area
.top
;
1759 const screen_dimen left
= view
->data_area
.left
;
1760 const screen_dimen height
= view
->data_area
.height
;
1761 const screen_dimen width
= view
->data_area
.width
;
1762 const int ngroups
= view
->bytes_per_line
/ 4;
1763 const screen_dimen text_start
=
1764 8 + 13 * ngroups
+ ((width
< 80) ? 0 : (ngroups
- 1 + 1));
1765 /* 8 characters are used for the file offset, and every hex group
1766 * takes 13 characters. On ``big'' screens, the groups are separated
1767 * by an extra vertical line, and there is an extra space before the
1771 screen_dimen row
, col
;
1774 mark_t boldflag
= MARK_NORMAL
;
1775 struct hexedit_change_node
*curr
= view
->change_list
;
1778 char hex_buff
[10]; /* A temporary buffer for sprintf and mvwaddstr */
1779 int bytes
; /* Number of bytes already printed on the line */
1781 view_display_clean (view
);
1783 /* Find the first displayable changed byte */
1784 from
= view
->dpy_start
;
1785 while (curr
&& (curr
->offset
< from
)) {
1789 for (row
= 0; get_byte (view
, from
) != -1 && row
< height
; row
++) {
1792 /* Print the hex offset */
1793 g_snprintf (hex_buff
, sizeof (hex_buff
), "%08"OFFSETTYPE_PRIX
" ", from
);
1794 widget_move (view
, top
+ row
, left
);
1795 tty_setcolor (MARKED_COLOR
);
1796 for (i
= 0; col
< width
&& hex_buff
[i
] != '\0'; i
++) {
1797 tty_print_char(hex_buff
[i
]);
1800 tty_setcolor (NORMAL_COLOR
);
1802 for (bytes
= 0; bytes
< view
->bytes_per_line
; bytes
++, from
++) {
1804 if ((c
= get_byte (view
, from
)) == -1)
1807 /* Save the cursor position for view_place_cursor() */
1808 if (from
== view
->hex_cursor
&& !view
->hexview_in_text
) {
1809 view
->cursor_row
= row
;
1810 view
->cursor_col
= col
;
1813 /* Determine the state of the current byte */
1815 (from
== view
->hex_cursor
) ? MARK_CURSOR
1816 : (curr
!= NULL
&& from
== curr
->offset
) ? MARK_CHANGED
1817 : (view
->search_start
<= from
&&
1818 from
< view
->search_start
+ view
->search_length
1822 /* Determine the value of the current byte */
1823 if (curr
!= NULL
&& from
== curr
->offset
) {
1828 /* Select the color for the hex number */
1830 boldflag
== MARK_NORMAL
? NORMAL_COLOR
:
1831 boldflag
== MARK_SELECTED
? MARKED_COLOR
:
1832 boldflag
== MARK_CHANGED
? VIEW_UNDERLINED_COLOR
:
1833 /* boldflag == MARK_CURSOR */
1834 view
->hexview_in_text
? MARKED_SELECTED_COLOR
:
1835 VIEW_UNDERLINED_COLOR
);
1837 /* Print the hex number */
1838 widget_move (view
, top
+ row
, left
+ col
);
1840 tty_print_char (hex_char
[c
/ 16]);
1844 tty_print_char (hex_char
[c
% 16]);
1848 /* Print the separator */
1849 tty_setcolor (NORMAL_COLOR
);
1850 if (bytes
!= view
->bytes_per_line
- 1) {
1852 tty_print_char (' ');
1856 /* After every four bytes, print a group separator */
1857 if (bytes
% 4 == 3) {
1858 if (view
->data_area
.width
>= 80 && col
< width
) {
1859 tty_print_one_vline ();
1863 tty_print_char (' ');
1869 /* Select the color for the character; this differs from the
1870 * hex color when boldflag == MARK_CURSOR */
1872 boldflag
== MARK_NORMAL
? NORMAL_COLOR
:
1873 boldflag
== MARK_SELECTED
? MARKED_COLOR
:
1874 boldflag
== MARK_CHANGED
? VIEW_UNDERLINED_COLOR
:
1875 /* boldflag == MARK_CURSOR */
1876 view
->hexview_in_text
? VIEW_UNDERLINED_COLOR
:
1877 MARKED_SELECTED_COLOR
);
1879 c
= convert_to_display_c (c
);
1880 if (!is_printable (c
))
1883 /* Print corresponding character on the text side */
1884 if (text_start
+ bytes
< width
) {
1885 widget_move (view
, top
+ row
, left
+ text_start
+ bytes
);
1889 /* Save the cursor position for view_place_cursor() */
1890 if (from
== view
->hex_cursor
&& view
->hexview_in_text
) {
1891 view
->cursor_row
= row
;
1892 view
->cursor_col
= text_start
+ bytes
;
1897 /* Be polite to the other functions */
1898 tty_setcolor (NORMAL_COLOR
);
1900 view_place_cursor (view
);
1901 view
->dpy_end
= from
;
1905 view_display_text (WView
* view
)
1907 const screen_dimen left
= view
->data_area
.left
;
1908 const screen_dimen top
= view
->data_area
.top
;
1909 const screen_dimen width
= view
->data_area
.width
;
1910 const screen_dimen height
= view
->data_area
.height
;
1911 screen_dimen row
, col
;
1914 struct hexedit_change_node
*curr
= view
->change_list
;
1916 view_display_clean (view
);
1917 view_display_ruler (view
);
1919 /* Find the first displayable changed byte */
1920 from
= view
->dpy_start
;
1921 while (curr
&& (curr
->offset
< from
)) {
1925 tty_setcolor (NORMAL_COLOR
);
1926 for (row
= 0, col
= 0; row
< height
&& (c
= get_byte (view
, from
)) != -1; from
++) {
1928 if (view
->text_nroff_mode
&& c
== '\b') {
1932 if ((c_next
= get_byte_indexed (view
, from
, 1)) != -1
1933 && is_printable (c_next
)
1935 && (c_prev
= get_byte (view
, from
- 1)) != -1
1936 && is_printable (c_prev
)
1937 && (c_prev
== c_next
|| c_prev
== '_'
1938 || (c_prev
== '+' && c_next
== 'o'))) {
1941 /* We're inside an nroff character sequence at the
1942 * beginning of the screen -- just skip the
1943 * backspace and continue with the next character. */
1950 if (c_prev
== '_' && (c_next
!= '_' || view_count_backspaces (view
, from
) == 1))
1951 tty_setcolor (VIEW_UNDERLINED_COLOR
);
1953 tty_setcolor (MARKED_COLOR
);
1958 if ((c
== '\n') || (col
>= width
&& view
->text_wrap_mode
)) {
1961 if (c
== '\n' || row
>= height
)
1966 c
= get_byte_indexed(view
, from
, 1);
1967 if (c
== '\r' || c
== '\n')
1975 offset_type line
, column
;
1976 view_offset_to_coord (view
, &line
, &column
, from
);
1977 col
+= (8 - column
% 8);
1978 if (view
->text_wrap_mode
&& col
>= width
&& width
!= 0) {
1985 if (view
->search_start
<= from
1986 && from
< view
->search_start
+ view
->search_length
) {
1987 tty_setcolor (SELECTED_COLOR
);
1990 if (col
>= view
->dpy_text_column
1991 && col
- view
->dpy_text_column
< width
) {
1992 widget_move (view
, top
+ row
, left
+ (col
- view
->dpy_text_column
));
1993 c
= convert_to_display_c (c
);
1994 if (!is_printable (c
))
1999 tty_setcolor (NORMAL_COLOR
);
2001 view
->dpy_end
= from
;
2004 /* Displays as much data from view->dpy_start as fits on the screen */
2006 display (WView
*view
)
2008 view_compute_areas (view
);
2009 if (view
->hex_mode
) {
2010 view_display_hex (view
);
2012 view_display_text (view
);
2014 view_display_status (view
);
2018 view_place_cursor (WView
*view
)
2020 const screen_dimen top
= view
->data_area
.top
;
2021 const screen_dimen left
= view
->data_area
.left
;
2024 col
= view
->cursor_col
;
2025 if (!view
->hexview_in_text
&& view
->hexedit_lownibble
)
2027 widget_move (&view
->widget
, top
+ view
->cursor_row
, left
+ col
);
2031 view_update (WView
*view
)
2033 static int dirt_limit
= 1;
2035 if (view
->dpy_bbar_dirty
) {
2036 view
->dpy_bbar_dirty
= FALSE
;
2038 buttonbar_redraw (view
->widget
.parent
);
2041 if (view
->dirty
> dirt_limit
) {
2042 /* Too many updates skipped -> force a update */
2045 /* Raise the update skipping limit */
2047 if (dirt_limit
> max_dirt_limit
)
2048 dirt_limit
= max_dirt_limit
;
2052 /* We have time to update the screen properly */
2058 /* We are busy -> skipping full update,
2059 only the status line is updated */
2060 view_display_status (view
);
2062 /* Here we had a refresh, if fast scrolling does not work
2063 restore the refresh, although this should not happen */
2067 /* {{{ Hex editor }}} */
2070 enqueue_change (struct hexedit_change_node
**head
,
2071 struct hexedit_change_node
*node
)
2073 /* chnode always either points to the head of the list or
2074 * to one of the ->next fields in the list. The value at
2075 * this location will be overwritten with the new node. */
2076 struct hexedit_change_node
**chnode
= head
;
2078 while (*chnode
!= NULL
&& (*chnode
)->offset
< node
->offset
)
2079 chnode
= &((*chnode
)->next
);
2081 node
->next
= *chnode
;
2086 view_handle_editkey (WView
*view
, int key
)
2088 struct hexedit_change_node
*node
;
2091 /* Has there been a change at this position? */
2092 node
= view
->change_list
;
2093 while (node
&& (node
->offset
!= view
->hex_cursor
))
2096 if (!view
->hexview_in_text
) {
2098 unsigned int hexvalue
= 0;
2100 if (key
>= '0' && key
<= '9')
2101 hexvalue
= 0 + (key
- '0');
2102 else if (key
>= 'A' && key
<= 'F')
2103 hexvalue
= 10 + (key
- 'A');
2104 else if (key
>= 'a' && key
<= 'f')
2105 hexvalue
= 10 + (key
- 'a');
2107 return MSG_NOT_HANDLED
;
2110 byte_val
= node
->value
;
2112 byte_val
= get_byte (view
, view
->hex_cursor
);
2114 if (view
->hexedit_lownibble
) {
2115 byte_val
= (byte_val
& 0xf0) | (hexvalue
);
2117 byte_val
= (byte_val
& 0x0f) | (hexvalue
<< 4);
2121 if (key
< 256 && (is_printable (key
) || (key
== '\n')))
2124 return MSG_NOT_HANDLED
;
2127 node
= g_new (struct hexedit_change_node
, 1);
2128 node
->offset
= view
->hex_cursor
;
2129 node
->value
= byte_val
;
2130 enqueue_change (&view
->change_list
, node
);
2132 node
->value
= byte_val
;
2136 view_move_right (view
, 1);
2141 view_hexedit_save_changes (WView
*view
)
2143 struct hexedit_change_node
*curr
, *next
;
2147 if (view
->change_list
== NULL
)
2151 assert (view
->filename
!= NULL
);
2152 fp
= mc_open (view
->filename
, O_WRONLY
);
2156 for (curr
= view
->change_list
; curr
!= NULL
; curr
= next
) {
2159 if (mc_lseek (fp
, curr
->offset
, SEEK_SET
) == -1
2160 || mc_write (fp
, &(curr
->value
), 1) != 1)
2163 /* delete the saved item from the change list */
2164 view
->change_list
= next
;
2166 view_set_byte (view
, curr
->offset
, curr
->value
);
2170 if (mc_close (fp
) == -1) {
2171 error
= g_strdup (strerror (errno
));
2172 message (D_ERROR
, _(" Save file "),
2173 _(" Error while closing the file: \n %s \n"
2174 " Data may have been written or not. "), error
);
2181 error
= g_strdup (strerror (errno
));
2182 text
= g_strdup_printf (_(" Cannot save file: \n %s "), error
);
2184 (void) mc_close (fp
);
2186 answer
= query_dialog (_(" Save file "), text
, D_ERROR
,
2187 2, _("&Retry"), _("&Cancel"));
2195 /* {{{ Miscellaneous functions }}} */
2198 view_ok_to_quit (WView
*view
)
2202 if (view
->change_list
== NULL
)
2205 r
= query_dialog (_("Quit"),
2206 _(" File was modified, Save with exit? "), D_NORMAL
, 3,
2207 _("&Cancel quit"), _("&Yes"), _("&No"));
2211 return view_hexedit_save_changes (view
);
2213 view_hexedit_free_change_list (view
);
2221 my_define (Dlg_head
*h
, int idx
, const char *text
, void (*fn
) (WView
*),
2224 buttonbar_set_label_data (h
, idx
, text
, (buttonbarfn
) fn
, view
);
2227 /* {{{ Searching }}} */
2229 /* Case insensitive search of text in data */
2231 icase_search_p (WView
*view
, char *text
, char *data
, int nothing
)
2235 const int direction
= view
->direction
;
2239 /* If we are searching backwards, reverse the string */
2240 if (direction
== -1) {
2241 g_strreverse (text
);
2242 g_strreverse (data
);
2245 q
= _icase_search (text
, data
, &lng
);
2247 if (direction
== -1) {
2248 g_strreverse (text
);
2249 g_strreverse (data
);
2254 view
->search_start
= q
- data
- lng
;
2256 view
->search_start
= strlen (data
) - (q
- data
);
2257 view
->search_length
= lng
;
2264 grow_string_buffer (char *text
, gulong
*size
)
2268 /* The grow steps */
2270 new = g_realloc (text
, *size
);
2278 get_line_at (WView
*view
, offset_type
*p
, offset_type
*skipped
)
2280 char *buffer
= NULL
;
2281 gulong buffer_size
= 0;
2282 offset_type usable_size
= 0;
2284 const int direction
= view
->direction
;
2285 offset_type pos
= *p
;
2291 if (pos
== 0 && direction
== -1)
2294 /* skip over all the possible zeros in the file */
2295 while ((ch
= get_byte (view
, pos
)) == 0) {
2296 if (pos
== 0 && direction
== -1)
2303 if (i
== 0 && (pos
!= 0 || direction
== -1)) {
2304 prev
= get_byte (view
, pos
- direction
);
2305 if ((prev
== -1) || (prev
== '\n'))
2309 for (i
= 1; ch
!= -1; ch
= get_byte (view
, pos
)) {
2310 if (i
>= usable_size
) {
2311 buffer
= grow_string_buffer (buffer
, &buffer_size
);
2312 usable_size
= buffer_size
- 2; /* prev & null terminator */
2317 if (pos
== 0 && direction
== -1)
2322 if (ch
== '\n' || ch
== '\0') {
2323 i
--; /* Strip newline/zero */
2332 /* If we are searching backwards, reverse the string */
2333 if (direction
== -1) {
2334 g_strreverse (buffer
+ 1);
2343 search_update_steps (WView
*view
)
2345 offset_type filesize
= view_get_filesize (view
);
2347 view
->update_steps
= 40000;
2348 else /* viewing a data stream, not a file */
2349 view
->update_steps
= filesize
/ 100;
2351 /* Do not update the percent display but every 20 ks */
2352 if (view
->update_steps
< 20000)
2353 view
->update_steps
= 20000;
2357 search (WView
*view
, char *text
,
2358 int (*search
) (WView
*, char *, char *, int))
2360 char *s
= NULL
; /* The line we read from the view buffer */
2361 offset_type p
, beginning
, search_start
;
2366 /* Used to keep track of where the line starts, when looking forward
2367 * is the index before transfering the line; the reverse case uses
2368 * the position returned after the line has been read */
2369 offset_type forward_line_start
;
2370 offset_type reverse_line_start
;
2374 d
= create_message (D_NORMAL
, _("Search"), _("Searching %s"), text
);
2378 found_len
= view
->search_length
;
2379 search_start
= view
->search_start
;
2381 if (view
->direction
== 1) {
2382 p
= search_start
+ ((found_len
) ? 1 : 0);
2384 p
= search_start
- ((found_len
&& search_start
>= 1) ? 1 : 0);
2388 /* Compute the percent steps */
2389 search_update_steps (view
);
2390 view
->update_activate
= 0;
2392 enable_interrupt_key ();
2393 for (;; g_free (s
)) {
2394 if (p
>= view
->update_activate
) {
2395 view
->update_activate
+= view
->update_steps
;
2397 view_percent (view
, p
);
2400 if (got_interrupt ())
2403 forward_line_start
= p
;
2404 s
= get_line_at (view
, &p
, &t
);
2405 reverse_line_start
= p
;
2410 search_status
= (*search
) (view
, text
, s
+ 1, match_normal
);
2411 if (search_status
< 0) {
2416 if (search_status
== 0)
2419 /* We found the string */
2421 /* Handle ^ and $ when regexp search starts at the middle of the line */
2422 if (*s
&& !view
->search_start
&& (search
== regexp_view_search
)) {
2423 if ((*text
== '^' && view
->direction
== 1)
2424 || (view
->direction
== -1 && text
[strlen (text
) - 1] == '$')
2429 /* Record the position used to continue the search */
2430 if (view
->direction
== 1)
2431 t
+= forward_line_start
;
2433 t
= reverse_line_start
? reverse_line_start
+ 2 : 0;
2434 view
->search_start
+= t
;
2436 if (t
!= beginning
) {
2437 view
->dpy_start
= t
;
2443 disable_interrupt_key ();
2449 message (D_NORMAL
, _("Search"), _(" Search string not found "));
2450 view
->search_length
= 0;
2454 /* Search buffer (its size is len) in the complete buffer
2455 * returns the position where the block was found or INVALID_OFFSET
2458 block_search (WView
*view
, const char *buffer
, int len
)
2460 int direction
= view
->direction
;
2461 const char *d
= buffer
;
2465 enable_interrupt_key ();
2467 e
= view
->search_start
+ ((view
->search_length
) ? 1 : 0);
2469 e
= view
->search_start
2470 - ((view
->search_length
&& view
->search_start
>= 1) ? 1 : 0);
2472 search_update_steps (view
);
2473 view
->update_activate
= 0;
2475 if (direction
== -1) {
2476 for (d
+= len
- 1;; e
--) {
2477 if (e
<= view
->update_activate
) {
2478 view
->update_activate
-= view
->update_steps
;
2480 view_percent (view
, e
);
2483 if (got_interrupt ())
2486 b
= get_byte (view
, e
);
2490 disable_interrupt_key ();
2495 e
+= buffer
+ len
- 1 - d
;
2496 d
= buffer
+ len
- 1;
2502 while (get_byte (view
, e
) != -1) {
2503 if (e
>= view
->update_activate
) {
2504 view
->update_activate
+= view
->update_steps
;
2506 view_percent (view
, e
);
2509 if (got_interrupt ())
2512 b
= get_byte (view
, e
++);
2516 if (d
- buffer
== len
) {
2517 disable_interrupt_key ();
2526 disable_interrupt_key ();
2527 return INVALID_OFFSET
;
2531 * Search in the hex mode. Supported input:
2532 * - numbers (oct, dec, hex). Each of them matches one byte.
2533 * - strings in double quotes. Matches exactly without quotes.
2536 hex_search (WView
*view
, const char *text
)
2538 char *buffer
; /* Parsed search string */
2539 char *cur
; /* Current position in it */
2540 int block_len
; /* Length of the search string */
2541 offset_type pos
; /* Position of the string in the file */
2542 int parse_error
= 0;
2545 view
->search_length
= 0;
2549 /* buffer will never be longer that text */
2550 buffer
= g_new (char, strlen (text
));
2553 /* First convert the string to a stream of bytes */
2558 /* Skip leading spaces */
2559 if (*text
== ' ' || *text
== '\t') {
2564 /* %i matches octal, decimal, and hexadecimal numbers */
2565 if (sscanf (text
, "%i%n", &val
, &ptr
) > 0) {
2566 /* Allow signed and unsigned char in the user input */
2567 if (val
< -128 || val
> 255) {
2572 *cur
++ = (char) val
;
2577 /* Try quoted string, strip quotes */
2579 const char *next_quote
;
2582 next_quote
= strchr (text
, '"');
2584 memcpy (cur
, text
, next_quote
- text
);
2585 cur
+= next_quote
- text
;
2586 text
= next_quote
+ 1;
2596 block_len
= cur
- buffer
;
2598 /* No valid bytes in the user input */
2599 if (block_len
<= 0 || parse_error
) {
2600 message (D_NORMAL
, _("Search"), _("Invalid hex search expression"));
2602 view
->search_length
= 0;
2606 /* Then start the search */
2607 pos
= block_search (view
, buffer
, block_len
);
2611 if (pos
== INVALID_OFFSET
) {
2612 message (D_NORMAL
, _("Search"), _(" Search string not found "));
2613 view
->search_length
= 0;
2617 view
->search_start
= pos
;
2618 view
->search_length
= block_len
;
2619 /* Set the edit cursor to the search position, left nibble */
2620 view
->hex_cursor
= view
->search_start
;
2621 view
->hexedit_lownibble
= FALSE
;
2623 /* Adjust the file offset */
2624 view
->dpy_start
= pos
- pos
% view
->bytes_per_line
;
2628 regexp_view_search (WView
*view
, char *pattern
, char *string
,
2632 static char *old_pattern
= NULL
;
2633 static int old_type
;
2634 regmatch_t pmatch
[1];
2635 int i
, flags
= REG_ICASE
;
2637 if (old_pattern
== NULL
|| strcmp (old_pattern
, pattern
) != 0
2638 || old_type
!= match_type
) {
2639 if (old_pattern
!= NULL
) {
2641 g_free (old_pattern
);
2644 for (i
= 0; pattern
[i
] != '\0'; i
++) {
2645 if (isupper ((unsigned char) pattern
[i
])) {
2650 flags
|= REG_EXTENDED
;
2651 if (regcomp (&r
, pattern
, flags
)) {
2652 message (D_ERROR
, MSG_ERROR
, _(" Invalid regular expression "));
2655 old_pattern
= g_strdup (pattern
);
2656 old_type
= match_type
;
2658 if (regexec (&r
, string
, 1, pmatch
, 0) != 0)
2660 view
->search_length
= pmatch
[0].rm_eo
- pmatch
[0].rm_so
;
2661 view
->search_start
= pmatch
[0].rm_so
;
2666 do_regexp_search (WView
*view
)
2668 search (view
, view
->search_exp
, regexp_view_search
);
2669 /* Had a refresh here */
2675 do_normal_search (WView
*view
)
2678 hex_search (view
, view
->search_exp
);
2680 search (view
, view
->search_exp
, icase_search_p
);
2681 /* Had a refresh here */
2686 /* {{{ User-definable commands }}} */
2689 The functions in this section can be bound to hotkeys. They are all
2690 of the same type (taking a pointer to WView as parameter and
2691 returning void). TODO: In the not-too-distant future, these commands
2692 will become fully configurable, like they already are in the
2693 internal editor. By convention, all the function names end in
2698 view_help_cmd (void)
2700 interactive_display (NULL
, "[Internal File Viewer]");
2703 /* Toggle between hexview and hexedit mode */
2705 view_toggle_hexedit_mode_cmd (WView
*view
)
2707 view_toggle_hexedit_mode (view
);
2711 /* Toggle between wrapped and unwrapped view */
2713 view_toggle_wrap_mode_cmd (WView
*view
)
2715 view_toggle_wrap_mode (view
);
2719 /* Toggle between hex view and text view */
2721 view_toggle_hex_mode_cmd (WView
*view
)
2723 view_toggle_hex_mode (view
);
2728 view_moveto_line_cmd (WView
*view
)
2730 char *answer
, *answer_end
, prompt
[BUF_SMALL
];
2731 offset_type line
, col
;
2733 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
2735 g_snprintf (prompt
, sizeof (prompt
),
2736 _(" The current line number is %d.\n"
2737 " Enter the new line number:"), (int) (line
+ 1));
2738 answer
= input_dialog (_(" Goto line "), prompt
, MC_HISTORY_VIEW_GOTO_LINE
, "");
2739 if (answer
!= NULL
&& answer
[0] != '\0') {
2741 line
= strtoul (answer
, &answer_end
, 10);
2742 if (*answer_end
== '\0' && errno
== 0 && line
>= 1)
2743 view_moveto (view
, line
- 1, 0);
2751 view_moveto_addr_cmd (WView
*view
)
2753 char *line
, *error
, prompt
[BUF_SMALL
];
2756 g_snprintf (prompt
, sizeof (prompt
),
2757 _(" The current address is 0x%lx.\n"
2758 " Enter the new address:"), view
->hex_cursor
);
2759 line
= input_dialog (_(" Goto Address "), prompt
, MC_HISTORY_VIEW_GOTO_ADDR
, "");
2761 if (*line
!= '\0') {
2762 addr
= strtoul (line
, &error
, 0);
2763 if ((*error
== '\0') && get_byte (view
, addr
) != -1) {
2764 view_moveto_offset (view
, addr
);
2766 message (D_ERROR
, _("Warning"), _(" Invalid address "));
2776 view_hexedit_save_changes_cmd (WView
*view
)
2778 (void) view_hexedit_save_changes (view
);
2781 /* {{{ Searching }}} */
2784 regexp_search (WView
*view
, int direction
)
2788 static char *last_regexp
;
2790 defval
= (last_regexp
!= NULL
? last_regexp
: "");
2792 regexp
= input_dialog (_("Search"), _(" Enter regexp:"), MC_HISTORY_VIEW_SEARCH_REGEX
, defval
);
2793 if (regexp
== NULL
|| regexp
[0] == '\0') {
2798 g_free (last_regexp
);
2799 view
->search_exp
= last_regexp
= regexp
;
2801 view
->direction
= direction
;
2802 do_regexp_search (view
);
2803 view
->last_search
= do_regexp_search
;
2806 /* {{{ User-definable commands }}} */
2809 view_regexp_search_cmd (WView
*view
)
2811 regexp_search (view
, 1);
2816 view_normal_search_cmd (WView
*view
)
2818 char *defval
, *exp
= NULL
;
2819 static char *last_search_string
;
2822 SEARCH_DLG_HEIGHT
= 8,
2823 SEARCH_DLG_WIDTH
= 58
2826 static int replace_backwards
;
2827 int treplace_backwards
= replace_backwards
;
2829 static QuickWidget quick_widgets
[] = {
2830 {quick_button
, 6, 10, 5, SEARCH_DLG_HEIGHT
, N_("&Cancel"), 0,
2833 {quick_button
, 2, 10, 5, SEARCH_DLG_HEIGHT
, N_("&OK"), 0, B_ENTER
,
2835 {quick_checkbox
, 3, SEARCH_DLG_WIDTH
, 4, SEARCH_DLG_HEIGHT
,
2836 N_("&Backwards"), 0, 0,
2838 {quick_input
, 3, SEARCH_DLG_WIDTH
, 3, SEARCH_DLG_HEIGHT
, "", 52, 0,
2839 0, 0, N_("Search")},
2840 {quick_label
, 2, SEARCH_DLG_WIDTH
, 2, SEARCH_DLG_HEIGHT
,
2841 N_(" Enter search string:"), 0, 0,
2845 static QuickDialog Quick_input
= {
2846 SEARCH_DLG_WIDTH
, SEARCH_DLG_HEIGHT
, -1, 0, N_("Search"),
2847 "[Input Line Keys]", quick_widgets
, 0
2850 defval
= g_strdup (last_search_string
!= NULL
? last_search_string
: "");
2851 convert_to_display (defval
);
2853 quick_widgets
[2].result
= &treplace_backwards
;
2854 quick_widgets
[3].str_result
= &exp
;
2855 quick_widgets
[3].text
= defval
;
2857 if (quick_dialog (&Quick_input
) == B_CANCEL
)
2860 replace_backwards
= treplace_backwards
;
2862 if (exp
== NULL
|| exp
[0] == '\0')
2865 convert_from_input (exp
);
2867 g_free (last_search_string
);
2868 view
->search_exp
= last_search_string
= exp
;
2871 view
->direction
= replace_backwards
? -1 : 1;
2872 do_normal_search (view
);
2873 view
->last_search
= do_normal_search
;
2881 view_toggle_magic_mode_cmd (WView
*view
)
2883 view_toggle_magic_mode (view
);
2888 view_toggle_nroff_mode_cmd (WView
*view
)
2890 view_toggle_nroff_mode (view
);
2895 view_quit_cmd (WView
*view
)
2897 if (view_ok_to_quit (view
))
2898 dlg_stop (view
->widget
.parent
);
2901 /* {{{ Miscellaneous functions }}} */
2903 /* Define labels and handlers for functional keys */
2905 view_labels (WView
*view
)
2907 Dlg_head
*h
= view
->widget
.parent
;
2909 buttonbar_set_label (h
, 1, Q_("ButtonBar|Help"), view_help_cmd
);
2911 my_define (h
, 10, Q_("ButtonBar|Quit"), view_quit_cmd
, view
);
2912 my_define (h
, 4, view
->hex_mode
2913 ? Q_("ButtonBar|Ascii")
2914 : Q_("ButtonBar|Hex"),
2915 view_toggle_hex_mode_cmd
, view
);
2916 my_define (h
, 5, view
->hex_mode
2917 ? Q_("ButtonBar|Goto")
2918 : Q_("ButtonBar|Line"),
2919 view
->hex_mode
? view_moveto_addr_cmd
: view_moveto_line_cmd
, view
);
2921 if (view
->hex_mode
) {
2922 if (view
->hexedit_mode
) {
2923 my_define (h
, 2, Q_("ButtonBar|View"),
2924 view_toggle_hexedit_mode_cmd
, view
);
2925 } else if (view
->datasource
== DS_FILE
) {
2926 my_define (h
, 2, Q_("ButtonBar|Edit"),
2927 view_toggle_hexedit_mode_cmd
, view
);
2929 buttonbar_clear_label (h
, 2);
2931 my_define (h
, 6, Q_("ButtonBar|Save"),
2932 view_hexedit_save_changes_cmd
, view
);
2934 my_define (h
, 2, view
->text_wrap_mode
2935 ? Q_("ButtonBar|UnWrap")
2936 : Q_("ButtonBar|Wrap"),
2937 view_toggle_wrap_mode_cmd
, view
);
2938 my_define (h
, 6, Q_("ButtonBar|RxSrch"),
2939 view_regexp_search_cmd
, view
);
2942 my_define (h
, 7, view
->hex_mode
2943 ? Q_("ButtonBar|HxSrch")
2944 : Q_("ButtonBar|Search"),
2945 view_normal_search_cmd
, view
);
2946 my_define (h
, 8, view
->magic_mode
2947 ? Q_("ButtonBar|Raw")
2948 : Q_("ButtonBar|Parse"),
2949 view_toggle_magic_mode_cmd
, view
);
2951 /* don't override the key to access the main menu */
2952 if (!view_is_in_panel (view
)) {
2953 my_define (h
, 9, view
->text_nroff_mode
2954 ? Q_("ButtonBar|Unform")
2955 : Q_("ButtonBar|Format"),
2956 view_toggle_nroff_mode_cmd
, view
);
2957 my_define (h
, 3, Q_("ButtonBar|Quit"), view_quit_cmd
, view
);
2961 /* {{{ Event handling }}} */
2963 /* Check for left and right arrows, possibly with modifiers */
2965 check_left_right_keys (WView
*view
, int c
)
2967 if (c
== KEY_LEFT
) {
2968 view_move_left (view
, 1);
2972 if (c
== KEY_RIGHT
) {
2973 view_move_right (view
, 1);
2977 /* Ctrl with arrows moves by 10 postions in the unwrap mode */
2978 if (view
->hex_mode
|| view
->text_wrap_mode
)
2979 return MSG_NOT_HANDLED
;
2981 if (c
== (KEY_M_CTRL
| KEY_LEFT
)) {
2982 if (view
->dpy_text_column
>= 10)
2983 view
->dpy_text_column
-= 10;
2985 view
->dpy_text_column
= 0;
2990 if (c
== (KEY_M_CTRL
| KEY_RIGHT
)) {
2991 if (view
->dpy_text_column
<= OFFSETTYPE_MAX
- 10)
2992 view
->dpy_text_column
+= 10;
2994 view
->dpy_text_column
= OFFSETTYPE_MAX
;
2999 return MSG_NOT_HANDLED
;
3002 /* {{{ User-definable commands }}} */
3005 view_continue_search_cmd (WView
*view
)
3007 if (view
->last_search
) {
3008 view
->last_search (view
);
3010 /* if not... then ask for an expression */
3011 view_normal_search_cmd (view
);
3016 view_toggle_ruler_cmd (WView
*view
)
3018 static const enum ruler_type next
[3] = {
3024 assert ((size_t) ruler
< 3);
3025 ruler
= next
[(size_t) ruler
];
3029 /* {{{ Event handling }}} */
3031 static void view_cmk_move_up (void *w
, int n
) {
3032 view_move_up ((WView
*) w
, n
);
3034 static void view_cmk_move_down (void *w
, int n
) {
3035 view_move_down ((WView
*) w
, n
);
3037 static void view_cmk_moveto_top (void *w
, int n
) {
3039 view_moveto_top ((WView
*) w
);
3041 static void view_cmk_moveto_bottom (void *w
, int n
) {
3043 view_moveto_bottom ((WView
*) w
);
3048 view_handle_key (WView
*view
, int c
)
3050 c
= convert_from_input_c (c
);
3052 if (view
->hex_mode
) {
3055 view
->hexview_in_text
= !view
->hexview_in_text
;
3060 view_moveto_bol (view
);
3065 view_move_left (view
, 1);
3069 view_moveto_eol (view
);
3073 view_move_right (view
, 1);
3077 if (view
->hexedit_mode
3078 && view_handle_editkey (view
, c
) == MSG_HANDLED
)
3082 if (check_left_right_keys (view
, c
))
3085 if (check_movement_keys (c
, view
->data_area
.height
+ 1, view
,
3086 view_cmk_move_up
, view_cmk_move_down
,
3087 view_cmk_moveto_top
, view_cmk_moveto_bottom
))
3093 regexp_search (view
, -1);
3097 regexp_search (view
, 1);
3100 /* Continue search */
3105 view_continue_search_cmd (view
);
3110 view_toggle_ruler_cmd (view
);
3114 view_move_left (view
, 1);
3120 view_move_down (view
, 1);
3124 view_move_down (view
, (view
->data_area
.height
+ 1) / 2);
3128 view_move_up (view
, (view
->data_area
.height
+ 1) / 2);
3133 view_move_up (view
, 1);
3137 view_move_right (view
, 1);
3142 view_move_down (view
, view
->data_area
.height
);
3149 /* Unlike Ctrl-O, run a new shell if the subshell is not running. */
3155 view_move_up (view
, view
->data_area
.height
);
3159 view_move_up (view
, 2);
3163 view_move_down (view
, 2);
3167 view
->marks
[view
->marker
] = view
->dpy_start
;
3171 view
->dpy_start
= view
->marks
[view
->marker
];
3175 /* Use to indicate parent that we want to see the next/previous file */
3176 /* Does not work in panel mode */
3179 if (!view_is_in_panel (view
))
3180 view
->move_dir
= c
== XCTRL ('f') ? 1 : -1;
3185 if (view_ok_to_quit (view
))
3186 view
->want_to_quit
= TRUE
;
3191 do_select_codepage ();
3195 #endif /* HAVE_CHARSET */
3197 #ifdef MC_ENABLE_DEBUGGING_CODE
3198 case 't': /* mnemonic: "test" */
3199 view_ccache_dump (view
);
3203 if (c
>= '0' && c
<= '9')
3204 view
->marker
= c
- '0';
3207 return MSG_NOT_HANDLED
;
3212 view_event (WView
*view
, Gpm_Event
*event
, int *result
)
3216 *result
= MOU_NORMAL
;
3218 /* We are not interested in the release events */
3219 if (!(event
->type
& (GPM_DOWN
| GPM_DRAG
)))
3223 if ((event
->buttons
& GPM_B_UP
) && (event
->type
& GPM_DOWN
)) {
3224 view_move_up (view
, 2);
3227 if ((event
->buttons
& GPM_B_DOWN
) && (event
->type
& GPM_DOWN
)) {
3228 view_move_down (view
, 2);
3235 /* Scrolling left and right */
3236 if (!view
->text_wrap_mode
) {
3237 if (x
< view
->data_area
.width
* 1/4) {
3238 view_move_left (view
, 1);
3240 } else if (x
< view
->data_area
.width
* 3/4) {
3241 /* ignore the click */
3243 view_move_right (view
, 1);
3248 /* Scrolling up and down */
3249 if (y
< view
->data_area
.top
+ view
->data_area
.height
* 1/3) {
3250 if (mouse_move_pages_viewer
)
3251 view_move_up (view
, view
->data_area
.height
/ 2);
3253 view_move_up (view
, 1);
3255 } else if (y
< view
->data_area
.top
+ view
->data_area
.height
* 2/3) {
3256 /* ignore the click */
3258 if (mouse_move_pages_viewer
)
3259 view_move_down (view
, view
->data_area
.height
/ 2);
3261 view_move_down (view
, 1);
3268 *result
= MOU_REPEAT
;
3272 /* Real view only */
3274 real_view_event (Gpm_Event
*event
, void *x
)
3276 WView
*view
= (WView
*) x
;
3279 if (view_event (view
, event
, &result
))
3285 view_adjust_size (Dlg_head
*h
)
3290 /* Look up the viewer and the buttonbar, we assume only two widgets here */
3291 view
= (WView
*) find_widget_type (h
, view_callback
);
3292 bar
= find_buttonbar (h
);
3293 widget_set_size (&view
->widget
, 0, 0, LINES
- 1, COLS
);
3294 widget_set_size ((Widget
*) bar
, LINES
- 1, 0, 1, COLS
);
3296 view_compute_areas (view
);
3297 view_update_bytes_per_line (view
);
3300 /* Callback for the view dialog */
3302 view_dialog_callback (Dlg_head
*h
, dlg_msg_t msg
, int parm
)
3306 view_adjust_size (h
);
3310 return default_dlg_callback (h
, msg
, parm
);
3314 /* {{{ External interface }}} */
3316 /* Real view only */
3318 mc_internal_viewer (const char *command
, const char *file
,
3319 int *move_dir_p
, int start_line
)
3326 /* Create dialog and widgets, put them on the dialog */
3328 create_dlg (0, 0, LINES
, COLS
, NULL
, view_dialog_callback
,
3329 "[Internal File Viewer]", NULL
, DLG_WANT_TAB
);
3331 wview
= view_new (0, 0, COLS
, LINES
- 1, 0);
3333 bar
= buttonbar_new (1);
3335 add_widget (view_dlg
, bar
);
3336 add_widget (view_dlg
, wview
);
3338 succeeded
= view_load (wview
, command
, file
, start_line
);
3342 *move_dir_p
= wview
->move_dir
;
3347 destroy_dlg (view_dlg
);
3352 /* {{{ Miscellaneous functions }}} */
3357 WView
*view
= (WView
*) v
;
3360 /* If the user is busy typing, wait until he finishes to update the
3363 if (!hook_present (idle_hook
, view_hook
))
3364 add_hook (&idle_hook
, view_hook
, v
);
3368 delete_hook (&idle_hook
, view_hook
);
3370 if (get_current_type () == view_listing
)
3371 panel
= current_panel
;
3372 else if (get_other_type () == view_listing
)
3373 panel
= other_panel
;
3377 view_load (view
, 0, panel
->dir
.list
[panel
->selected
].fname
, 0);
3381 /* {{{ Event handling }}} */
3384 view_callback (Widget
*w
, widget_msg_t msg
, int parm
)
3386 WView
*view
= (WView
*) w
;
3388 Dlg_head
*h
= view
->widget
.parent
;
3390 view_compute_areas (view
);
3391 view_update_bytes_per_line (view
);
3395 if (view_is_in_panel (view
))
3396 add_hook (&select_file_hook
, view_hook
, view
);
3398 view
->dpy_bbar_dirty
= TRUE
;
3407 view_place_cursor (view
);
3411 i
= view_handle_key ((WView
*) view
, parm
);
3412 if (view
->want_to_quit
&& !view_is_in_panel (view
))
3420 view
->dpy_bbar_dirty
= TRUE
;
3424 case WIDGET_DESTROY
:
3426 if (view_is_in_panel (view
))
3427 delete_hook (&select_file_hook
, view_hook
);
3431 return default_proc (msg
, parm
);
3435 /* {{{ External interface }}} */
3438 view_new (int y
, int x
, int cols
, int lines
, int is_panel
)
3440 WView
*view
= g_new0 (WView
, 1);
3443 init_widget (&view
->widget
, y
, x
, lines
, cols
,
3447 view
->filename
= NULL
;
3448 view
->command
= NULL
;
3450 view_set_datasource_none (view
);
3452 view
->growbuf_in_use
= FALSE
;
3453 /* leave the other growbuf fields uninitialized */
3455 view
->hex_mode
= FALSE
;
3456 view
->hexedit_mode
= FALSE
;
3457 view
->hexview_in_text
= FALSE
;
3458 view
->text_nroff_mode
= FALSE
;
3459 view
->text_wrap_mode
= FALSE
;
3460 view
->magic_mode
= FALSE
;
3462 view
->hexedit_lownibble
= FALSE
;
3463 view
->coord_cache
= NULL
;
3465 view
->dpy_frame_size
= is_panel
? 1 : 0;
3466 view
->dpy_start
= 0;
3467 view
->dpy_text_column
= 0;
3469 view
->hex_cursor
= 0;
3470 view
->cursor_col
= 0;
3471 view
->cursor_row
= 0;
3472 view
->change_list
= NULL
;
3474 /* {status,ruler,data}_area are left uninitialized */
3477 view
->dpy_bbar_dirty
= TRUE
;
3478 view
->bytes_per_line
= 1;
3480 view
->search_start
= 0;
3481 view
->search_length
= 0;
3482 view
->search_exp
= NULL
;
3483 view
->direction
= 1; /* forward */
3484 view
->last_search
= 0; /* it's a function */
3486 view
->want_to_quit
= FALSE
;
3488 for (i
= 0; i
< sizeof(view
->marks
) / sizeof(view
->marks
[0]); i
++)
3492 view
->update_steps
= 0;
3493 view
->update_activate
= 0;
3495 if (default_hex_mode
)
3496 view_toggle_hex_mode (view
);
3497 if (default_nroff_flag
)
3498 view_toggle_nroff_mode (view
);
3499 if (global_wrap_mode
)
3500 view_toggle_wrap_mode (view
);
3501 if (default_magic_flag
)
3502 view_toggle_magic_mode (view
);