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>
47 #include <mhl/memory.h>
48 #include <mhl/string.h>
52 #include "cmd.h" /* For view_other_cmd */
53 #include "dialog.h" /* Needed by widget.h */
54 #include "widget.h" /* Needed for buttonbar_new */
58 #include "key.h" /* For mi_getch() */
61 #include "wtools.h" /* For query_set_sel() */
63 #include "panel.h" /* Needed for current_panel and other_panel */
66 #include "main.h" /* slow_terminal */
70 #include "selcodepage.h"
72 /* Block size for reading files in parts */
73 #define VIEW_PAGE_SIZE ((size_t) 8192)
74 #define VIEW_COORD_CACHE_GRANUL 1024
76 typedef unsigned char byte
;
78 /* Offset in bytes into a file */
79 typedef unsigned long offset_type
;
80 #define INVALID_OFFSET ((offset_type) -1)
81 #define OFFSETTYPE_MAX (~((offset_type) 0))
82 #define OFFSETTYPE_PRIX "lX"
83 #define OFFSETTYPE_PRId "lu"
85 /* A width or height on the screen */
86 typedef unsigned int screen_dimen
;
88 /* A cache entry for mapping offsets into line/column pairs and vice versa.
89 * cc_offset, cc_line, and cc_column are the 0-based values of the offset,
90 * line and column of that cache entry. cc_nroff_column is the column
91 * corresponding to cc_offset in nroff mode.
93 struct coord_cache_entry
{
94 offset_type cc_offset
;
96 offset_type cc_column
;
97 offset_type cc_nroff_column
;
100 /* A node for building a change list on change_list */
101 struct hexedit_change_node
{
102 struct hexedit_change_node
*next
;
107 /* data sources of the view */
109 DS_NONE
, /* No data available */
110 DS_STDIO_PIPE
, /* Data comes from a pipe using popen/pclose */
111 DS_VFS_PIPE
, /* Data comes from a piped-in VFS file */
112 DS_FILE
, /* Data comes from a VFS file */
113 DS_STRING
/* Data comes from a string in memory */
117 screen_dimen top
, left
;
118 screen_dimen height
, width
;
124 char *filename
; /* Name of the file */
125 char *command
; /* Command used to pipe data in */
127 enum view_ds datasource
; /* Where the displayed data comes from */
129 /* stdio pipe data source */
130 FILE *ds_stdio_pipe
; /* Output of a shell command */
132 /* vfs pipe data source */
133 int ds_vfs_pipe
; /* Non-seekable vfs file descriptor */
135 /* vfs file data source */
136 int ds_file_fd
; /* File with random access */
137 off_t ds_file_filesize
; /* Size of the file */
138 off_t ds_file_offset
; /* Offset of the currently loaded data */
139 byte
*ds_file_data
; /* Currently loaded data */
140 size_t ds_file_datalen
; /* Number of valid bytes in file_data */
141 size_t ds_file_datasize
; /* Number of allocated bytes in file_data */
143 /* string data source */
144 byte
*ds_string_data
; /* The characters of the string */
145 size_t ds_string_len
; /* The length of the string */
147 /* Growing buffers information */
148 gboolean growbuf_in_use
; /* Use the growing buffers? */
149 byte
**growbuf_blockptr
; /* Pointer to the block pointers */
150 size_t growbuf_blocks
; /* The number of blocks in *block_ptr */
151 size_t growbuf_lastindex
; /* Number of bytes in the last page of the
153 gboolean growbuf_finished
; /* TRUE when all data has been read. */
156 gboolean hex_mode
; /* Hexview or Hexedit */
157 gboolean hexedit_mode
; /* Hexedit */
158 gboolean hexview_in_text
; /* Is the hexview cursor in the text area? */
159 gboolean text_nroff_mode
; /* Nroff-style highlighting */
160 gboolean text_wrap_mode
; /* Wrap text lines to fit them on the screen */
161 gboolean magic_mode
; /* Preprocess the file using external programs */
163 /* Additional editor state */
164 gboolean hexedit_lownibble
; /* Are we editing the last significant nibble? */
165 GArray
*coord_cache
; /* Cache for mapping offsets to cursor positions */
167 /* Display information */
168 screen_dimen dpy_frame_size
;/* Size of the frame surrounding the real viewer */
169 offset_type dpy_start
; /* Offset of the displayed data */
170 offset_type dpy_end
; /* Offset after the displayed data */
171 offset_type dpy_text_column
;/* Number of skipped columns in non-wrap
173 offset_type hex_cursor
; /* Hexview cursor position in file */
174 screen_dimen cursor_col
; /* Cursor column */
175 screen_dimen cursor_row
; /* Cursor row */
176 struct hexedit_change_node
*change_list
; /* Linked list of changes */
177 struct area status_area
; /* Where the status line is displayed */
178 struct area ruler_area
; /* Where the ruler is displayed */
179 struct area data_area
; /* Where the data is displayed */
181 int dirty
; /* Number of skipped updates */
182 gboolean dpy_bbar_dirty
; /* Does the button bar need to be updated? */
185 int bytes_per_line
; /* Number of bytes per line in hex mode */
187 /* Search variables */
188 offset_type search_start
; /* First character to start searching from */
189 offset_type search_length
; /* Length of found string or 0 if none was found */
190 char *search_exp
; /* The search expression */
191 int direction
; /* 1= forward; -1 backward */
192 void (*last_search
)(WView
*);
193 /* Pointer to the last search command */
194 gboolean want_to_quit
; /* Prepare for cleanup ... */
197 int marker
; /* mark to use */
198 offset_type marks
[10]; /* 10 marks: 0..9 */
200 int move_dir
; /* return value from widget:
202 * -1 view previous file
206 offset_type update_steps
; /* The number of bytes between percent
208 offset_type update_activate
;/* Last point where we updated the status */
212 /* {{{ Global Variables }}} */
214 /* Maxlimit for skipping updates */
215 int max_dirt_limit
= 10;
217 /* If set, show a ruler */
218 static enum ruler_type
{
222 } ruler
= RULER_NONE
;
224 /* Scrolling is done in pages or line increments */
225 int mouse_move_pages_viewer
= 1;
227 /* wrap mode default */
228 int global_wrap_mode
= 1;
230 int default_hex_mode
= 0;
231 int default_magic_flag
= 1;
232 int default_nroff_flag
= 1;
233 int altered_hex_mode
= 0;
234 int altered_magic_flag
= 0;
235 int altered_nroff_flag
= 0;
237 static const char hex_char
[] = "0123456789ABCDEF";
239 int mcview_remember_file_position
= FALSE
;
241 /* {{{ Function Prototypes }}} */
243 /* Our widget callback */
244 static cb_ret_t
view_callback (Widget
*, widget_msg_t
, int);
246 static int regexp_view_search (WView
* view
, char *pattern
, char *string
,
248 static void view_labels (WView
* view
);
250 static void view_init_growbuf (WView
*);
251 static void view_place_cursor (WView
*view
);
252 static void display (WView
*);
253 static void view_done (WView
*);
255 /* {{{ Helper Functions }}} */
257 /* difference or zero */
258 static inline screen_dimen
259 dimen_doz (screen_dimen a
, screen_dimen b
)
261 return (a
>= b
) ? a
- b
: 0;
264 static inline screen_dimen
265 dimen_min (screen_dimen a
, screen_dimen b
)
267 return (a
< b
) ? a
: b
;
270 static inline offset_type
271 offset_doz (offset_type a
, offset_type b
)
273 return (a
>= b
) ? a
- b
: 0;
276 static inline offset_type
277 offset_rounddown (offset_type a
, offset_type b
)
283 /* {{{ Simple Primitive Functions for WView }}} */
285 static inline gboolean
286 view_is_in_panel (WView
*view
)
288 return (view
->dpy_frame_size
!= 0);
292 view_compute_areas (WView
*view
)
294 struct area view_area
;
295 screen_dimen height
, rest
, y
;
297 /* The viewer is surrounded by a frame of size view->dpy_frame_size.
298 * Inside that frame, there are: The status line (at the top),
299 * the data area and an optional ruler, which is shown above or
300 * below the data area. */
302 view_area
.top
= view
->dpy_frame_size
;
303 view_area
.left
= view
->dpy_frame_size
;
304 view_area
.height
= dimen_doz(view
->widget
.lines
, 2 * view
->dpy_frame_size
);
305 view_area
.width
= dimen_doz(view
->widget
.cols
, 2 * view
->dpy_frame_size
);
307 /* Most coordinates of the areas equal those of the whole viewer */
308 view
->status_area
= view_area
;
309 view
->ruler_area
= view_area
;
310 view
->data_area
= view_area
;
312 /* Compute the heights of the areas */
313 rest
= view_area
.height
;
315 height
= dimen_min(rest
, 1);
316 view
->status_area
.height
= height
;
319 height
= dimen_min(rest
, (ruler
== RULER_NONE
|| view
->hex_mode
) ? 0 : 2);
320 view
->ruler_area
.height
= height
;
323 view
->data_area
.height
= rest
;
325 /* Compute the position of the areas */
328 view
->status_area
.top
= y
;
329 y
+= view
->status_area
.height
;
331 if (ruler
== RULER_TOP
) {
332 view
->ruler_area
.top
= y
;
333 y
+= view
->ruler_area
.height
;
336 view
->data_area
.top
= y
;
337 y
+= view
->data_area
.height
;
339 if (ruler
== RULER_BOTTOM
) {
340 view
->ruler_area
.top
= y
;
341 y
+= view
->ruler_area
.height
;
346 view_hexedit_free_change_list (WView
*view
)
348 struct hexedit_change_node
*curr
, *next
;
350 for (curr
= view
->change_list
; curr
!= NULL
; curr
= next
) {
354 view
->change_list
= NULL
;
358 /* {{{ Growing buffer }}} */
361 view_init_growbuf (WView
*view
)
363 view
->growbuf_in_use
= TRUE
;
364 view
->growbuf_blockptr
= NULL
;
365 view
->growbuf_blocks
= 0;
366 view
->growbuf_lastindex
= VIEW_PAGE_SIZE
;
367 view
->growbuf_finished
= FALSE
;
371 view_growbuf_free (WView
*view
)
375 assert (view
->growbuf_in_use
);
377 for (i
= 0; i
< view
->growbuf_blocks
; i
++)
378 g_free (view
->growbuf_blockptr
[i
]);
379 g_free (view
->growbuf_blockptr
);
380 view
->growbuf_blockptr
= NULL
;
381 view
->growbuf_in_use
= FALSE
;
385 view_growbuf_filesize (WView
*view
)
387 assert(view
->growbuf_in_use
);
389 if (view
->growbuf_blocks
== 0)
392 return ((offset_type
) view
->growbuf_blocks
- 1) * VIEW_PAGE_SIZE
393 + view
->growbuf_lastindex
;
396 /* Copies the output from the pipe to the growing buffer, until either
397 * the end-of-pipe is reached or the interval [0..ofs) of the growing
398 * buffer is completely filled. */
400 view_growbuf_read_until (WView
*view
, offset_type ofs
)
407 assert (view
->growbuf_in_use
);
409 if (view
->growbuf_finished
)
413 while (view_growbuf_filesize (view
) < ofs
|| short_read
) {
414 if (view
->growbuf_lastindex
== VIEW_PAGE_SIZE
) {
415 /* Append a new block to the growing buffer */
416 byte
*newblock
= g_try_malloc (VIEW_PAGE_SIZE
);
417 byte
**newblocks
= g_try_malloc (sizeof (*newblocks
) * (view
->growbuf_blocks
+ 1));
418 if (!newblock
|| !newblocks
) {
423 memcpy (newblocks
, view
->growbuf_blockptr
, sizeof (*newblocks
) * view
->growbuf_blocks
);
424 g_free (view
->growbuf_blockptr
);
425 view
->growbuf_blockptr
= newblocks
;
426 view
->growbuf_blockptr
[view
->growbuf_blocks
++] = newblock
;
427 view
->growbuf_lastindex
= 0;
429 p
= view
->growbuf_blockptr
[view
->growbuf_blocks
- 1] + view
->growbuf_lastindex
;
430 bytesfree
= VIEW_PAGE_SIZE
- view
->growbuf_lastindex
;
432 if (view
->datasource
== DS_STDIO_PIPE
) {
433 nread
= fread (p
, 1, bytesfree
, view
->ds_stdio_pipe
);
435 view
->growbuf_finished
= TRUE
;
436 (void) pclose (view
->ds_stdio_pipe
);
438 close_error_pipe (D_NORMAL
, NULL
);
439 view
->ds_stdio_pipe
= NULL
;
443 assert (view
->datasource
== DS_VFS_PIPE
);
445 nread
= mc_read (view
->ds_vfs_pipe
, p
, bytesfree
);
446 } while (nread
== -1 && errno
== EINTR
);
447 if (nread
== -1 || nread
== 0) {
448 view
->growbuf_finished
= TRUE
;
449 (void) mc_close (view
->ds_vfs_pipe
);
450 view
->ds_vfs_pipe
= -1;
454 short_read
= ((size_t)nread
< bytesfree
);
455 view
->growbuf_lastindex
+= nread
;
460 get_byte_growing_buffer (WView
*view
, offset_type byte_index
)
462 offset_type pageno
= byte_index
/ VIEW_PAGE_SIZE
;
463 offset_type pageindex
= byte_index
% VIEW_PAGE_SIZE
;
465 assert (view
->growbuf_in_use
);
467 if ((size_t) pageno
!= pageno
)
470 view_growbuf_read_until (view
, byte_index
+ 1);
471 if (view
->growbuf_blocks
== 0)
473 if (pageno
< view
->growbuf_blocks
- 1)
474 return view
->growbuf_blockptr
[pageno
][pageindex
];
475 if (pageno
== view
->growbuf_blocks
- 1 && pageindex
< view
->growbuf_lastindex
)
476 return view
->growbuf_blockptr
[pageno
][pageindex
];
480 /* {{{ Data sources }}} */
483 The data source provides the viewer with data from either a file, a
484 string or the output of a command. The get_byte() function can be
485 used to get the value of a byte at a specific offset. If the offset
486 is out of range, -1 is returned. The function get_byte_indexed(a,b)
487 returns the byte at the offset a+b, or -1 if a+b is out of range.
489 The view_set_byte() function has the effect that later calls to
490 get_byte() will return the specified byte for this offset. This
491 function is designed only for use by the hexedit component after
492 saving its changes. Inspect the source before you want to use it for
495 The view_get_filesize() function returns the current size of the
496 data source. If the growing buffer is used, this size may increase
497 later on. Use the view_may_still_grow() function when you want to
498 know if the size can change later.
502 view_get_filesize (WView
*view
)
504 switch (view
->datasource
) {
509 return view_growbuf_filesize (view
);
511 return view
->ds_file_filesize
;
513 return view
->ds_string_len
;
515 assert(!"Unknown datasource type");
520 static inline gboolean
521 view_may_still_grow (WView
*view
)
523 return (view
->growbuf_in_use
&& !view
->growbuf_finished
);
526 /* returns TRUE if the idx lies in the half-open interval
527 * [offset; offset + size), FALSE otherwise.
529 static inline gboolean
530 already_loaded (offset_type offset
, offset_type idx
, size_t size
)
532 return (offset
<= idx
&& idx
- offset
< size
);
536 view_file_load_data (WView
*view
, offset_type byte_index
)
538 offset_type blockoffset
;
542 assert (view
->datasource
== DS_FILE
);
544 if (already_loaded (view
->ds_file_offset
, byte_index
, view
->ds_file_datalen
))
547 if (byte_index
>= view
->ds_file_filesize
)
550 blockoffset
= offset_rounddown (byte_index
, view
->ds_file_datasize
);
551 if (mc_lseek (view
->ds_file_fd
, blockoffset
, SEEK_SET
) == -1)
555 while (bytes_read
< view
->ds_file_datasize
) {
556 res
= mc_read (view
->ds_file_fd
, view
->ds_file_data
+ bytes_read
, view
->ds_file_datasize
- bytes_read
);
561 bytes_read
+= (size_t) res
;
563 view
->ds_file_offset
= blockoffset
;
564 if (bytes_read
> view
->ds_file_filesize
- view
->ds_file_offset
) {
565 /* the file has grown in the meantime -- stick to the old size */
566 view
->ds_file_datalen
= view
->ds_file_filesize
- view
->ds_file_offset
;
568 view
->ds_file_datalen
= bytes_read
;
573 view
->ds_file_datalen
= 0;
577 get_byte_none (WView
*view
, offset_type byte_index
)
579 assert (view
->datasource
== DS_NONE
);
586 get_byte_file (WView
*view
, offset_type byte_index
)
588 assert (view
->datasource
== DS_FILE
);
590 view_file_load_data (view
, byte_index
);
591 if (already_loaded(view
->ds_file_offset
, byte_index
, view
->ds_file_datalen
))
592 return view
->ds_file_data
[byte_index
- view
->ds_file_offset
];
597 get_byte_string (WView
*view
, offset_type byte_index
)
599 assert (view
->datasource
== DS_STRING
);
600 if (byte_index
< view
->ds_string_len
)
601 return view
->ds_string_data
[byte_index
];
606 get_byte (WView
*view
, offset_type offset
)
608 switch (view
->datasource
) {
611 return get_byte_growing_buffer (view
, offset
);
613 return get_byte_file (view
, offset
);
615 return get_byte_string (view
, offset
);
617 return get_byte_none (view
, offset
);
619 assert(!"Unknown datasource type");
624 get_byte_indexed (WView
*view
, offset_type base
, offset_type ofs
)
626 if (base
<= OFFSETTYPE_MAX
- ofs
)
627 return get_byte (view
, base
+ ofs
);
632 view_set_byte (WView
*view
, offset_type offset
, byte b
)
635 assert (offset
< view_get_filesize (view
));
636 assert (view
->datasource
== DS_FILE
);
637 view
->ds_file_datalen
= 0; /* just force reloading */
641 view_set_datasource_none (WView
*view
)
643 view
->datasource
= DS_NONE
;
647 view_set_datasource_vfs_pipe (WView
*view
, int fd
)
650 view
->datasource
= DS_VFS_PIPE
;
651 view
->ds_vfs_pipe
= fd
;
653 view_init_growbuf (view
);
657 view_set_datasource_stdio_pipe (WView
*view
, FILE *fp
)
660 view
->datasource
= DS_STDIO_PIPE
;
661 view
->ds_stdio_pipe
= fp
;
663 view_init_growbuf (view
);
667 view_set_datasource_string (WView
*view
, const char *s
)
669 view
->datasource
= DS_STRING
;
670 view
->ds_string_data
= (byte
*) g_strdup (s
);
671 view
->ds_string_len
= strlen (s
);
675 view_set_datasource_file (WView
*view
, int fd
, const struct stat
*st
)
677 view
->datasource
= DS_FILE
;
678 view
->ds_file_fd
= fd
;
679 view
->ds_file_filesize
= st
->st_size
;
680 view
->ds_file_offset
= 0;
681 view
->ds_file_data
= g_malloc (4096);
682 view
->ds_file_datalen
= 0;
683 view
->ds_file_datasize
= 4096;
687 view_close_datasource (WView
*view
)
689 switch (view
->datasource
) {
693 if (view
->ds_stdio_pipe
!= NULL
) {
694 (void) pclose (view
->ds_stdio_pipe
);
696 close_error_pipe (D_NORMAL
, NULL
);
697 view
->ds_stdio_pipe
= NULL
;
699 view_growbuf_free (view
);
702 if (view
->ds_vfs_pipe
!= -1) {
703 (void) mc_close (view
->ds_vfs_pipe
);
704 view
->ds_vfs_pipe
= -1;
706 view_growbuf_free (view
);
709 (void) mc_close (view
->ds_file_fd
);
710 view
->ds_file_fd
= -1;
711 g_free (view
->ds_file_data
);
712 view
->ds_file_data
= NULL
;
715 g_free (view
->ds_string_data
);
716 view
->ds_string_data
= NULL
;
719 assert (!"Unknown datasource type");
721 view
->datasource
= DS_NONE
;
724 /* {{{ The Coordinate Cache }}} */
727 This cache provides you with a fast lookup to map file offsets into
728 line/column pairs and vice versa. The interface to the mapping is
729 provided by the functions view_coord_to_offset() and
730 view_offset_to_coord().
732 The cache is implemented as a simple sorted array holding entries
733 that map some of the offsets to their line/column pair. Entries that
734 are not cached themselves are interpolated (exactly) from their
735 neighbor entries. The algorithm used for determining the line/column
736 for a specific offset needs to be kept synchronized with the one used
745 static inline gboolean
746 coord_cache_entry_less (const struct coord_cache_entry
*a
,
747 const struct coord_cache_entry
*b
, enum ccache_type crit
,
750 if (crit
== CCACHE_OFFSET
)
751 return (a
->cc_offset
< b
->cc_offset
);
753 if (a
->cc_line
< b
->cc_line
)
756 if (a
->cc_line
== b
->cc_line
) {
758 return (a
->cc_nroff_column
< b
->cc_nroff_column
);
760 return (a
->cc_column
< b
->cc_column
);
766 #ifdef MC_ENABLE_DEBUGGING_CODE
767 static void view_coord_to_offset (WView
*, offset_type
*, offset_type
, offset_type
);
768 static void view_offset_to_coord (WView
*, offset_type
*, offset_type
*, offset_type
);
771 view_ccache_dump (WView
*view
)
774 offset_type offset
, line
, column
, nextline_offset
, filesize
;
776 const struct coord_cache_entry
*cache
;
778 assert (view
->coord_cache
!= NULL
);
780 filesize
= view_get_filesize (view
);
781 cache
= &(g_array_index (view
->coord_cache
, struct coord_cache_entry
, 0));
783 f
= fopen("mcview-ccache.out", "w");
786 (void)setvbuf(f
, NULL
, _IONBF
, 0);
789 for (i
= 0; i
< view
->coord_cache
->len
; i
++) {
792 "offset %8"OFFSETTYPE_PRId
" "
793 "line %8"OFFSETTYPE_PRId
" "
794 "column %8"OFFSETTYPE_PRId
" "
795 "nroff_column %8"OFFSETTYPE_PRId
"\n",
796 (unsigned int) i
, cache
[i
].cc_offset
, cache
[i
].cc_line
,
797 cache
[i
].cc_column
, cache
[i
].cc_nroff_column
);
799 (void)fprintf (f
, "\n");
801 /* offset -> line/column translation */
802 for (offset
= 0; offset
< filesize
; offset
++) {
803 view_offset_to_coord (view
, &line
, &column
, offset
);
805 "offset %8"OFFSETTYPE_PRId
" "
806 "line %8"OFFSETTYPE_PRId
" "
807 "column %8"OFFSETTYPE_PRId
"\n",
808 offset
, line
, column
);
811 /* line/column -> offset translation */
812 for (line
= 0; TRUE
; line
++) {
813 view_coord_to_offset (view
, &nextline_offset
, line
+ 1, 0);
814 (void)fprintf (f
, "nextline_offset %8"OFFSETTYPE_PRId
"\n",
817 for (column
= 0; TRUE
; column
++) {
818 view_coord_to_offset (view
, &offset
, line
, column
);
819 if (offset
>= nextline_offset
)
822 (void)fprintf (f
, "line %8"OFFSETTYPE_PRId
" column %8"OFFSETTYPE_PRId
" offset %8"OFFSETTYPE_PRId
"\n",
823 line
, column
, offset
);
826 if (nextline_offset
>= filesize
- 1)
834 static inline gboolean
835 is_nroff_sequence (WView
*view
, offset_type offset
)
839 /* The following commands are ordered to speed up the calculation. */
841 c1
= get_byte_indexed (view
, offset
, 1);
842 if (c1
== -1 || c1
!= '\b')
845 c0
= get_byte_indexed (view
, offset
, 0);
846 if (c0
== -1 || !is_printable(c0
))
849 c2
= get_byte_indexed (view
, offset
, 2);
850 if (c2
== -1 || !is_printable(c2
))
853 return (c0
== c2
|| c0
== '_' || (c0
== '+' && c2
== 'o'));
856 /* Find and return the index of the last cache entry that is
857 * smaller than ''coord'', according to the criterion ''sort_by''. */
859 view_ccache_find (WView
*view
, const struct coord_cache_entry
*cache
,
860 const struct coord_cache_entry
*coord
, enum ccache_type sort_by
)
862 guint base
, i
, limit
;
864 limit
= view
->coord_cache
->len
;
869 i
= base
+ limit
/ 2;
870 if (coord_cache_entry_less (coord
, &cache
[i
], sort_by
, view
->text_nroff_mode
)) {
871 /* continue the search in the lower half of the cache */
873 /* continue the search in the upper half of the cache */
876 limit
= (limit
+ 1) / 2;
881 /* Look up the missing components of ''coord'', which are given by
882 * ''lookup_what''. The function returns the smallest value that
883 * matches the existing components of ''coord''.
886 view_ccache_lookup (WView
*view
, struct coord_cache_entry
*coord
,
887 enum ccache_type lookup_what
)
890 struct coord_cache_entry
*cache
, current
, next
, entry
;
891 enum ccache_type sorter
;
899 if (!view
->coord_cache
) {
900 view
->coord_cache
= g_array_new (FALSE
, FALSE
, sizeof(struct coord_cache_entry
));
901 current
.cc_offset
= 0;
903 current
.cc_column
= 0;
904 current
.cc_nroff_column
= 0;
905 g_array_append_val (view
->coord_cache
, current
);
908 sorter
= (lookup_what
== CCACHE_OFFSET
) ? CCACHE_LINECOL
: CCACHE_OFFSET
;
911 /* find the two neighbor entries in the cache */
912 cache
= &(g_array_index (view
->coord_cache
, struct coord_cache_entry
, 0));
913 i
= view_ccache_find (view
, cache
, coord
, sorter
);
914 /* now i points to the lower neighbor in the cache */
917 if (i
+ 1 < view
->coord_cache
->len
)
918 limit
= cache
[i
+ 1].cc_offset
;
920 limit
= current
.cc_offset
+ VIEW_COORD_CACHE_GRANUL
;
923 nroff_state
= NROFF_START
;
924 for (; current
.cc_offset
< limit
; current
= next
) {
927 if ((c
= get_byte (view
, current
.cc_offset
)) == -1)
930 if (!coord_cache_entry_less (¤t
, coord
, sorter
, view
->text_nroff_mode
)) {
931 if (lookup_what
== CCACHE_OFFSET
932 && view
->text_nroff_mode
933 && nroff_state
!= NROFF_START
) {
934 /* don't break here */
940 /* Provide useful default values for ''next'' */
941 next
.cc_offset
= current
.cc_offset
+ 1;
942 next
.cc_line
= current
.cc_line
;
943 next
.cc_column
= current
.cc_column
+ 1;
944 next
.cc_nroff_column
= current
.cc_nroff_column
+ 1;
946 /* and override some of them as necessary. */
948 nextc
= get_byte_indexed(view
, current
.cc_offset
, 1);
950 /* Ignore '\r' if it is followed by '\r' or '\n'. If it is
951 * followed by anything else, it is a Mac line ending and
952 * produces a line break.
954 if (nextc
== '\r' || nextc
== '\n') {
955 next
.cc_column
= current
.cc_column
;
956 next
.cc_nroff_column
= current
.cc_nroff_column
;
958 next
.cc_line
= current
.cc_line
+ 1;
960 next
.cc_nroff_column
= 0;
963 } else if (nroff_state
== NROFF_BACKSPACE
) {
964 next
.cc_nroff_column
= current
.cc_nroff_column
- 1;
966 } else if (c
== '\t') {
967 next
.cc_column
= offset_rounddown (current
.cc_column
, 8) + 8;
968 next
.cc_nroff_column
=
969 offset_rounddown (current
.cc_nroff_column
, 8) + 8;
971 } else if (c
== '\n') {
972 next
.cc_line
= current
.cc_line
+ 1;
974 next
.cc_nroff_column
= 0;
977 /* Use all default values from above */
980 switch (nroff_state
) {
982 case NROFF_CONTINUATION
:
983 if (is_nroff_sequence (view
, current
.cc_offset
))
984 nroff_state
= NROFF_BACKSPACE
;
986 nroff_state
= NROFF_START
;
988 case NROFF_BACKSPACE
:
989 nroff_state
= NROFF_CONTINUATION
;
993 /* Cache entries must guarantee that for each i < j,
994 * line[i] <= line[j] and column[i] < column[j]. In the case of
995 * nroff sequences and '\r' characters, this is not guaranteed,
996 * so we cannot save them. */
997 if (nroff_state
== NROFF_START
&& c
!= '\r')
1001 if (i
+ 1 == view
->coord_cache
->len
&& entry
.cc_offset
!= cache
[i
].cc_offset
) {
1002 g_array_append_val (view
->coord_cache
, entry
);
1006 if (lookup_what
== CCACHE_OFFSET
) {
1007 coord
->cc_offset
= current
.cc_offset
;
1009 coord
->cc_line
= current
.cc_line
;
1010 coord
->cc_column
= current
.cc_column
;
1011 coord
->cc_nroff_column
= current
.cc_nroff_column
;
1016 view_coord_to_offset (WView
*view
, offset_type
*ret_offset
,
1017 offset_type line
, offset_type column
)
1019 struct coord_cache_entry coord
;
1021 coord
.cc_line
= line
;
1022 coord
.cc_column
= column
;
1023 coord
.cc_nroff_column
= column
;
1024 view_ccache_lookup (view
, &coord
, CCACHE_OFFSET
);
1025 *ret_offset
= coord
.cc_offset
;
1029 view_offset_to_coord (WView
*view
, offset_type
*ret_line
,
1030 offset_type
*ret_column
, offset_type offset
)
1032 struct coord_cache_entry coord
;
1034 coord
.cc_offset
= offset
;
1035 view_ccache_lookup (view
, &coord
, CCACHE_LINECOL
);
1036 *ret_line
= coord
.cc_line
;
1037 *ret_column
= (view
->text_nroff_mode
)
1038 ? coord
.cc_nroff_column
1042 /* {{{ Cursor Movement }}} */
1045 The following variables have to do with the current position and are
1046 updated by the cursor movement functions.
1048 In hex view and wrapped text view mode, dpy_start marks the offset of
1049 the top-left corner on the screen, in non-wrapping text mode it is
1050 the beginning of the current line. In hex mode, hex_cursor is the
1051 offset of the cursor. In non-wrapping text mode, dpy_text_column is
1052 the number of columns that are hidden on the left side on the screen.
1054 In hex mode, dpy_start is updated by the view_fix_cursor_position()
1055 function in order to keep the other functions simple. In
1056 non-wrapping text mode dpy_start and dpy_text_column are normalized
1057 such that dpy_text_column < view_get_datacolumns().
1060 /* prototypes for functions used by view_moveto_bottom() */
1061 static void view_move_up (WView
*, offset_type
);
1062 static void view_moveto_bol (WView
*);
1065 view_scroll_to_cursor (WView
*view
)
1067 if (view
->hex_mode
) {
1068 const offset_type bytes
= view
->bytes_per_line
;
1069 const offset_type displaysize
= view
->data_area
.height
* bytes
;
1070 const offset_type cursor
= view
->hex_cursor
;
1071 offset_type topleft
= view
->dpy_start
;
1073 if (topleft
+ displaysize
<= cursor
)
1074 topleft
= offset_rounddown (cursor
, bytes
)
1075 - (displaysize
- bytes
);
1076 if (cursor
< topleft
)
1077 topleft
= offset_rounddown (cursor
, bytes
);
1078 view
->dpy_start
= topleft
;
1079 } else if (view
->text_wrap_mode
) {
1080 offset_type line
, col
, columns
;
1082 columns
= view
->data_area
.width
;
1083 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
+ view
->dpy_text_column
);
1085 col
= offset_rounddown (col
, columns
);
1086 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1087 view
->dpy_text_column
= 0;
1094 view_movement_fixups (WView
*view
, gboolean reset_search
)
1096 view_scroll_to_cursor (view
);
1098 view
->search_start
= view
->dpy_start
;
1099 view
->search_length
= 0;
1105 view_moveto_top (WView
*view
)
1107 view
->dpy_start
= 0;
1108 view
->hex_cursor
= 0;
1109 view
->dpy_text_column
= 0;
1110 view_movement_fixups (view
, TRUE
);
1114 view_moveto_bottom (WView
*view
)
1116 offset_type datalines
, lines_up
, filesize
, last_offset
;
1118 if (view
->growbuf_in_use
)
1119 view_growbuf_read_until (view
, OFFSETTYPE_MAX
);
1121 filesize
= view_get_filesize (view
);
1122 last_offset
= offset_doz(filesize
, 1);
1123 datalines
= view
->data_area
.height
;
1124 lines_up
= offset_doz(datalines
, 1);
1126 if (view
->hex_mode
) {
1127 view
->hex_cursor
= filesize
;
1128 view_move_up (view
, lines_up
);
1129 view
->hex_cursor
= last_offset
;
1131 view
->dpy_start
= last_offset
;
1132 view_moveto_bol (view
);
1133 view_move_up (view
, lines_up
);
1135 view_movement_fixups (view
, TRUE
);
1139 view_moveto_bol (WView
*view
)
1141 if (view
->hex_mode
) {
1142 view
->hex_cursor
-= view
->hex_cursor
% view
->bytes_per_line
;
1143 } else if (view
->text_wrap_mode
) {
1146 offset_type line
, column
;
1147 view_offset_to_coord (view
, &line
, &column
, view
->dpy_start
);
1148 view_coord_to_offset (view
, &(view
->dpy_start
), line
, 0);
1149 view
->dpy_text_column
= 0;
1151 view_movement_fixups (view
, TRUE
);
1155 view_moveto_eol (WView
*view
)
1157 if (view
->hex_mode
) {
1158 offset_type filesize
, bol
;
1160 bol
= offset_rounddown (view
->hex_cursor
, view
->bytes_per_line
);
1161 if (get_byte_indexed (view
, bol
, view
->bytes_per_line
- 1) != -1) {
1162 view
->hex_cursor
= bol
+ view
->bytes_per_line
- 1;
1164 filesize
= view_get_filesize (view
);
1165 view
->hex_cursor
= offset_doz(filesize
, 1);
1167 } else if (view
->text_wrap_mode
) {
1170 offset_type line
, col
;
1172 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1173 view_coord_to_offset (view
, &(view
->dpy_start
), line
, OFFSETTYPE_MAX
);
1175 view_movement_fixups (view
, FALSE
);
1179 view_moveto_offset (WView
*view
, offset_type offset
)
1181 if (view
->hex_mode
) {
1182 view
->hex_cursor
= offset
;
1183 view
->dpy_start
= offset
- offset
% view
->bytes_per_line
;
1185 view
->dpy_start
= offset
;
1187 view_movement_fixups (view
, TRUE
);
1191 view_moveto (WView
*view
, offset_type line
, offset_type col
)
1195 view_coord_to_offset (view
, &offset
, line
, col
);
1196 view_moveto_offset (view
, offset
);
1200 view_move_up (WView
*view
, offset_type lines
)
1202 if (view
->hex_mode
) {
1203 offset_type bytes
= lines
* view
->bytes_per_line
;
1204 if (view
->hex_cursor
>= bytes
) {
1205 view
->hex_cursor
-= bytes
;
1206 if (view
->hex_cursor
< view
->dpy_start
)
1207 view
->dpy_start
= offset_doz (view
->dpy_start
, bytes
);
1209 view
->hex_cursor
%= view
->bytes_per_line
;
1211 } else if (view
->text_wrap_mode
) {
1212 const screen_dimen width
= view
->data_area
.width
;
1213 offset_type i
, col
, line
, linestart
;
1215 for (i
= 0; i
< lines
; i
++) {
1216 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1219 } else if (line
>= 1) {
1220 view_coord_to_offset (view
, &linestart
, line
, 0);
1221 view_offset_to_coord (view
, &line
, &col
, linestart
- 1);
1223 /* if the only thing that would be displayed were a
1224 * single newline character, advance to the previous
1225 * part of the line. */
1226 if (col
> 0 && col
% width
== 0)
1233 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1236 offset_type line
, column
;
1238 view_offset_to_coord (view
, &line
, &column
, view
->dpy_start
);
1239 line
= offset_doz(line
, lines
);
1240 view_coord_to_offset (view
, &(view
->dpy_start
), line
, column
);
1242 view_movement_fixups (view
, (lines
!= 1));
1246 view_move_down (WView
*view
, offset_type lines
)
1248 if (view
->hex_mode
) {
1249 offset_type i
, limit
, last_byte
;
1251 last_byte
= view_get_filesize (view
);
1252 if (last_byte
>= (offset_type
) view
->bytes_per_line
)
1253 limit
= last_byte
- view
->bytes_per_line
;
1256 for (i
= 0; i
< lines
&& view
->hex_cursor
< limit
; i
++) {
1257 view
->hex_cursor
+= view
->bytes_per_line
;
1259 view
->dpy_start
+= view
->bytes_per_line
;
1262 } else if (view
->dpy_end
== view_get_filesize (view
)) {
1263 /* don't move further down. There's nothing more to see. */
1265 } else if (view
->text_wrap_mode
) {
1266 offset_type line
, col
, i
;
1268 for (i
= 0; i
< lines
; i
++) {
1269 offset_type new_offset
, chk_line
, chk_col
;
1271 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1272 col
+= view
->data_area
.width
;
1273 view_coord_to_offset (view
, &new_offset
, line
, col
);
1275 /* skip to the next line if the only thing that would be
1276 * displayed is the newline character. */
1277 view_offset_to_coord (view
, &chk_line
, &chk_col
, new_offset
);
1278 if (chk_line
== line
&& chk_col
== col
1279 && get_byte (view
, new_offset
) == '\n')
1282 view
->dpy_start
= new_offset
;
1286 offset_type line
, col
;
1288 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1290 view_coord_to_offset (view
, &(view
->dpy_start
), line
, col
);
1292 view_movement_fixups (view
, (lines
!= 1));
1296 view_move_left (WView
*view
, offset_type columns
)
1298 if (view
->hex_mode
) {
1299 assert (columns
== 1);
1300 if (view
->hexview_in_text
|| !view
->hexedit_lownibble
) {
1301 if (view
->hex_cursor
> 0)
1304 if (!view
->hexview_in_text
)
1305 view
->hexedit_lownibble
= !view
->hexedit_lownibble
;
1306 } else if (view
->text_wrap_mode
) {
1309 if (view
->dpy_text_column
>= columns
)
1310 view
->dpy_text_column
-= columns
;
1312 view
->dpy_text_column
= 0;
1314 view_movement_fixups (view
, FALSE
);
1318 view_move_right (WView
*view
, offset_type columns
)
1320 if (view
->hex_mode
) {
1321 assert (columns
== 1);
1322 if (view
->hexview_in_text
|| view
->hexedit_lownibble
) {
1323 if (get_byte_indexed (view
, view
->hex_cursor
, 1) != -1)
1326 if (!view
->hexview_in_text
)
1327 view
->hexedit_lownibble
= !view
->hexedit_lownibble
;
1328 } else if (view
->text_wrap_mode
) {
1331 view
->dpy_text_column
+= columns
;
1333 view_movement_fixups (view
, FALSE
);
1336 /* {{{ Toggling of viewer modes }}} */
1339 view_toggle_hex_mode (WView
*view
)
1341 view
->hex_mode
= !view
->hex_mode
;
1343 if (view
->hex_mode
) {
1344 view
->hex_cursor
= view
->dpy_start
;
1346 offset_rounddown (view
->dpy_start
, view
->bytes_per_line
);
1347 view
->widget
.options
|= W_WANT_CURSOR
;
1349 view
->dpy_start
= view
->hex_cursor
;
1350 view_moveto_bol (view
);
1351 view
->widget
.options
&= ~W_WANT_CURSOR
;
1353 altered_hex_mode
= 1;
1354 view
->dpy_bbar_dirty
= TRUE
;
1359 view_toggle_hexedit_mode (WView
*view
)
1361 view
->hexedit_mode
= !view
->hexedit_mode
;
1362 view
->dpy_bbar_dirty
= TRUE
;
1367 view_toggle_wrap_mode (WView
*view
)
1369 view
->text_wrap_mode
= !view
->text_wrap_mode
;
1370 if (view
->text_wrap_mode
) {
1371 view_scroll_to_cursor (view
);
1375 view_offset_to_coord (view
, &line
, &(view
->dpy_text_column
), view
->dpy_start
);
1376 view_coord_to_offset (view
, &(view
->dpy_start
), line
, 0);
1378 view
->dpy_bbar_dirty
= TRUE
;
1383 view_toggle_nroff_mode (WView
*view
)
1385 view
->text_nroff_mode
= !view
->text_nroff_mode
;
1386 altered_nroff_flag
= 1;
1387 view
->dpy_bbar_dirty
= TRUE
;
1392 view_toggle_magic_mode (WView
*view
)
1394 char *filename
, *command
;
1396 altered_magic_flag
= 1;
1397 view
->magic_mode
= !view
->magic_mode
;
1398 filename
= g_strdup (view
->filename
);
1399 command
= g_strdup (view
->command
);
1402 view_load (view
, command
, filename
, 0);
1405 view
->dpy_bbar_dirty
= TRUE
;
1409 /* {{{ Miscellaneous functions }}} */
1412 view_done (WView
*view
)
1414 /* Save current file position */
1415 if (mcview_remember_file_position
&& view
->filename
!= NULL
) {
1417 offset_type line
, col
;
1419 canon_fname
= vfs_canon (view
->filename
);
1420 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1421 save_file_position (canon_fname
, line
+ 1, col
);
1422 g_free (canon_fname
);
1425 /* Write back the global viewer mode */
1426 default_hex_mode
= view
->hex_mode
;
1427 default_nroff_flag
= view
->text_nroff_mode
;
1428 default_magic_flag
= view
->magic_mode
;
1429 global_wrap_mode
= view
->text_wrap_mode
;
1431 /* Free memory used by the viewer */
1433 /* view->widget needs no destructor */
1435 g_free (view
->filename
), view
->filename
= NULL
;
1436 g_free (view
->command
), view
->command
= NULL
;
1438 view_close_datasource (view
);
1439 /* the growing buffer is freed with the datasource */
1441 if (view
->coord_cache
) {
1442 g_array_free (view
->coord_cache
, TRUE
), view
->coord_cache
= NULL
;
1445 view_hexedit_free_change_list (view
);
1446 /* FIXME: what about view->search_exp? */
1450 view_show_error (WView
*view
, const char *msg
)
1452 view_close_datasource (view
);
1453 if (view_is_in_panel (view
)) {
1454 view_set_datasource_string (view
, msg
);
1456 message (D_ERROR
, MSG_ERROR
, "%s", msg
);
1461 view_load_command_output (WView
*view
, const char *command
)
1465 view_close_datasource (view
);
1468 if ((fp
= popen (command
, "r")) == NULL
) {
1469 /* Avoid two messages. Message from stderr has priority. */
1471 if (!close_error_pipe (view_is_in_panel (view
) ? -1 : D_ERROR
, NULL
))
1472 view_show_error (view
, _(" Cannot spawn child process "));
1476 /* First, check if filter produced any output */
1477 view_set_datasource_stdio_pipe (view
, fp
);
1478 if (get_byte (view
, 0) == -1) {
1479 view_close_datasource (view
);
1481 /* Avoid two messages. Message from stderr has priority. */
1483 if (!close_error_pipe (view_is_in_panel (view
) ? -1 : D_ERROR
, NULL
))
1484 view_show_error (view
, _("Empty output from child filter"));
1491 view_load (WView
*view
, const char *command
, const char *file
,
1496 char tmp
[BUF_MEDIUM
];
1498 gboolean retval
= FALSE
;
1500 assert (view
->bytes_per_line
!= 0);
1503 /* Set up the state */
1504 view_set_datasource_none (view
);
1505 view
->filename
= g_strdup (file
);
1508 /* Clear the markers */
1510 for (i
= 0; i
< 10; i
++)
1513 if (!view_is_in_panel (view
)) {
1514 view
->dpy_text_column
= 0;
1517 if (command
&& (view
->magic_mode
|| file
== NULL
|| file
[0] == '\0')) {
1518 retval
= view_load_command_output (view
, command
);
1519 } else if (file
!= NULL
&& file
[0] != '\0') {
1521 if ((fd
= mc_open (file
, O_RDONLY
| O_NONBLOCK
)) == -1) {
1522 snprintf (tmp
, sizeof (tmp
), _(" Cannot open \"%s\"\n %s "),
1523 file
, unix_error_string (errno
));
1524 view_show_error (view
, tmp
);
1528 /* Make sure we are working with a regular file */
1529 if (mc_fstat (fd
, &st
) == -1) {
1531 snprintf (tmp
, sizeof (tmp
), _(" Cannot stat \"%s\"\n %s "),
1532 file
, unix_error_string (errno
));
1533 view_show_error (view
, tmp
);
1537 if (!S_ISREG (st
.st_mode
)) {
1539 view_show_error (view
, _(" Cannot view: not a regular file "));
1543 if (st
.st_size
== 0 || mc_lseek (fd
, 0, SEEK_SET
) == -1) {
1544 /* Must be one of those nice files that grow (/proc) */
1545 view_set_datasource_vfs_pipe (view
, fd
);
1547 type
= get_compression_type (fd
);
1549 if (view
->magic_mode
&& (type
!= COMPRESSION_NONE
)) {
1550 g_free (view
->filename
);
1551 view
->filename
= g_strconcat (file
, decompress_extension (type
), (char *) NULL
);
1553 view_set_datasource_file (view
, fd
, &st
);
1559 view
->command
= g_strdup (command
);
1560 view
->dpy_start
= 0;
1561 view
->search_start
= 0;
1562 view
->search_length
= 0;
1563 view
->dpy_text_column
= 0;
1564 view
->last_search
= 0; /* Start a new search */
1566 assert (view
->bytes_per_line
!= 0);
1567 if (mcview_remember_file_position
&& file
!= NULL
&& start_line
== 0) {
1571 canon_fname
= vfs_canon (file
);
1572 load_file_position (file
, &line
, &col
);
1573 g_free (canon_fname
);
1574 view_moveto (view
, offset_doz(line
, 1), col
);
1575 } else if (start_line
> 0) {
1576 view_moveto (view
, start_line
- 1, 0);
1579 view
->hexedit_lownibble
= FALSE
;
1580 view
->hexview_in_text
= FALSE
;
1581 view
->change_list
= NULL
;
1586 /* {{{ Display management }}} */
1589 view_update_bytes_per_line (WView
*view
)
1591 const screen_dimen cols
= view
->data_area
.width
;
1597 bytes
= 4 * ((cols
- 8) / ((cols
< 80) ? 17 : 18));
1600 view
->bytes_per_line
= bytes
;
1601 view
->dirty
= max_dirt_limit
+ 1; /* To force refresh */
1605 view_percent (WView
*view
, offset_type p
)
1607 const screen_dimen top
= view
->status_area
.top
;
1608 const screen_dimen right
= view
->status_area
.left
+ view
->status_area
.width
;
1609 const screen_dimen height
= view
->status_area
.height
;
1611 offset_type filesize
;
1613 if (height
< 1 || right
< 4)
1615 if (view_may_still_grow (view
))
1617 filesize
= view_get_filesize (view
);
1619 if (filesize
== 0 || view
->dpy_end
== filesize
)
1621 else if (p
> (INT_MAX
/ 100))
1622 percent
= p
/ (filesize
/ 100);
1624 percent
= p
* 100 / filesize
;
1626 widget_move (view
, top
, right
- 4);
1627 tty_printf ("%3d%%", percent
);
1631 view_display_status (WView
*view
)
1633 const screen_dimen top
= view
->status_area
.top
;
1634 const screen_dimen left
= view
->status_area
.left
;
1635 const screen_dimen width
= view
->status_area
.width
;
1636 const screen_dimen height
= view
->status_area
.height
;
1637 const char *file_label
, *file_name
;
1638 screen_dimen file_label_width
;
1644 tty_setcolor (SELECTED_COLOR
);
1645 widget_move (view
, top
, left
);
1648 file_label
= _("File: %s");
1649 file_label_width
= strlen (file_label
) - 2;
1650 file_name
= view
->filename
? view
->filename
1651 : view
->command
? view
->command
1654 if (width
< file_label_width
+ 6)
1655 addstr ((char *) name_trunc (file_name
, width
));
1657 i
= (width
> 22 ? 22 : width
) - file_label_width
;
1658 tty_printf (file_label
, name_trunc (file_name
, i
));
1660 widget_move (view
, top
, left
+ 24);
1661 /* FIXME: the format strings need to be changed when offset_type changes */
1663 tty_printf (_("Offset 0x%08lx"), (unsigned long) view
->hex_cursor
);
1665 offset_type line
, col
;
1666 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
1667 tty_printf (_("Line %lu Col %lu"),
1668 (unsigned long) line
+ 1,
1669 (unsigned long) (view
->text_wrap_mode
? col
: view
->dpy_text_column
));
1673 offset_type filesize
;
1674 filesize
= view_get_filesize (view
);
1675 widget_move (view
, top
, left
+ 43);
1676 if (!view_may_still_grow (view
)) {
1677 tty_printf (_("%s bytes"), size_trunc (filesize
));
1679 tty_printf (_(">= %s bytes"), size_trunc (filesize
));
1683 view_percent (view
, view
->hex_mode
1688 tty_setcolor (SELECTED_COLOR
);
1692 view_display_clean (WView
*view
)
1694 tty_setcolor (NORMAL_COLOR
);
1695 widget_erase ((Widget
*) view
);
1696 if (view
->dpy_frame_size
!= 0) {
1697 draw_double_box (view
->widget
.parent
, view
->widget
.y
,
1698 view
->widget
.x
, view
->widget
.lines
,
1711 view_count_backspaces (WView
*view
, off_t offset
)
1714 while (offset
>= 2 * backspaces
1715 && get_byte (view
, offset
- 2 * backspaces
) == '\b')
1721 view_display_ruler (WView
*view
)
1723 static const char ruler_chars
[] = "|----*----";
1724 const screen_dimen top
= view
->ruler_area
.top
;
1725 const screen_dimen left
= view
->ruler_area
.left
;
1726 const screen_dimen width
= view
->ruler_area
.width
;
1727 const screen_dimen height
= view
->ruler_area
.height
;
1728 const screen_dimen line_row
= (ruler
== RULER_TOP
) ? 0 : 1;
1729 const screen_dimen nums_row
= (ruler
== RULER_TOP
) ? 1 : 0;
1735 if (ruler
== RULER_NONE
|| height
< 1)
1738 tty_setcolor (MARKED_COLOR
);
1739 for (c
= 0; c
< width
; c
++) {
1740 cl
= view
->dpy_text_column
+ c
;
1741 if (line_row
< height
) {
1742 widget_move (view
, top
+ line_row
, left
+ c
);
1743 tty_print_char (ruler_chars
[cl
% 10]);
1746 if ((cl
!= 0) && (cl
% 10) == 0) {
1747 snprintf (r_buff
, sizeof (r_buff
), "%"OFFSETTYPE_PRId
, cl
);
1748 if (nums_row
< height
) {
1749 widget_move (view
, top
+ nums_row
, left
+ c
- 1);
1750 tty_print_string (r_buff
);
1754 attrset (NORMAL_COLOR
);
1758 view_display_hex (WView
*view
)
1760 const screen_dimen top
= view
->data_area
.top
;
1761 const screen_dimen left
= view
->data_area
.left
;
1762 const screen_dimen height
= view
->data_area
.height
;
1763 const screen_dimen width
= view
->data_area
.width
;
1764 const int ngroups
= view
->bytes_per_line
/ 4;
1765 const screen_dimen text_start
=
1766 8 + 13 * ngroups
+ ((width
< 80) ? 0 : (ngroups
- 1 + 1));
1767 /* 8 characters are used for the file offset, and every hex group
1768 * takes 13 characters. On ``big'' screens, the groups are separated
1769 * by an extra vertical line, and there is an extra space before the
1773 screen_dimen row
, col
;
1776 mark_t boldflag
= MARK_NORMAL
;
1777 struct hexedit_change_node
*curr
= view
->change_list
;
1780 char hex_buff
[10]; /* A temporary buffer for sprintf and mvwaddstr */
1781 int bytes
; /* Number of bytes already printed on the line */
1783 view_display_clean (view
);
1785 /* Find the first displayable changed byte */
1786 from
= view
->dpy_start
;
1787 while (curr
&& (curr
->offset
< from
)) {
1791 for (row
= 0; get_byte (view
, from
) != -1 && row
< height
; row
++) {
1794 /* Print the hex offset */
1795 snprintf (hex_buff
, sizeof (hex_buff
), "%08"OFFSETTYPE_PRIX
" ", from
);
1796 widget_move (view
, top
+ row
, left
);
1797 tty_setcolor (MARKED_COLOR
);
1798 for (i
= 0; col
< width
&& hex_buff
[i
] != '\0'; i
++) {
1799 tty_print_char(hex_buff
[i
]);
1802 tty_setcolor (NORMAL_COLOR
);
1804 for (bytes
= 0; bytes
< view
->bytes_per_line
; bytes
++, from
++) {
1806 if ((c
= get_byte (view
, from
)) == -1)
1809 /* Save the cursor position for view_place_cursor() */
1810 if (from
== view
->hex_cursor
&& !view
->hexview_in_text
) {
1811 view
->cursor_row
= row
;
1812 view
->cursor_col
= col
;
1815 /* Determine the state of the current byte */
1817 (from
== view
->hex_cursor
) ? MARK_CURSOR
1818 : (curr
!= NULL
&& from
== curr
->offset
) ? MARK_CHANGED
1819 : (view
->search_start
<= from
&&
1820 from
< view
->search_start
+ view
->search_length
1824 /* Determine the value of the current byte */
1825 if (curr
!= NULL
&& from
== curr
->offset
) {
1830 /* Select the color for the hex number */
1832 boldflag
== MARK_NORMAL
? NORMAL_COLOR
:
1833 boldflag
== MARK_SELECTED
? MARKED_COLOR
:
1834 boldflag
== MARK_CHANGED
? VIEW_UNDERLINED_COLOR
:
1835 /* boldflag == MARK_CURSOR */
1836 view
->hexview_in_text
? MARKED_SELECTED_COLOR
:
1837 VIEW_UNDERLINED_COLOR
);
1839 /* Print the hex number */
1840 widget_move (view
, top
+ row
, left
+ col
);
1842 tty_print_char (hex_char
[c
/ 16]);
1846 tty_print_char (hex_char
[c
% 16]);
1850 /* Print the separator */
1851 tty_setcolor (NORMAL_COLOR
);
1852 if (bytes
!= view
->bytes_per_line
- 1) {
1854 tty_print_char (' ');
1858 /* After every four bytes, print a group separator */
1859 if (bytes
% 4 == 3) {
1860 if (view
->data_area
.width
>= 80 && col
< width
) {
1861 tty_print_one_vline ();
1865 tty_print_char (' ');
1871 /* Select the color for the character; this differs from the
1872 * hex color when boldflag == MARK_CURSOR */
1874 boldflag
== MARK_NORMAL
? NORMAL_COLOR
:
1875 boldflag
== MARK_SELECTED
? MARKED_COLOR
:
1876 boldflag
== MARK_CHANGED
? VIEW_UNDERLINED_COLOR
:
1877 /* boldflag == MARK_CURSOR */
1878 view
->hexview_in_text
? VIEW_UNDERLINED_COLOR
:
1879 MARKED_SELECTED_COLOR
);
1881 c
= convert_to_display_c (c
);
1882 if (!is_printable (c
))
1885 /* Print corresponding character on the text side */
1886 if (text_start
+ bytes
< width
) {
1887 widget_move (view
, top
+ row
, left
+ text_start
+ bytes
);
1891 /* Save the cursor position for view_place_cursor() */
1892 if (from
== view
->hex_cursor
&& view
->hexview_in_text
) {
1893 view
->cursor_row
= row
;
1894 view
->cursor_col
= text_start
+ bytes
;
1899 /* Be polite to the other functions */
1900 tty_setcolor (NORMAL_COLOR
);
1902 view_place_cursor (view
);
1903 view
->dpy_end
= from
;
1907 view_display_text (WView
* view
)
1909 const screen_dimen left
= view
->data_area
.left
;
1910 const screen_dimen top
= view
->data_area
.top
;
1911 const screen_dimen width
= view
->data_area
.width
;
1912 const screen_dimen height
= view
->data_area
.height
;
1913 screen_dimen row
, col
;
1916 struct hexedit_change_node
*curr
= view
->change_list
;
1918 view_display_clean (view
);
1919 view_display_ruler (view
);
1921 /* Find the first displayable changed byte */
1922 from
= view
->dpy_start
;
1923 while (curr
&& (curr
->offset
< from
)) {
1927 tty_setcolor (NORMAL_COLOR
);
1928 for (row
= 0, col
= 0; row
< height
&& (c
= get_byte (view
, from
)) != -1; from
++) {
1930 if (view
->text_nroff_mode
&& c
== '\b') {
1934 if ((c_next
= get_byte_indexed (view
, from
, 1)) != -1
1935 && is_printable (c_next
)
1937 && (c_prev
= get_byte (view
, from
- 1)) != -1
1938 && is_printable (c_prev
)
1939 && (c_prev
== c_next
|| c_prev
== '_'
1940 || (c_prev
== '+' && c_next
== 'o'))) {
1943 /* We're inside an nroff character sequence at the
1944 * beginning of the screen -- just skip the
1945 * backspace and continue with the next character. */
1952 if (c_prev
== '_' && (c_next
!= '_' || view_count_backspaces (view
, from
) == 1))
1953 tty_setcolor (VIEW_UNDERLINED_COLOR
);
1955 tty_setcolor (MARKED_COLOR
);
1960 if ((c
== '\n') || (col
>= width
&& view
->text_wrap_mode
)) {
1963 if (c
== '\n' || row
>= height
)
1968 c
= get_byte_indexed(view
, from
, 1);
1969 if (c
== '\r' || c
== '\n')
1977 offset_type line
, column
;
1978 view_offset_to_coord (view
, &line
, &column
, from
);
1979 col
+= (8 - column
% 8);
1980 if (view
->text_wrap_mode
&& col
>= width
&& width
!= 0) {
1987 if (view
->search_start
<= from
1988 && from
< view
->search_start
+ view
->search_length
) {
1989 tty_setcolor (SELECTED_COLOR
);
1992 if (col
>= view
->dpy_text_column
1993 && col
- view
->dpy_text_column
< width
) {
1994 widget_move (view
, top
+ row
, left
+ (col
- view
->dpy_text_column
));
1995 c
= convert_to_display_c (c
);
1996 if (!is_printable (c
))
2001 tty_setcolor (NORMAL_COLOR
);
2003 view
->dpy_end
= from
;
2006 /* Displays as much data from view->dpy_start as fits on the screen */
2008 display (WView
*view
)
2010 view_compute_areas (view
);
2011 if (view
->hex_mode
) {
2012 view_display_hex (view
);
2014 view_display_text (view
);
2016 view_display_status (view
);
2020 view_place_cursor (WView
*view
)
2022 const screen_dimen top
= view
->data_area
.top
;
2023 const screen_dimen left
= view
->data_area
.left
;
2026 col
= view
->cursor_col
;
2027 if (!view
->hexview_in_text
&& view
->hexedit_lownibble
)
2029 widget_move (&view
->widget
, top
+ view
->cursor_row
, left
+ col
);
2033 view_update (WView
*view
)
2035 static int dirt_limit
= 1;
2037 if (view
->dpy_bbar_dirty
) {
2038 view
->dpy_bbar_dirty
= FALSE
;
2040 buttonbar_redraw (view
->widget
.parent
);
2043 if (view
->dirty
> dirt_limit
) {
2044 /* Too many updates skipped -> force a update */
2047 /* Raise the update skipping limit */
2049 if (dirt_limit
> max_dirt_limit
)
2050 dirt_limit
= max_dirt_limit
;
2054 /* We have time to update the screen properly */
2060 /* We are busy -> skipping full update,
2061 only the status line is updated */
2062 view_display_status (view
);
2064 /* Here we had a refresh, if fast scrolling does not work
2065 restore the refresh, although this should not happen */
2069 /* {{{ Hex editor }}} */
2072 enqueue_change (struct hexedit_change_node
**head
,
2073 struct hexedit_change_node
*node
)
2075 /* chnode always either points to the head of the list or
2076 * to one of the ->next fields in the list. The value at
2077 * this location will be overwritten with the new node. */
2078 struct hexedit_change_node
**chnode
= head
;
2080 while (*chnode
!= NULL
&& (*chnode
)->offset
< node
->offset
)
2081 chnode
= &((*chnode
)->next
);
2083 node
->next
= *chnode
;
2088 view_handle_editkey (WView
*view
, int key
)
2090 struct hexedit_change_node
*node
;
2093 /* Has there been a change at this position? */
2094 node
= view
->change_list
;
2095 while (node
&& (node
->offset
!= view
->hex_cursor
))
2098 if (!view
->hexview_in_text
) {
2100 unsigned int hexvalue
= 0;
2102 if (key
>= '0' && key
<= '9')
2103 hexvalue
= 0 + (key
- '0');
2104 else if (key
>= 'A' && key
<= 'F')
2105 hexvalue
= 10 + (key
- 'A');
2106 else if (key
>= 'a' && key
<= 'f')
2107 hexvalue
= 10 + (key
- 'a');
2109 return MSG_NOT_HANDLED
;
2112 byte_val
= node
->value
;
2114 byte_val
= get_byte (view
, view
->hex_cursor
);
2116 if (view
->hexedit_lownibble
) {
2117 byte_val
= (byte_val
& 0xf0) | (hexvalue
);
2119 byte_val
= (byte_val
& 0x0f) | (hexvalue
<< 4);
2123 if (key
< 256 && (is_printable (key
) || (key
== '\n')))
2126 return MSG_NOT_HANDLED
;
2129 node
= g_new (struct hexedit_change_node
, 1);
2130 node
->offset
= view
->hex_cursor
;
2131 node
->value
= byte_val
;
2132 enqueue_change (&view
->change_list
, node
);
2134 node
->value
= byte_val
;
2138 view_move_right (view
, 1);
2143 view_hexedit_save_changes (WView
*view
)
2145 struct hexedit_change_node
*curr
, *next
;
2149 if (view
->change_list
== NULL
)
2153 assert (view
->filename
!= NULL
);
2154 fp
= mc_open (view
->filename
, O_WRONLY
);
2158 for (curr
= view
->change_list
; curr
!= NULL
; curr
= next
) {
2161 if (mc_lseek (fp
, curr
->offset
, SEEK_SET
) == -1
2162 || mc_write (fp
, &(curr
->value
), 1) != 1)
2165 /* delete the saved item from the change list */
2166 view
->change_list
= next
;
2168 view_set_byte (view
, curr
->offset
, curr
->value
);
2172 if (mc_close (fp
) == -1) {
2173 error
= g_strdup (strerror (errno
));
2174 message (D_ERROR
, _(" Save file "),
2175 _(" Error while closing the file: \n %s \n"
2176 " Data may have been written or not. "), error
);
2183 error
= g_strdup (strerror (errno
));
2184 text
= g_strdup_printf (_(" Cannot save file: \n %s "), error
);
2186 (void) mc_close (fp
);
2188 answer
= query_dialog (_(" Save file "), text
, D_ERROR
,
2189 2, _("&Retry"), _("&Cancel"));
2197 /* {{{ Miscellaneous functions }}} */
2200 view_ok_to_quit (WView
*view
)
2204 if (view
->change_list
== NULL
)
2207 r
= query_dialog (_("Quit"),
2208 _(" File was modified, Save with exit? "), D_NORMAL
, 3,
2209 _("&Cancel quit"), _("&Yes"), _("&No"));
2213 return view_hexedit_save_changes (view
);
2215 view_hexedit_free_change_list (view
);
2223 my_define (Dlg_head
*h
, int idx
, const char *text
, void (*fn
) (WView
*),
2226 buttonbar_set_label_data (h
, idx
, text
, (buttonbarfn
) fn
, view
);
2229 /* {{{ Searching }}} */
2231 /* Case insensitive search of text in data */
2233 icase_search_p (WView
*view
, char *text
, char *data
, int nothing
)
2237 const int direction
= view
->direction
;
2241 /* If we are searching backwards, reverse the string */
2242 if (direction
== -1) {
2243 g_strreverse (text
);
2244 g_strreverse (data
);
2247 q
= _icase_search (text
, data
, &lng
);
2249 if (direction
== -1) {
2250 g_strreverse (text
);
2251 g_strreverse (data
);
2256 view
->search_start
= q
- data
- lng
;
2258 view
->search_start
= strlen (data
) - (q
- data
);
2259 view
->search_length
= lng
;
2266 grow_string_buffer (char *text
, gulong
*size
)
2270 /* The grow steps */
2272 new = g_realloc (text
, *size
);
2280 get_line_at (WView
*view
, offset_type
*p
, offset_type
*skipped
)
2282 char *buffer
= NULL
;
2283 gulong buffer_size
= 0;
2284 offset_type usable_size
= 0;
2286 const int direction
= view
->direction
;
2287 offset_type pos
= *p
;
2293 if (pos
== 0 && direction
== -1)
2296 /* skip over all the possible zeros in the file */
2297 while ((ch
= get_byte (view
, pos
)) == 0) {
2298 if (pos
== 0 && direction
== -1)
2305 if (i
== 0 && (pos
!= 0 || direction
== -1)) {
2306 prev
= get_byte (view
, pos
- direction
);
2307 if ((prev
== -1) || (prev
== '\n'))
2311 for (i
= 1; ch
!= -1; ch
= get_byte (view
, pos
)) {
2312 if (i
>= usable_size
) {
2313 buffer
= grow_string_buffer (buffer
, &buffer_size
);
2314 usable_size
= buffer_size
- 2; /* prev & null terminator */
2319 if (pos
== 0 && direction
== -1)
2324 if (ch
== '\n' || ch
== '\0') {
2325 i
--; /* Strip newline/zero */
2334 /* If we are searching backwards, reverse the string */
2335 if (direction
== -1) {
2336 g_strreverse (buffer
+ 1);
2345 search_update_steps (WView
*view
)
2347 offset_type filesize
= view_get_filesize (view
);
2349 view
->update_steps
= 40000;
2350 else /* viewing a data stream, not a file */
2351 view
->update_steps
= filesize
/ 100;
2353 /* Do not update the percent display but every 20 ks */
2354 if (view
->update_steps
< 20000)
2355 view
->update_steps
= 20000;
2359 search (WView
*view
, char *text
,
2360 int (*search
) (WView
*, char *, char *, int))
2362 char *s
= NULL
; /* The line we read from the view buffer */
2363 offset_type p
, beginning
, search_start
;
2368 /* Used to keep track of where the line starts, when looking forward
2369 * is the index before transfering the line; the reverse case uses
2370 * the position returned after the line has been read */
2371 offset_type forward_line_start
;
2372 offset_type reverse_line_start
;
2376 d
= create_message (D_NORMAL
, _("Search"), _("Searching %s"), text
);
2380 found_len
= view
->search_length
;
2381 search_start
= view
->search_start
;
2383 if (view
->direction
== 1) {
2384 p
= search_start
+ ((found_len
) ? 1 : 0);
2386 p
= search_start
- ((found_len
&& search_start
>= 1) ? 1 : 0);
2390 /* Compute the percent steps */
2391 search_update_steps (view
);
2392 view
->update_activate
= 0;
2394 enable_interrupt_key ();
2395 for (;; g_free (s
)) {
2396 if (p
>= view
->update_activate
) {
2397 view
->update_activate
+= view
->update_steps
;
2399 view_percent (view
, p
);
2402 if (got_interrupt ())
2405 forward_line_start
= p
;
2406 s
= get_line_at (view
, &p
, &t
);
2407 reverse_line_start
= p
;
2412 search_status
= (*search
) (view
, text
, s
+ 1, match_normal
);
2413 if (search_status
< 0) {
2418 if (search_status
== 0)
2421 /* We found the string */
2423 /* Handle ^ and $ when regexp search starts at the middle of the line */
2424 if (*s
&& !view
->search_start
&& (search
== regexp_view_search
)) {
2425 if ((*text
== '^' && view
->direction
== 1)
2426 || (view
->direction
== -1 && text
[strlen (text
) - 1] == '$')
2431 /* Record the position used to continue the search */
2432 if (view
->direction
== 1)
2433 t
+= forward_line_start
;
2435 t
= reverse_line_start
? reverse_line_start
+ 2 : 0;
2436 view
->search_start
+= t
;
2438 if (t
!= beginning
) {
2439 view
->dpy_start
= t
;
2445 disable_interrupt_key ();
2451 message (D_NORMAL
, _("Search"), _(" Search string not found "));
2452 view
->search_length
= 0;
2456 /* Search buffer (its size is len) in the complete buffer
2457 * returns the position where the block was found or INVALID_OFFSET
2460 block_search (WView
*view
, const char *buffer
, int len
)
2462 int direction
= view
->direction
;
2463 const char *d
= buffer
;
2467 enable_interrupt_key ();
2469 e
= view
->search_start
+ ((view
->search_length
) ? 1 : 0);
2471 e
= view
->search_start
2472 - ((view
->search_length
&& view
->search_start
>= 1) ? 1 : 0);
2474 search_update_steps (view
);
2475 view
->update_activate
= 0;
2477 if (direction
== -1) {
2478 for (d
+= len
- 1;; e
--) {
2479 if (e
<= view
->update_activate
) {
2480 view
->update_activate
-= view
->update_steps
;
2482 view_percent (view
, e
);
2485 if (got_interrupt ())
2488 b
= get_byte (view
, e
);
2492 disable_interrupt_key ();
2497 e
+= buffer
+ len
- 1 - d
;
2498 d
= buffer
+ len
- 1;
2504 while (get_byte (view
, e
) != -1) {
2505 if (e
>= view
->update_activate
) {
2506 view
->update_activate
+= view
->update_steps
;
2508 view_percent (view
, e
);
2511 if (got_interrupt ())
2514 b
= get_byte (view
, e
++);
2518 if (d
- buffer
== len
) {
2519 disable_interrupt_key ();
2528 disable_interrupt_key ();
2529 return INVALID_OFFSET
;
2533 * Search in the hex mode. Supported input:
2534 * - numbers (oct, dec, hex). Each of them matches one byte.
2535 * - strings in double quotes. Matches exactly without quotes.
2538 hex_search (WView
*view
, const char *text
)
2540 char *buffer
; /* Parsed search string */
2541 char *cur
; /* Current position in it */
2542 int block_len
; /* Length of the search string */
2543 offset_type pos
; /* Position of the string in the file */
2544 int parse_error
= 0;
2547 view
->search_length
= 0;
2551 /* buffer will never be longer that text */
2552 buffer
= g_new (char, strlen (text
));
2555 /* First convert the string to a stream of bytes */
2560 /* Skip leading spaces */
2561 if (*text
== ' ' || *text
== '\t') {
2566 /* %i matches octal, decimal, and hexadecimal numbers */
2567 if (sscanf (text
, "%i%n", &val
, &ptr
) > 0) {
2568 /* Allow signed and unsigned char in the user input */
2569 if (val
< -128 || val
> 255) {
2574 *cur
++ = (char) val
;
2579 /* Try quoted string, strip quotes */
2581 const char *next_quote
;
2584 next_quote
= strchr (text
, '"');
2586 memcpy (cur
, text
, next_quote
- text
);
2587 cur
+= next_quote
- text
;
2588 text
= next_quote
+ 1;
2598 block_len
= cur
- buffer
;
2600 /* No valid bytes in the user input */
2601 if (block_len
<= 0 || parse_error
) {
2602 message (D_NORMAL
, _("Search"), _("Invalid hex search expression"));
2604 view
->search_length
= 0;
2608 /* Then start the search */
2609 pos
= block_search (view
, buffer
, block_len
);
2613 if (pos
== INVALID_OFFSET
) {
2614 message (D_NORMAL
, _("Search"), _(" Search string not found "));
2615 view
->search_length
= 0;
2619 view
->search_start
= pos
;
2620 view
->search_length
= block_len
;
2621 /* Set the edit cursor to the search position, left nibble */
2622 view
->hex_cursor
= view
->search_start
;
2623 view
->hexedit_lownibble
= FALSE
;
2625 /* Adjust the file offset */
2626 view
->dpy_start
= pos
- pos
% view
->bytes_per_line
;
2630 regexp_view_search (WView
*view
, char *pattern
, char *string
,
2634 static char *old_pattern
= NULL
;
2635 static int old_type
;
2636 regmatch_t pmatch
[1];
2637 int i
, flags
= REG_ICASE
;
2639 if (old_pattern
== NULL
|| strcmp (old_pattern
, pattern
) != 0
2640 || old_type
!= match_type
) {
2641 if (old_pattern
!= NULL
) {
2643 g_free (old_pattern
);
2646 for (i
= 0; pattern
[i
] != '\0'; i
++) {
2647 if (isupper ((unsigned char) pattern
[i
])) {
2652 flags
|= REG_EXTENDED
;
2653 if (regcomp (&r
, pattern
, flags
)) {
2654 message (D_ERROR
, MSG_ERROR
, _(" Invalid regular expression "));
2657 old_pattern
= g_strdup (pattern
);
2658 old_type
= match_type
;
2660 if (regexec (&r
, string
, 1, pmatch
, 0) != 0)
2662 view
->search_length
= pmatch
[0].rm_eo
- pmatch
[0].rm_so
;
2663 view
->search_start
= pmatch
[0].rm_so
;
2668 do_regexp_search (WView
*view
)
2670 search (view
, view
->search_exp
, regexp_view_search
);
2671 /* Had a refresh here */
2677 do_normal_search (WView
*view
)
2680 hex_search (view
, view
->search_exp
);
2682 search (view
, view
->search_exp
, icase_search_p
);
2683 /* Had a refresh here */
2688 /* {{{ User-definable commands }}} */
2691 The functions in this section can be bound to hotkeys. They are all
2692 of the same type (taking a pointer to WView as parameter and
2693 returning void). TODO: In the not-too-distant future, these commands
2694 will become fully configurable, like they already are in the
2695 internal editor. By convention, all the function names end in
2700 view_help_cmd (void)
2702 interactive_display (NULL
, "[Internal File Viewer]");
2705 /* Toggle between hexview and hexedit mode */
2707 view_toggle_hexedit_mode_cmd (WView
*view
)
2709 view_toggle_hexedit_mode (view
);
2713 /* Toggle between wrapped and unwrapped view */
2715 view_toggle_wrap_mode_cmd (WView
*view
)
2717 view_toggle_wrap_mode (view
);
2721 /* Toggle between hex view and text view */
2723 view_toggle_hex_mode_cmd (WView
*view
)
2725 view_toggle_hex_mode (view
);
2730 view_moveto_line_cmd (WView
*view
)
2732 char *answer
, *answer_end
, prompt
[BUF_SMALL
];
2733 offset_type line
, col
;
2735 view_offset_to_coord (view
, &line
, &col
, view
->dpy_start
);
2737 snprintf (prompt
, sizeof (prompt
),
2738 _(" The current line number is %d.\n"
2739 " Enter the new line number:"), (int) (line
+ 1));
2740 answer
= input_dialog (_(" Goto line "), prompt
, MC_HISTORY_VIEW_GOTO_LINE
, "");
2741 if (answer
!= NULL
&& answer
[0] != '\0') {
2743 line
= strtoul (answer
, &answer_end
, 10);
2744 if (*answer_end
== '\0' && errno
== 0 && line
>= 1)
2745 view_moveto (view
, line
- 1, 0);
2753 view_moveto_addr_cmd (WView
*view
)
2755 char *line
, *error
, prompt
[BUF_SMALL
];
2758 snprintf (prompt
, sizeof (prompt
),
2759 _(" The current address is 0x%lx.\n"
2760 " Enter the new address:"), view
->hex_cursor
);
2761 line
= input_dialog (_(" Goto Address "), prompt
, MC_HISTORY_VIEW_GOTO_ADDR
, "");
2763 if (*line
!= '\0') {
2764 addr
= strtoul (line
, &error
, 0);
2765 if ((*error
== '\0') && get_byte (view
, addr
) != -1) {
2766 view_moveto_offset (view
, addr
);
2768 message (D_ERROR
, _("Warning"), _(" Invalid address "));
2778 view_hexedit_save_changes_cmd (WView
*view
)
2780 (void) view_hexedit_save_changes (view
);
2783 /* {{{ Searching }}} */
2786 regexp_search (WView
*view
, int direction
)
2790 static char *last_regexp
;
2792 defval
= (last_regexp
!= NULL
? last_regexp
: "");
2794 regexp
= input_dialog (_("Search"), _(" Enter regexp:"), MC_HISTORY_VIEW_SEARCH_REGEX
, defval
);
2795 if (regexp
== NULL
|| regexp
[0] == '\0') {
2800 g_free (last_regexp
);
2801 view
->search_exp
= last_regexp
= regexp
;
2803 view
->direction
= direction
;
2804 do_regexp_search (view
);
2805 view
->last_search
= do_regexp_search
;
2808 /* {{{ User-definable commands }}} */
2811 view_regexp_search_cmd (WView
*view
)
2813 regexp_search (view
, 1);
2818 view_normal_search_cmd (WView
*view
)
2820 char *defval
, *exp
= NULL
;
2821 static char *last_search_string
;
2824 SEARCH_DLG_HEIGHT
= 8,
2825 SEARCH_DLG_WIDTH
= 58
2828 static int replace_backwards
;
2829 int treplace_backwards
= replace_backwards
;
2831 static QuickWidget quick_widgets
[] = {
2832 {quick_button
, 6, 10, 5, SEARCH_DLG_HEIGHT
, N_("&Cancel"), 0,
2835 {quick_button
, 2, 10, 5, SEARCH_DLG_HEIGHT
, N_("&OK"), 0, B_ENTER
,
2837 {quick_checkbox
, 3, SEARCH_DLG_WIDTH
, 4, SEARCH_DLG_HEIGHT
,
2838 N_("&Backwards"), 0, 0,
2840 {quick_input
, 3, SEARCH_DLG_WIDTH
, 3, SEARCH_DLG_HEIGHT
, "", 52, 0,
2841 0, 0, N_("Search")},
2842 {quick_label
, 2, SEARCH_DLG_WIDTH
, 2, SEARCH_DLG_HEIGHT
,
2843 N_(" Enter search string:"), 0, 0,
2847 static QuickDialog Quick_input
= {
2848 SEARCH_DLG_WIDTH
, SEARCH_DLG_HEIGHT
, -1, 0, N_("Search"),
2849 "[Input Line Keys]", quick_widgets
, 0
2852 defval
= g_strdup (last_search_string
!= NULL
? last_search_string
: "");
2853 convert_to_display (defval
);
2855 quick_widgets
[2].result
= &treplace_backwards
;
2856 quick_widgets
[3].str_result
= &exp
;
2857 quick_widgets
[3].text
= defval
;
2859 if (quick_dialog (&Quick_input
) == B_CANCEL
)
2862 replace_backwards
= treplace_backwards
;
2864 if (exp
== NULL
|| exp
[0] == '\0')
2867 convert_from_input (exp
);
2869 g_free (last_search_string
);
2870 view
->search_exp
= last_search_string
= exp
;
2873 view
->direction
= replace_backwards
? -1 : 1;
2874 do_normal_search (view
);
2875 view
->last_search
= do_normal_search
;
2883 view_toggle_magic_mode_cmd (WView
*view
)
2885 view_toggle_magic_mode (view
);
2890 view_toggle_nroff_mode_cmd (WView
*view
)
2892 view_toggle_nroff_mode (view
);
2897 view_quit_cmd (WView
*view
)
2899 if (view_ok_to_quit (view
))
2900 dlg_stop (view
->widget
.parent
);
2903 /* {{{ Miscellaneous functions }}} */
2905 /* Define labels and handlers for functional keys */
2907 view_labels (WView
*view
)
2909 Dlg_head
*h
= view
->widget
.parent
;
2911 buttonbar_set_label (h
, 1, Q_("ButtonBar|Help"), view_help_cmd
);
2913 my_define (h
, 10, Q_("ButtonBar|Quit"), view_quit_cmd
, view
);
2914 my_define (h
, 4, view
->hex_mode
2915 ? Q_("ButtonBar|Ascii")
2916 : Q_("ButtonBar|Hex"),
2917 view_toggle_hex_mode_cmd
, view
);
2918 my_define (h
, 5, view
->hex_mode
2919 ? Q_("ButtonBar|Goto")
2920 : Q_("ButtonBar|Line"),
2921 view
->hex_mode
? view_moveto_addr_cmd
: view_moveto_line_cmd
, view
);
2923 if (view
->hex_mode
) {
2924 if (view
->hexedit_mode
) {
2925 my_define (h
, 2, Q_("ButtonBar|View"),
2926 view_toggle_hexedit_mode_cmd
, view
);
2927 } else if (view
->datasource
== DS_FILE
) {
2928 my_define (h
, 2, Q_("ButtonBar|Edit"),
2929 view_toggle_hexedit_mode_cmd
, view
);
2931 buttonbar_clear_label (h
, 2);
2933 my_define (h
, 6, Q_("ButtonBar|Save"),
2934 view_hexedit_save_changes_cmd
, view
);
2936 my_define (h
, 2, view
->text_wrap_mode
2937 ? Q_("ButtonBar|UnWrap")
2938 : Q_("ButtonBar|Wrap"),
2939 view_toggle_wrap_mode_cmd
, view
);
2940 my_define (h
, 6, Q_("ButtonBar|RxSrch"),
2941 view_regexp_search_cmd
, view
);
2944 my_define (h
, 7, view
->hex_mode
2945 ? Q_("ButtonBar|HxSrch")
2946 : Q_("ButtonBar|Search"),
2947 view_normal_search_cmd
, view
);
2948 my_define (h
, 8, view
->magic_mode
2949 ? Q_("ButtonBar|Raw")
2950 : Q_("ButtonBar|Parse"),
2951 view_toggle_magic_mode_cmd
, view
);
2953 /* don't override the key to access the main menu */
2954 if (!view_is_in_panel (view
)) {
2955 my_define (h
, 9, view
->text_nroff_mode
2956 ? Q_("ButtonBar|Unform")
2957 : Q_("ButtonBar|Format"),
2958 view_toggle_nroff_mode_cmd
, view
);
2959 my_define (h
, 3, Q_("ButtonBar|Quit"), view_quit_cmd
, view
);
2963 /* {{{ Event handling }}} */
2965 /* Check for left and right arrows, possibly with modifiers */
2967 check_left_right_keys (WView
*view
, int c
)
2969 if (c
== KEY_LEFT
) {
2970 view_move_left (view
, 1);
2974 if (c
== KEY_RIGHT
) {
2975 view_move_right (view
, 1);
2979 /* Ctrl with arrows moves by 10 postions in the unwrap mode */
2980 if (view
->hex_mode
|| view
->text_wrap_mode
)
2981 return MSG_NOT_HANDLED
;
2983 if (c
== (KEY_M_CTRL
| KEY_LEFT
)) {
2984 if (view
->dpy_text_column
>= 10)
2985 view
->dpy_text_column
-= 10;
2987 view
->dpy_text_column
= 0;
2992 if (c
== (KEY_M_CTRL
| KEY_RIGHT
)) {
2993 if (view
->dpy_text_column
<= OFFSETTYPE_MAX
- 10)
2994 view
->dpy_text_column
+= 10;
2996 view
->dpy_text_column
= OFFSETTYPE_MAX
;
3001 return MSG_NOT_HANDLED
;
3004 /* {{{ User-definable commands }}} */
3007 view_continue_search_cmd (WView
*view
)
3009 if (view
->last_search
) {
3010 view
->last_search (view
);
3012 /* if not... then ask for an expression */
3013 view_normal_search_cmd (view
);
3018 view_toggle_ruler_cmd (WView
*view
)
3020 static const enum ruler_type next
[3] = {
3026 assert ((size_t) ruler
< 3);
3027 ruler
= next
[(size_t) ruler
];
3031 /* {{{ Event handling }}} */
3033 static void view_cmk_move_up (void *w
, int n
) {
3034 view_move_up ((WView
*) w
, n
);
3036 static void view_cmk_move_down (void *w
, int n
) {
3037 view_move_down ((WView
*) w
, n
);
3039 static void view_cmk_moveto_top (void *w
, int n
) {
3041 view_moveto_top ((WView
*) w
);
3043 static void view_cmk_moveto_bottom (void *w
, int n
) {
3045 view_moveto_bottom ((WView
*) w
);
3050 view_handle_key (WView
*view
, int c
)
3052 c
= convert_from_input_c (c
);
3054 if (view
->hex_mode
) {
3057 view
->hexview_in_text
= !view
->hexview_in_text
;
3062 view_moveto_bol (view
);
3067 view_move_left (view
, 1);
3071 view_moveto_eol (view
);
3075 view_move_right (view
, 1);
3079 if (view
->hexedit_mode
3080 && view_handle_editkey (view
, c
) == MSG_HANDLED
)
3084 if (check_left_right_keys (view
, c
))
3087 if (check_movement_keys (c
, view
->data_area
.height
+ 1, view
,
3088 view_cmk_move_up
, view_cmk_move_down
,
3089 view_cmk_moveto_top
, view_cmk_moveto_bottom
))
3095 regexp_search (view
, -1);
3099 regexp_search (view
, 1);
3102 /* Continue search */
3107 view_continue_search_cmd (view
);
3112 view_toggle_ruler_cmd (view
);
3116 view_move_left (view
, 1);
3122 view_move_down (view
, 1);
3126 view_move_down (view
, (view
->data_area
.height
+ 1) / 2);
3130 view_move_up (view
, (view
->data_area
.height
+ 1) / 2);
3135 view_move_up (view
, 1);
3139 view_move_right (view
, 1);
3144 view_move_down (view
, view
->data_area
.height
);
3151 /* Unlike Ctrl-O, run a new shell if the subshell is not running. */
3157 view_move_up (view
, view
->data_area
.height
);
3161 view_move_up (view
, 2);
3165 view_move_down (view
, 2);
3169 view
->marks
[view
->marker
] = view
->dpy_start
;
3173 view
->dpy_start
= view
->marks
[view
->marker
];
3177 /* Use to indicate parent that we want to see the next/previous file */
3178 /* Does not work in panel mode */
3181 if (!view_is_in_panel (view
))
3182 view
->move_dir
= c
== XCTRL ('f') ? 1 : -1;
3187 if (view_ok_to_quit (view
))
3188 view
->want_to_quit
= TRUE
;
3193 do_select_codepage ();
3197 #endif /* HAVE_CHARSET */
3199 #ifdef MC_ENABLE_DEBUGGING_CODE
3200 case 't': /* mnemonic: "test" */
3201 view_ccache_dump (view
);
3205 if (c
>= '0' && c
<= '9')
3206 view
->marker
= c
- '0';
3209 return MSG_NOT_HANDLED
;
3214 view_event (WView
*view
, Gpm_Event
*event
, int *result
)
3218 *result
= MOU_NORMAL
;
3220 /* We are not interested in the release events */
3221 if (!(event
->type
& (GPM_DOWN
| GPM_DRAG
)))
3225 if ((event
->buttons
& GPM_B_UP
) && (event
->type
& GPM_DOWN
)) {
3226 view_move_up (view
, 2);
3229 if ((event
->buttons
& GPM_B_DOWN
) && (event
->type
& GPM_DOWN
)) {
3230 view_move_down (view
, 2);
3237 /* Scrolling left and right */
3238 if (!view
->text_wrap_mode
) {
3239 if (x
< view
->data_area
.width
* 1/4) {
3240 view_move_left (view
, 1);
3242 } else if (x
< view
->data_area
.width
* 3/4) {
3243 /* ignore the click */
3245 view_move_right (view
, 1);
3250 /* Scrolling up and down */
3251 if (y
< view
->data_area
.top
+ view
->data_area
.height
* 1/3) {
3252 if (mouse_move_pages_viewer
)
3253 view_move_up (view
, view
->data_area
.height
/ 2);
3255 view_move_up (view
, 1);
3257 } else if (y
< view
->data_area
.top
+ view
->data_area
.height
* 2/3) {
3258 /* ignore the click */
3260 if (mouse_move_pages_viewer
)
3261 view_move_down (view
, view
->data_area
.height
/ 2);
3263 view_move_down (view
, 1);
3270 *result
= MOU_REPEAT
;
3274 /* Real view only */
3276 real_view_event (Gpm_Event
*event
, void *x
)
3278 WView
*view
= (WView
*) x
;
3281 if (view_event (view
, event
, &result
))
3287 view_adjust_size (Dlg_head
*h
)
3292 /* Look up the viewer and the buttonbar, we assume only two widgets here */
3293 view
= (WView
*) find_widget_type (h
, view_callback
);
3294 bar
= find_buttonbar (h
);
3295 widget_set_size (&view
->widget
, 0, 0, LINES
- 1, COLS
);
3296 widget_set_size ((Widget
*) bar
, LINES
- 1, 0, 1, COLS
);
3298 view_compute_areas (view
);
3299 view_update_bytes_per_line (view
);
3302 /* Callback for the view dialog */
3304 view_dialog_callback (Dlg_head
*h
, dlg_msg_t msg
, int parm
)
3308 view_adjust_size (h
);
3312 return default_dlg_callback (h
, msg
, parm
);
3316 /* {{{ External interface }}} */
3318 /* Real view only */
3320 mc_internal_viewer (const char *command
, const char *file
,
3321 int *move_dir_p
, int start_line
)
3328 /* Create dialog and widgets, put them on the dialog */
3330 create_dlg (0, 0, LINES
, COLS
, NULL
, view_dialog_callback
,
3331 "[Internal File Viewer]", NULL
, DLG_WANT_TAB
);
3333 wview
= view_new (0, 0, COLS
, LINES
- 1, 0);
3335 bar
= buttonbar_new (1);
3337 add_widget (view_dlg
, bar
);
3338 add_widget (view_dlg
, wview
);
3340 succeeded
= view_load (wview
, command
, file
, start_line
);
3344 *move_dir_p
= wview
->move_dir
;
3349 destroy_dlg (view_dlg
);
3354 /* {{{ Miscellaneous functions }}} */
3359 WView
*view
= (WView
*) v
;
3362 /* If the user is busy typing, wait until he finishes to update the
3365 if (!hook_present (idle_hook
, view_hook
))
3366 add_hook (&idle_hook
, view_hook
, v
);
3370 delete_hook (&idle_hook
, view_hook
);
3372 if (get_current_type () == view_listing
)
3373 panel
= current_panel
;
3374 else if (get_other_type () == view_listing
)
3375 panel
= other_panel
;
3379 view_load (view
, 0, panel
->dir
.list
[panel
->selected
].fname
, 0);
3383 /* {{{ Event handling }}} */
3386 view_callback (Widget
*w
, widget_msg_t msg
, int parm
)
3388 WView
*view
= (WView
*) w
;
3390 Dlg_head
*h
= view
->widget
.parent
;
3392 view_compute_areas (view
);
3393 view_update_bytes_per_line (view
);
3397 if (view_is_in_panel (view
))
3398 add_hook (&select_file_hook
, view_hook
, view
);
3400 view
->dpy_bbar_dirty
= TRUE
;
3409 view_place_cursor (view
);
3413 i
= view_handle_key ((WView
*) view
, parm
);
3414 if (view
->want_to_quit
&& !view_is_in_panel (view
))
3422 view
->dpy_bbar_dirty
= TRUE
;
3426 case WIDGET_DESTROY
:
3428 if (view_is_in_panel (view
))
3429 delete_hook (&select_file_hook
, view_hook
);
3433 return default_proc (msg
, parm
);
3437 /* {{{ External interface }}} */
3440 view_new (int y
, int x
, int cols
, int lines
, int is_panel
)
3442 WView
*view
= g_new0 (WView
, 1);
3445 init_widget (&view
->widget
, y
, x
, lines
, cols
,
3449 view
->filename
= NULL
;
3450 view
->command
= NULL
;
3452 view_set_datasource_none (view
);
3454 view
->growbuf_in_use
= FALSE
;
3455 /* leave the other growbuf fields uninitialized */
3457 view
->hex_mode
= FALSE
;
3458 view
->hexedit_mode
= FALSE
;
3459 view
->hexview_in_text
= FALSE
;
3460 view
->text_nroff_mode
= FALSE
;
3461 view
->text_wrap_mode
= FALSE
;
3462 view
->magic_mode
= FALSE
;
3464 view
->hexedit_lownibble
= FALSE
;
3465 view
->coord_cache
= NULL
;
3467 view
->dpy_frame_size
= is_panel
? 1 : 0;
3468 view
->dpy_start
= 0;
3469 view
->dpy_text_column
= 0;
3471 view
->hex_cursor
= 0;
3472 view
->cursor_col
= 0;
3473 view
->cursor_row
= 0;
3474 view
->change_list
= NULL
;
3476 /* {status,ruler,data}_area are left uninitialized */
3479 view
->dpy_bbar_dirty
= TRUE
;
3480 view
->bytes_per_line
= 1;
3482 view
->search_start
= 0;
3483 view
->search_length
= 0;
3484 view
->search_exp
= NULL
;
3485 view
->direction
= 1; /* forward */
3486 view
->last_search
= 0; /* it's a function */
3488 view
->want_to_quit
= FALSE
;
3490 for (i
= 0; i
< sizeof(view
->marks
) / sizeof(view
->marks
[0]); i
++)
3494 view
->update_steps
= 0;
3495 view
->update_activate
= 0;
3497 if (default_hex_mode
)
3498 view_toggle_hex_mode (view
);
3499 if (default_nroff_flag
)
3500 view_toggle_nroff_mode (view
);
3501 if (global_wrap_mode
)
3502 view_toggle_wrap_mode (view
);
3503 if (default_magic_flag
)
3504 view_toggle_magic_mode (view
);