fixed several missing #include's
[free-mc.git] / src / view.c
blob59ef83da50f54da3b6716c14900878bd078432f9
1 /*
2 Internal file viewer for the Midnight Commander
4 Copyright (C) 1994, 1995, 1996 The Free Software Foundation
6 Written by: 1994, 1995, 1998 Miguel de Icaza
7 1994, 1995 Janne Kukonlehto
8 1995 Jakub Jelinek
9 1996 Joseph M. Hinkle
10 1997 Norbert Warmuth
11 1998 Pavel Machek
12 2004 Roland Illig <roland.illig@gmx.de>
13 2005 Roland Illig <roland.illig@gmx.de>
15 This program is free software; you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation; either version 2 of the License, or
18 (at your option) any later version.
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
30 /** \file view.c
31 * \brief Source: internal file viewer
34 #ifdef HAVE_CONFIG_H
35 # include <config.h>
36 #endif
38 #include <assert.h>
39 #include <ctype.h>
40 #include <errno.h>
41 #include <limits.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <fcntl.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <unistd.h>
50 #include "global.h"
51 #include "tty.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 */
55 #include "color.h"
56 #include "mouse.h"
57 #include "help.h"
58 #include "key.h" /* For mi_getch() */
59 #include "layout.h"
60 #include "setup.h"
61 #include "wtools.h" /* For query_set_sel() */
62 #include "dir.h"
63 #include "panel.h" /* Needed for current_panel and other_panel */
64 #include "win.h"
65 #include "execute.h"
66 #include "main.h" /* slow_terminal */
67 #include "view.h"
68 #include "history.h"
69 #include "charsets.h"
70 #include "selcodepage.h"
71 #include "strutil.h"
72 #include "../src/search/search.h"
74 /* Block size for reading files in parts */
75 #define VIEW_PAGE_SIZE ((size_t) 8192)
77 typedef unsigned char byte;
79 /* Offset in bytes into a file */
80 typedef unsigned long offset_type;
81 #define INVALID_OFFSET ((offset_type) -1)
82 #define OFFSETTYPE_MAX (~((offset_type) 0))
83 #define OFFSETTYPE_PRIX "lX"
84 #define OFFSETTYPE_PRId "lu"
86 /* A width or height on the screen */
87 typedef unsigned int screen_dimen;
89 /* A node for building a change list on change_list */
90 struct hexedit_change_node {
91 struct hexedit_change_node *next;
92 offset_type offset;
93 byte value;
96 /* data sources of the view */
97 enum view_ds {
98 DS_NONE, /* No data available */
99 DS_STDIO_PIPE, /* Data comes from a pipe using popen/pclose */
100 DS_VFS_PIPE, /* Data comes from a piped-in VFS file */
101 DS_FILE, /* Data comes from a VFS file */
102 DS_STRING /* Data comes from a string in memory */
105 struct area {
106 screen_dimen top, left;
107 screen_dimen height, width;
110 #define VLF_DISCARD 1
111 #define VLF_INIT 2
112 #define VLF_COMPLETE 3
114 /* basic structure for caching text, it correspond to one line in wrapped text.
115 * That makes easier to move in wrap mode.
116 * cache_lines are stored in two linked lists, one for normal mode
117 * and one for nroff mode
118 * cache_line is valid of end is not INVALID_OFFSET
119 * last line has set width to (screen_dimen) (-1)*/
120 /* never access next and previous in cache_line directly, use appropriately function
121 * instead */
122 struct cache_line {
123 /* number of line in text, so two cache_line have same number
124 * if they are on same text line (text line is wider than screen) */
125 unsigned long number;
126 /* previous cache_line in list*/
127 struct cache_line *prev;
128 /* next cache_line in list*/
129 struct cache_line *next;
130 /* offset when cache_line start in text,
131 * cache_line.start = cache_line->prev.end */
132 offset_type start;
133 /* offset when cache_line ends
134 * cache_line.end = cache_line->next.start */
135 offset_type end;
136 /* how many column take on screen */
137 screen_dimen width;
138 /* correct ident, if prevoius line ends by tabulator */
139 screen_dimen left;
142 struct WView {
143 Widget widget;
145 char *filename; /* Name of the file */
146 char *command; /* Command used to pipe data in */
148 enum view_ds datasource; /* Where the displayed data comes from */
150 /* stdio pipe data source */
151 FILE *ds_stdio_pipe; /* Output of a shell command */
153 /* vfs pipe data source */
154 int ds_vfs_pipe; /* Non-seekable vfs file descriptor */
156 /* vfs file data source */
157 int ds_file_fd; /* File with random access */
158 off_t ds_file_filesize; /* Size of the file */
159 off_t ds_file_offset; /* Offset of the currently loaded data */
160 byte *ds_file_data; /* Currently loaded data */
161 size_t ds_file_datalen; /* Number of valid bytes in file_data */
162 size_t ds_file_datasize; /* Number of allocated bytes in file_data */
164 /* string data source */
165 byte *ds_string_data; /* The characters of the string */
166 size_t ds_string_len; /* The length of the string */
168 /* Growing buffers information */
169 gboolean growbuf_in_use; /* Use the growing buffers? */
170 byte **growbuf_blockptr; /* Pointer to the block pointers */
171 size_t growbuf_blocks; /* The number of blocks in *block_ptr */
172 size_t growbuf_lastindex; /* Number of bytes in the last page of the
173 growing buffer */
174 gboolean growbuf_finished; /* TRUE when all data has been read. */
176 /* Editor modes */
177 gboolean hex_mode; /* Hexview or Hexedit */
178 gboolean hexedit_mode; /* Hexedit */
179 gboolean hexview_in_text; /* Is the hexview cursor in the text area? */
180 gboolean text_nroff_mode; /* Nroff-style highlighting */
181 gboolean text_wrap_mode; /* Wrap text lines to fit them on the screen */
182 gboolean magic_mode; /* Preprocess the file using external programs */
184 /* Additional editor state */
185 gboolean hexedit_lownibble; /* Are we editing the last significant nibble? */
187 /* Display information */
188 screen_dimen dpy_frame_size;/* Size of the frame surrounding the real viewer */
189 offset_type dpy_start; /* Offset of the displayed data */
190 offset_type dpy_end; /* Offset after the displayed data */
191 offset_type dpy_text_column;/* Number of skipped columns in non-wrap
192 * text mode */
193 offset_type hex_cursor; /* Hexview cursor position in file */
194 screen_dimen cursor_col; /* Cursor column */
195 screen_dimen cursor_row; /* Cursor row */
196 struct hexedit_change_node *change_list; /* Linked list of changes */
197 struct area status_area; /* Where the status line is displayed */
198 struct area ruler_area; /* Where the ruler is displayed */
199 struct area data_area; /* Where the data is displayed */
201 int dirty; /* Number of skipped updates */
202 gboolean dpy_bbar_dirty; /* Does the button bar need to be updated? */
204 /* Mode variables */
205 int bytes_per_line; /* Number of bytes per line in hex mode */
207 /* Search variables */
208 offset_type search_start; /* First character to start searching from */
209 offset_type search_end; /* Length of found string or 0 if none was found */
211 /* Pointer to the last search command */
212 gboolean want_to_quit; /* Prepare for cleanup ... */
214 /* Markers */
215 int marker; /* mark to use */
216 offset_type marks [10]; /* 10 marks: 0..9 */
218 int move_dir; /* return value from widget:
219 * 0 do nothing
220 * -1 view previous file
221 * 1 view next file
224 offset_type update_steps; /* The number of bytes between percent
225 * increments */
226 offset_type update_activate;/* Last point where we updated the status */
228 /* never access cache_line in view directly, use appropriately function
229 * instead */
230 /* first cache_line for normal mode */
231 struct cache_line *lines;
232 /* last cache_line for normal mode, is set when text is read to the end */
233 struct cache_line *lines_end;
234 /* first cache_line for nroff mode */
235 struct cache_line *nroff_lines;
236 /* last cache_line for nroff mode, is set when text is read to the end */
237 struct cache_line *nroff_lines_end;
238 /* cache_line, that is first showed on display (something like cursor)
239 * used for both normal adn nroff mode */
240 struct cache_line *first_showed_line;
241 /* converter for translation of text */
242 GIConv converter;
244 /* handle of search engine */
245 mc_search_t *search;
246 gchar *last_search_string;
247 mc_search_type_t search_type;
248 gboolean search_all_codepages;
249 gboolean search_case;
250 gboolean search_backwards;
254 /* {{{ Global Variables }}} */
256 /* Maxlimit for skipping updates */
257 int max_dirt_limit = 10;
259 /* If set, show a ruler */
260 static enum ruler_type {
261 RULER_NONE,
262 RULER_TOP,
263 RULER_BOTTOM
264 } ruler = RULER_NONE;
266 /* Scrolling is done in pages or line increments */
267 int mouse_move_pages_viewer = 1;
269 /* wrap mode default */
270 int global_wrap_mode = 1;
272 int default_hex_mode = 0;
273 int default_magic_flag = 1;
274 int default_nroff_flag = 1;
275 int altered_hex_mode = 0;
276 int altered_magic_flag = 0;
277 int altered_nroff_flag = 0;
279 static const char hex_char[] = "0123456789ABCDEF";
281 int mcview_remember_file_position = FALSE;
283 /* {{{ Function Prototypes }}} */
285 /* Our widget callback */
286 static cb_ret_t view_callback (Widget *, widget_msg_t, int);
288 static void view_labels (WView * view);
290 static void view_init_growbuf (WView *);
291 static void view_place_cursor (WView *view);
292 static void display (WView *);
293 static void view_done (WView *);
295 /* {{{ Helper Functions }}} */
297 /* difference or zero */
298 static inline screen_dimen
299 dimen_doz (screen_dimen a, screen_dimen b)
301 return (a >= b) ? a - b : 0;
304 static inline screen_dimen
305 dimen_min (screen_dimen a, screen_dimen b)
307 return (a < b) ? a : b;
310 static inline offset_type
311 offset_doz (offset_type a, offset_type b)
313 return (a >= b) ? a - b : 0;
316 static inline offset_type
317 offset_rounddown (offset_type a, offset_type b)
319 assert (b != 0);
320 return a - a % b;
323 /* {{{ Simple Primitive Functions for WView }}} */
325 static inline gboolean
326 view_is_in_panel (WView *view)
328 return (view->dpy_frame_size != 0);
331 static inline int get_byte (WView *view, offset_type offset);
333 /* read on character from text, character is translated into terminal encoding
334 * writes result in ch, return how many bytes was read or -1 if end of text */
335 static int
336 view_get_char (WView *view, offset_type from, char *ch, int size)
338 static char buffer [MB_LEN_MAX + 1];
339 static int c;
340 static size_t result;
342 result = 0;
344 while (result < sizeof (buffer) - 1) {
345 c = get_byte (view, from + result);
346 if (c == -1) break;
347 buffer[result] = (unsigned char) c;
348 result++;
349 buffer[result] = '\0';
350 switch (str_translate_char (view->converter, buffer, result, ch, size)) {
351 case ESTR_SUCCESS:
352 return (int) result;
353 case ESTR_PROBLEM:
354 break;
355 case ESTR_FAILURE:
356 ch[0] = '?';
357 ch[1] = '\0';
358 return 1;
361 return -1;
364 /* this structure and view_read_* functions make reading text simpler
365 * view need remeber 4 following charsets: actual, next and two previous */
366 struct read_info {
367 char ch[4][MB_LEN_MAX + 1];
368 char *cnxt;
369 char *cact;
370 char *chi1;
371 char *chi2;
372 offset_type next;
373 offset_type actual;
374 int result;
377 /* set read_info into initial state and read first character to cnxt
378 * return how many bytes was read or -1 if end of text */
379 static void
380 view_read_start (WView *view, struct read_info *info, offset_type from)
382 info->ch[0][0] = '\0';
383 info->ch[1][0] = '\0';
384 info->ch[2][0] = '\0';
385 info->ch[3][0] = '\0';
387 info->cnxt = info->ch[0];
388 info->cact = info->ch[1];
389 info->chi1 = info->ch[2];
390 info->chi2 = info->ch[3];
392 info->next = from;
394 info->result = view_get_char (view, info->next, info->cnxt, MB_LEN_MAX + 1);
397 /* move characters in read_info one forward (chi2 = last previous is forgotten)
398 * read additional charsets into cnxt
399 * return how many bytes was read or -1 if end of text */
400 static void
401 view_read_continue (WView *view, struct read_info *info)
403 char *tmp;
405 tmp = info->chi2;
406 info->chi2 = info->chi1;
407 info->chi1 = info->cact;
408 info->cact = info->cnxt;
409 info->cnxt = tmp;
411 info->actual = info->next;
412 info->next+= info->result;
414 info->result = view_get_char (view, info->next, info->cnxt, MB_LEN_MAX + 1);
417 #define VRT_NW_NO 0
418 #define VRT_NW_YES 1
419 #define VRT_NW_CONTINUE 2
421 /* test if cact of read_info is newline
422 * return VRT_NW_NO, VRT_NW_YES
423 * or VRT_NW_CONTINUE follow after cact ("\r\n", ...) */
424 static int
425 view_read_test_new_line (WView *view, struct read_info *info)
427 #define cmp(t1,t2) (strcmp((t1),(t2)) == 0)
428 (void) view;
429 if (cmp (info->cact, "\n")) return VRT_NW_YES;
431 if (cmp (info->cact, "\r")) {
432 if (info->result != -1 && (cmp (info->cnxt, "\r") || cmp (info->cnxt, "\n")))
433 return VRT_NW_CONTINUE;
435 return VRT_NW_YES;
437 return VRT_NW_NO;
440 /* test if cact of read_info is tabulator
441 * return VRT_NW_NO or VRT_NW_YES */
442 static int
443 view_read_test_tabulator (WView *view, struct read_info *info)
445 #define cmp(t1,t2) (strcmp((t1),(t2)) == 0)
446 (void) view;
447 return cmp (info->cact, "\t");
450 /* test if read_info.cact is '\b' and charsets in read_info are correct
451 * nroff sequence, return VRT_NW_NO or VRT_NW_YES */
452 static int
453 view_read_test_nroff_back (WView *view, struct read_info *info)
455 #define cmp(t1,t2) (strcmp((t1),(t2)) == 0)
456 return view->text_nroff_mode && cmp (info->cact, "\b") &&
457 (info->result != -1) && str_isprint (info->cnxt) &&
458 !cmp (info->chi1, "") && str_isprint (info->chi1) &&
459 (cmp (info->chi1, info->cnxt) || cmp (info->chi1, "_")
460 || (cmp (info->chi1, "+") && cmp (info->cnxt, "o")));
464 /* rutine for view_load_cache_line, line is ended and following cache_line is
465 * set up for loading
466 * used when end of line has been read in text */
467 static struct cache_line *
468 view_lc_create_next_line (struct cache_line *line, offset_type start)
470 struct cache_line *result = NULL;
472 line->end = start;
474 if (line->next == NULL) {
475 result = g_new0 (struct cache_line, 1);
476 line->next = result;
477 result->prev = line;
478 } else result = line->next;
480 result->start = start;
481 result->width = 0;
482 result->left = 0;
483 result->number = line->number + 1;
484 result->end = INVALID_OFFSET;
486 return result;
489 /* rutine for view_load_cache_line, line is ended and following cache_line is
490 * set up for loading
491 * used when line has maximum width */
492 static struct cache_line *
493 view_lc_create_wrap_line (struct cache_line *line, offset_type start,
494 screen_dimen left)
496 struct cache_line *result = NULL;
498 line->end = start;
500 if (line->next == NULL) {
501 result = g_new0 (struct cache_line, 1);
502 line->next = result;
503 result->prev = line;
504 } else result = line->next;
506 result->start = start;
507 result->width = 0;
508 result->left = left;
509 result->number = line->number;
510 result->end = INVALID_OFFSET;
512 return result;
515 /* read charsets fro text and set up correct width, end and start of next line
516 * view->data_area.width must be greater than 0 */
517 static struct cache_line *
518 view_load_cache_line (WView *view, struct cache_line *line)
520 const screen_dimen width = view->data_area.width;
521 struct read_info info;
522 offset_type nroff_start = 0;
523 int nroff_seq = 0;
524 gsize w;
526 line->width = 0;
528 view_read_start (view, &info, line->start);
529 while (info.result != -1) {
530 view_read_continue (view, &info);
532 switch (view_read_test_new_line (view, &info)) {
533 case VRT_NW_YES:
534 line = view_lc_create_next_line (line, info.next);
535 return line;
536 case VRT_NW_CONTINUE:
537 continue;
540 if (view_read_test_tabulator (view, &info)) {
541 line->width+= 8 - (line->left + line->width) % 8;
542 if ((width != 0) && (line->left + line->width >= width)) {
543 w = line->left + line->width - width;
544 /* if width of screen is very small, tabulator is cut to line
545 * left of next line is 0 */
546 w = (w < width) ? w : 0;
547 line->width = width - line->left;
548 line = view_lc_create_wrap_line (line, info.next, w);
549 return line;
553 if (view_read_test_nroff_back (view, &info)) {
554 w = str_term_width1 (info.chi1);
555 line->width-= w;
556 nroff_seq = 1;
557 continue;
559 /* assure, that nroff sequence never start in previous cache_line */
560 if (nroff_seq > 0)
561 nroff_seq--;
562 else
563 nroff_start = info.actual;
565 w = str_isprint (info.cact) ? str_term_width1 (info.cact) : 1;
567 if (line->left + line->width + w > width) {
568 line = view_lc_create_wrap_line (line, nroff_start, 0);
569 return line;
570 } else {
571 while (info.result != -1 && str_iscombiningmark (info.cnxt)) {
572 view_read_continue (view, &info);
575 line->width+= w;
579 /* text read to the end, seting lines_end*/
580 if (view->text_nroff_mode)
581 view->nroff_lines_end = line;
582 else
583 view->lines_end = line;
585 line = view_lc_create_next_line (line, info.next);
586 line->width = (screen_dimen) (-1);
587 line->end = info.next;
589 return line;
592 static void
593 view_lc_set_param (offset_type *param, offset_type value)
595 if ((*param) > value) (*param) = value;
598 /* offset to column or column to offset, value that will be maped must be
599 * set to INVALID_OFFSET, it is very analogous to view_load_cache_line
600 * and it is possible to integrate them in one function */
601 static void
602 view_map_offset_and_column (WView *view, struct cache_line *line,
603 offset_type *column, offset_type *offset)
605 const screen_dimen width = view->data_area.width;
606 struct read_info info;
607 offset_type nroff_start = 0;
608 int nroff_seq = 0;
609 gsize w;
610 screen_dimen col;
612 /* HACK: valgrind screams here.
613 * TODO: to figure out WHY valgrind detects uninitialized
614 * variables. Maybe, info isn't fully initialized?
616 memset (&info, 0, sizeof info);
618 col = 0;
619 view_read_start (view, &info, line->start);
620 while (info.result != -1) {
621 view_read_continue (view, &info);
623 if (*column == INVALID_OFFSET) {
624 if (*offset < info.next) {
625 (*column) = col + line->left;
626 return;
630 switch (view_read_test_new_line (view, &info)) {
631 case VRT_NW_YES:
632 view_lc_set_param (offset, info.actual);
633 view_lc_set_param (column, line->left + col);
634 return;
635 case VRT_NW_CONTINUE:
636 continue;
639 if (view_read_test_tabulator (view, &info)) {
640 col+= 8 - (line->left + col) % 8;
641 if ((width != 0) && (line->left + col >= width)) {
642 w = line->left + col - width;
643 w = (w < width) ? w : 0;
644 col = width - line->left;
645 view_lc_set_param (offset, info.actual);
646 view_lc_set_param (column, line->left + col);
647 return;
651 if (view_read_test_nroff_back (view, &info)) {
652 w = str_term_width1 (info.chi1);
653 col-= w;
654 nroff_seq = 1;
655 continue;
657 if (nroff_seq > 0)
658 nroff_seq--;
659 else
660 nroff_start = info.actual;
662 w = str_isprint (info.cact) ? str_term_width1 (info.cact) : 1;
664 if (line->left + col + w > width) {
665 view_lc_set_param (offset, nroff_start);
666 view_lc_set_param (column, line->left + col);
667 return;
668 } else {
669 while (info.result != -1 && str_iscombiningmark (info.cnxt)) {
670 view_read_continue (view, &info);
674 col+= w;
676 if (*offset == INVALID_OFFSET) {
677 if (*column < col + line->left) {
678 (*offset) = nroff_start;
679 return;
684 view_lc_set_param (offset, info.actual);
685 view_lc_set_param (column, line->left + col);
688 /* macro, that iterate cache_line until stop holds,
689 * nf is function to iterate cache_line
690 * l is line to iterate and t is temporary line only */
691 #define view_move_to_stop(l,t,stop,nf) \
692 while (((t = nf (view, l)) != NULL) && (stop)) l = t;
694 /* make all cache_lines invalidet */
695 static void
696 view_reset_cache_lines (WView *view)
698 if (view->lines != NULL) {
699 view->lines->end = INVALID_OFFSET;
701 view->lines_end = NULL;
703 if (view->nroff_lines != NULL) {
704 view->nroff_lines->end = INVALID_OFFSET;
706 view->nroff_lines_end = NULL;
708 view->first_showed_line = NULL;
711 #define MAX_UNLOADED_CACHE_LINE 100
713 /* free some cache_lines, if count of cache_lines is bigger
714 * than MAX_UNLOADED_CACHE_LINE */
715 static void
716 view_reduce_cache_lines (WView *view)
718 struct cache_line *line;
719 struct cache_line *next;
720 int li;
722 li = 0;
723 line = view->lines;
724 while (line != NULL && li < MAX_UNLOADED_CACHE_LINE) {
725 line = line->next;
726 li++;
728 if (line != NULL) line->prev->next = NULL;
729 while (line != NULL) {
730 next = line->next;
731 g_free (line);
732 line = next;
734 view->lines_end = NULL;
736 line = view->nroff_lines;
737 while (line != NULL && li < MAX_UNLOADED_CACHE_LINE) {
738 line = line->next;
739 li++;
741 if (line != NULL) line->prev->next = NULL;
742 while (line != NULL) {
743 next = line->next;
744 g_free (line);
745 line = next;
747 view->nroff_lines_end = NULL;
749 view->first_showed_line = NULL;
752 /* return first cache_line for actual mode (normal / nroff) */
753 static struct cache_line *
754 view_get_first_line (WView *view)
756 struct cache_line **first_line;
758 first_line = (view->text_nroff_mode) ?
759 &(view->nroff_lines) : &(view->lines);
761 if (*first_line == NULL) {
762 (*first_line) = g_new0 (struct cache_line, 1);
763 (*first_line)->end = INVALID_OFFSET;
766 if ((*first_line)->end == INVALID_OFFSET)
767 view_load_cache_line (view, *first_line);
769 return (*first_line);
772 /* return following chahe_line or NULL */
773 static struct cache_line*
774 view_get_next_line (WView *view, struct cache_line *line)
776 struct cache_line *result;
778 if (line->next != NULL) {
779 result = line->next;
781 if (result->end == INVALID_OFFSET)
782 view_load_cache_line (view, result);
784 return (result->width != (screen_dimen) (-1)) ? result : NULL;
786 return NULL;
789 /* return last cache_line, it could take same time, because whole read text must
790 * be read (only once) */
791 static struct cache_line *
792 view_get_last_line (WView *view)
794 struct cache_line *result;
795 struct cache_line *next;
797 result = (view->text_nroff_mode) ?
798 view->nroff_lines_end : view->nroff_lines_end;
800 if (result != NULL) return result;
802 if (view->first_showed_line == NULL)
803 view->first_showed_line = view_get_first_line (view);
805 result = view->first_showed_line;
806 next = view_get_next_line (view, result);
807 while (next != NULL) {
808 result = next;
809 next = view_get_next_line (view, result);
811 return result;
814 /* return previous cache_line or NULL */
815 static struct cache_line *
816 view_get_previous_line (WView *view, struct cache_line *line)
818 (void) view;
819 return line->prev;
822 /* return first displayed cache_line */
823 static struct cache_line *
824 view_get_first_showed_line (WView *view)
826 struct cache_line *result;
828 if (view->first_showed_line == NULL)
829 view->first_showed_line = view_get_first_line (view);
831 result = view->first_showed_line;
832 return result;
835 /* return first cache_line with same number as line */
836 static struct cache_line *
837 view_get_start_of_whole_line (WView *view, struct cache_line *line)
839 struct cache_line *t;
841 if (line != NULL) {
842 view_move_to_stop (line, t, t->number == line->number, view_get_previous_line)
843 return line;
845 return NULL;
848 /* return last cache_line with same number as line */
849 static struct cache_line *
850 view_get_end_of_whole_line (WView *view, struct cache_line *line)
852 struct cache_line *t;
854 if (line != NULL) {
855 view_move_to_stop (line, t, t->number == line->number, view_get_next_line)
856 return line;
858 return NULL;
861 /* return last cache_line, that has number lesser than line
862 * or NULL */
863 static struct cache_line *
864 view_get_previous_whole_line (WView *view, struct cache_line *line)
866 line = view_get_start_of_whole_line (view, line);
867 return view_get_previous_line (view, line);
870 /* return first cache_line, that has number greater than line
871 * or NULL */
872 static struct cache_line *
873 view_get_next_whole_line (WView *view, struct cache_line *line)
875 line = view_get_end_of_whole_line (view, line);
876 return view_get_next_line (view, line);
879 /* return sum of widths of all cache_lines that has same number as line */
880 static screen_dimen
881 view_width_of_whole_line (WView *view, struct cache_line *line)
883 struct cache_line *next;
884 screen_dimen result = 0;
886 line = view_get_start_of_whole_line (view, line);
887 next = view_get_next_line (view, line);
888 while ((next != NULL) && (next->number == line->number)) {
889 result+= line->left + line->width;
890 line = next;
891 next = view_get_next_line (view, line);
893 result+= line->left + line->width;
894 return result;
897 /* return sum of widths of cache_lines before line, that has same number as line */
898 static screen_dimen
899 view_width_of_whole_line_before (WView *view, struct cache_line *line)
901 struct cache_line *next;
902 screen_dimen result = 0;
904 next = view_get_start_of_whole_line (view, line);
905 while (next != line) {
906 result+= next->left + next->width;
907 next = view_get_next_line (view, next);
909 return result;
912 /* map column to offset and cache_line */
913 static offset_type
914 view_column_to_offset (WView *view, struct cache_line **line, offset_type column)
916 struct cache_line *next;
917 offset_type result;
919 *line = view_get_start_of_whole_line (view, *line);
921 while (column >= (*line)->left + (*line)->width) {
922 column-= (*line)->left + (*line)->width;
923 result = (*line)->end;
924 next = view_get_next_line (view, *line);
925 if ((next == NULL) || (next->number != (*line)->number)) break;
926 (*line) = next;
928 /* if (column < (*line)->left + (*line)->width) { */
929 result = INVALID_OFFSET,
930 view_map_offset_and_column (view, *line, &column, &result);
931 /* } */
932 return result;
935 /* map offset to cache_line */
936 static struct cache_line *
937 view_offset_to_line (WView *view, offset_type from)
939 struct cache_line *result;
940 struct cache_line *t;
942 result = view_get_first_line (view);
944 view_move_to_stop (result, t, result->end <= from, view_get_next_line)
945 return result;
948 /* map offset to cache_line, searching starts from line */
949 static struct cache_line *
950 view_offset_to_line_from (WView *view, offset_type from, struct cache_line *line)
952 struct cache_line *result;
953 struct cache_line *t;
955 result = line;
957 view_move_to_stop (result, t, result->start > from, view_get_previous_line)
958 view_move_to_stop (result, t, result->end <= from, view_get_next_line)
960 return result;
963 /* mam offset to column */
964 static screen_dimen
965 view_offset_to_column (WView *view, struct cache_line *line, offset_type from)
967 offset_type result = INVALID_OFFSET;
969 view_map_offset_and_column (view, line, &result, &from);
971 result+= view_width_of_whole_line_before (view, line);
973 return result;
976 static void
977 view_compute_areas (WView *view)
979 struct area view_area;
980 struct cache_line *next;
981 screen_dimen height, rest, y;
982 screen_dimen old_width;
984 old_width = view->data_area.width;
986 /* The viewer is surrounded by a frame of size view->dpy_frame_size.
987 * Inside that frame, there are: The status line (at the top),
988 * the data area and an optional ruler, which is shown above or
989 * below the data area. */
991 view_area.top = view->dpy_frame_size;
992 view_area.left = view->dpy_frame_size;
993 view_area.height = dimen_doz(view->widget.lines, 2 * view->dpy_frame_size);
994 view_area.width = dimen_doz(view->widget.cols, 2 * view->dpy_frame_size);
996 /* Most coordinates of the areas equal those of the whole viewer */
997 view->status_area = view_area;
998 view->ruler_area = view_area;
999 view->data_area = view_area;
1001 /* Compute the heights of the areas */
1002 rest = view_area.height;
1004 height = dimen_min(rest, 1);
1005 view->status_area.height = height;
1006 rest -= height;
1008 height = dimen_min(rest, (ruler == RULER_NONE || view->hex_mode) ? 0 : 2);
1009 view->ruler_area.height = height;
1010 rest -= height;
1012 view->data_area.height = rest;
1014 /* Compute the position of the areas */
1015 y = view_area.top;
1017 view->status_area.top = y;
1018 y += view->status_area.height;
1020 if (ruler == RULER_TOP) {
1021 view->ruler_area.top = y;
1022 y += view->ruler_area.height;
1025 view->data_area.top = y;
1026 y += view->data_area.height;
1028 if (ruler == RULER_BOTTOM) {
1029 view->ruler_area.top = y;
1030 y += view->ruler_area.height;
1033 if (old_width != view->data_area.width) {
1034 view_reset_cache_lines (view);
1035 view->first_showed_line = view_get_first_line (view);
1036 next = view_get_next_line (view, view->first_showed_line);
1037 while ((next != NULL) && (view->first_showed_line->end <= view->dpy_start)) {
1038 view->first_showed_line = next;
1039 next = view_get_next_line (view, view->first_showed_line);
1041 view->dpy_start = view->first_showed_line->start;
1045 static void
1046 view_hexedit_free_change_list (WView *view)
1048 struct hexedit_change_node *curr, *next;
1050 for (curr = view->change_list; curr != NULL; curr = next) {
1051 next = curr->next;
1052 g_free (curr);
1054 view->change_list = NULL;
1055 view->dirty++;
1058 /* {{{ Growing buffer }}} */
1060 static void
1061 view_init_growbuf (WView *view)
1063 view->growbuf_in_use = TRUE;
1064 view->growbuf_blockptr = NULL;
1065 view->growbuf_blocks = 0;
1066 view->growbuf_lastindex = VIEW_PAGE_SIZE;
1067 view->growbuf_finished = FALSE;
1070 static void
1071 view_growbuf_free (WView *view)
1073 size_t i;
1075 assert (view->growbuf_in_use);
1077 for (i = 0; i < view->growbuf_blocks; i++)
1078 g_free (view->growbuf_blockptr[i]);
1079 g_free (view->growbuf_blockptr);
1080 view->growbuf_blockptr = NULL;
1081 view->growbuf_in_use = FALSE;
1084 static offset_type
1085 view_growbuf_filesize (WView *view)
1087 assert(view->growbuf_in_use);
1089 if (view->growbuf_blocks == 0)
1090 return 0;
1091 else
1092 return ((offset_type) view->growbuf_blocks - 1) * VIEW_PAGE_SIZE
1093 + view->growbuf_lastindex;
1096 /* Copies the output from the pipe to the growing buffer, until either
1097 * the end-of-pipe is reached or the interval [0..ofs) of the growing
1098 * buffer is completely filled. */
1099 static void
1100 view_growbuf_read_until (WView *view, offset_type ofs)
1102 ssize_t nread;
1103 byte *p;
1104 size_t bytesfree;
1105 gboolean short_read;
1107 assert (view->growbuf_in_use);
1109 if (view->growbuf_finished)
1110 return;
1112 short_read = FALSE;
1113 while (view_growbuf_filesize (view) < ofs || short_read) {
1114 if (view->growbuf_lastindex == VIEW_PAGE_SIZE) {
1115 /* Append a new block to the growing buffer */
1116 byte *newblock = g_try_malloc (VIEW_PAGE_SIZE);
1117 byte **newblocks = g_try_malloc (sizeof (*newblocks) * (view->growbuf_blocks + 1));
1118 if (!newblock || !newblocks) {
1119 g_free (newblock);
1120 g_free (newblocks);
1121 return;
1123 memcpy (newblocks, view->growbuf_blockptr, sizeof (*newblocks) * view->growbuf_blocks);
1124 g_free (view->growbuf_blockptr);
1125 view->growbuf_blockptr = newblocks;
1126 view->growbuf_blockptr[view->growbuf_blocks++] = newblock;
1127 view->growbuf_lastindex = 0;
1129 p = view->growbuf_blockptr[view->growbuf_blocks - 1] + view->growbuf_lastindex;
1130 bytesfree = VIEW_PAGE_SIZE - view->growbuf_lastindex;
1132 if (view->datasource == DS_STDIO_PIPE) {
1133 nread = fread (p, 1, bytesfree, view->ds_stdio_pipe);
1134 if (nread == 0) {
1135 view->growbuf_finished = TRUE;
1136 (void) pclose (view->ds_stdio_pipe);
1137 display (view);
1138 close_error_pipe (D_NORMAL, NULL);
1139 view->ds_stdio_pipe = NULL;
1140 return;
1142 } else {
1143 assert (view->datasource == DS_VFS_PIPE);
1144 do {
1145 nread = mc_read (view->ds_vfs_pipe, p, bytesfree);
1146 } while (nread == -1 && errno == EINTR);
1147 if (nread == -1 || nread == 0) {
1148 view->growbuf_finished = TRUE;
1149 (void) mc_close (view->ds_vfs_pipe);
1150 view->ds_vfs_pipe = -1;
1151 return;
1154 short_read = ((size_t)nread < bytesfree);
1155 view->growbuf_lastindex += nread;
1159 static int
1160 get_byte_growing_buffer (WView *view, offset_type byte_index)
1162 offset_type pageno = byte_index / VIEW_PAGE_SIZE;
1163 offset_type pageindex = byte_index % VIEW_PAGE_SIZE;
1165 assert (view->growbuf_in_use);
1167 if ((size_t) pageno != pageno)
1168 return -1;
1170 view_growbuf_read_until (view, byte_index + 1);
1171 if (view->growbuf_blocks == 0)
1172 return -1;
1173 if (pageno < view->growbuf_blocks - 1)
1174 return view->growbuf_blockptr[pageno][pageindex];
1175 if (pageno == view->growbuf_blocks - 1 && pageindex < view->growbuf_lastindex)
1176 return view->growbuf_blockptr[pageno][pageindex];
1177 return -1;
1180 /* {{{ Data sources }}} */
1183 The data source provides the viewer with data from either a file, a
1184 string or the output of a command. The get_byte() function can be
1185 used to get the value of a byte at a specific offset. If the offset
1186 is out of range, -1 is returned. The function get_byte_indexed(a,b)
1187 returns the byte at the offset a+b, or -1 if a+b is out of range.
1189 The view_set_byte() function has the effect that later calls to
1190 get_byte() will return the specified byte for this offset. This
1191 function is designed only for use by the hexedit component after
1192 saving its changes. Inspect the source before you want to use it for
1193 other purposes.
1195 The view_get_filesize() function returns the current size of the
1196 data source. If the growing buffer is used, this size may increase
1197 later on. Use the view_may_still_grow() function when you want to
1198 know if the size can change later.
1201 static offset_type
1202 view_get_filesize (WView *view)
1204 switch (view->datasource) {
1205 case DS_NONE:
1206 return 0;
1207 case DS_STDIO_PIPE:
1208 case DS_VFS_PIPE:
1209 return view_growbuf_filesize (view);
1210 case DS_FILE:
1211 return view->ds_file_filesize;
1212 case DS_STRING:
1213 return view->ds_string_len;
1214 default:
1215 assert(!"Unknown datasource type");
1216 return 0;
1220 static inline gboolean
1221 view_may_still_grow (WView *view)
1223 return (view->growbuf_in_use && !view->growbuf_finished);
1226 /* returns TRUE if the idx lies in the half-open interval
1227 * [offset; offset + size), FALSE otherwise.
1229 static inline gboolean
1230 already_loaded (offset_type offset, offset_type idx, size_t size)
1232 return (offset <= idx && idx - offset < size);
1235 static inline void
1236 view_file_load_data (WView *view, offset_type byte_index)
1238 offset_type blockoffset;
1239 ssize_t res;
1240 size_t bytes_read;
1242 assert (view->datasource == DS_FILE);
1244 if (already_loaded (view->ds_file_offset, byte_index, view->ds_file_datalen))
1245 return;
1247 if (byte_index >= view->ds_file_filesize)
1248 return;
1250 blockoffset = offset_rounddown (byte_index, view->ds_file_datasize);
1251 if (mc_lseek (view->ds_file_fd, blockoffset, SEEK_SET) == -1)
1252 goto error;
1254 bytes_read = 0;
1255 while (bytes_read < view->ds_file_datasize) {
1256 res = mc_read (view->ds_file_fd, view->ds_file_data + bytes_read, view->ds_file_datasize - bytes_read);
1257 if (res == -1)
1258 goto error;
1259 if (res == 0)
1260 break;
1261 bytes_read += (size_t) res;
1263 view->ds_file_offset = blockoffset;
1264 if (bytes_read > view->ds_file_filesize - view->ds_file_offset) {
1265 /* the file has grown in the meantime -- stick to the old size */
1266 view->ds_file_datalen = view->ds_file_filesize - view->ds_file_offset;
1267 } else {
1268 view->ds_file_datalen = bytes_read;
1270 return;
1272 error:
1273 view->ds_file_datalen = 0;
1276 static int
1277 get_byte_none (WView *view, offset_type byte_index)
1279 assert (view->datasource == DS_NONE);
1280 (void) &view;
1281 (void) byte_index;
1282 return -1;
1285 static inline int
1286 get_byte_file (WView *view, offset_type byte_index)
1288 assert (view->datasource == DS_FILE);
1290 view_file_load_data (view, byte_index);
1291 if (already_loaded(view->ds_file_offset, byte_index, view->ds_file_datalen))
1292 return view->ds_file_data[byte_index - view->ds_file_offset];
1293 return -1;
1296 static int
1297 get_byte_string (WView *view, offset_type byte_index)
1299 assert (view->datasource == DS_STRING);
1300 if (byte_index < view->ds_string_len)
1301 return view->ds_string_data[byte_index];
1302 return -1;
1305 static inline int
1306 get_byte (WView *view, offset_type offset)
1308 switch (view->datasource) {
1309 case DS_STDIO_PIPE:
1310 case DS_VFS_PIPE:
1311 return get_byte_growing_buffer (view, offset);
1312 case DS_FILE:
1313 return get_byte_file (view, offset);
1314 case DS_STRING:
1315 return get_byte_string (view, offset);
1316 case DS_NONE:
1317 return get_byte_none (view, offset);
1319 assert(!"Unknown datasource type");
1320 return -1;
1323 static inline int
1324 get_byte_indexed (WView *view, offset_type base, offset_type ofs)
1326 if (base <= OFFSETTYPE_MAX - ofs)
1327 return get_byte (view, base + ofs);
1328 return -1;
1331 static void
1332 view_set_byte (WView *view, offset_type offset, byte b)
1334 (void) &b;
1335 assert (offset < view_get_filesize (view));
1336 assert (view->datasource == DS_FILE);
1337 view->ds_file_datalen = 0; /* just force reloading */
1340 static void
1341 view_set_datasource_none (WView *view)
1343 view->datasource = DS_NONE;
1346 static void
1347 view_set_datasource_vfs_pipe (WView *view, int fd)
1349 assert (fd != -1);
1350 view->datasource = DS_VFS_PIPE;
1351 view->ds_vfs_pipe = fd;
1353 view_init_growbuf (view);
1356 static void
1357 view_set_datasource_stdio_pipe (WView *view, FILE *fp)
1359 assert (fp != NULL);
1360 view->datasource = DS_STDIO_PIPE;
1361 view->ds_stdio_pipe = fp;
1363 view_init_growbuf (view);
1366 static void
1367 view_set_datasource_string (WView *view, const char *s)
1369 view->datasource = DS_STRING;
1370 view->ds_string_data = (byte *) g_strdup (s);
1371 view->ds_string_len = strlen (s);
1374 static void
1375 view_set_datasource_file (WView *view, int fd, const struct stat *st)
1377 view->datasource = DS_FILE;
1378 view->ds_file_fd = fd;
1379 view->ds_file_filesize = st->st_size;
1380 view->ds_file_offset = 0;
1381 view->ds_file_data = g_malloc (4096);
1382 view->ds_file_datalen = 0;
1383 view->ds_file_datasize = 4096;
1386 static void
1387 view_close_datasource (WView *view)
1389 switch (view->datasource) {
1390 case DS_NONE:
1391 break;
1392 case DS_STDIO_PIPE:
1393 if (view->ds_stdio_pipe != NULL) {
1394 (void) pclose (view->ds_stdio_pipe);
1395 display (view);
1396 close_error_pipe (D_NORMAL, NULL);
1397 view->ds_stdio_pipe = NULL;
1399 view_growbuf_free (view);
1400 break;
1401 case DS_VFS_PIPE:
1402 if (view->ds_vfs_pipe != -1) {
1403 (void) mc_close (view->ds_vfs_pipe);
1404 view->ds_vfs_pipe = -1;
1406 view_growbuf_free (view);
1407 break;
1408 case DS_FILE:
1409 (void) mc_close (view->ds_file_fd);
1410 view->ds_file_fd = -1;
1411 g_free (view->ds_file_data);
1412 view->ds_file_data = NULL;
1413 break;
1414 case DS_STRING:
1415 g_free (view->ds_string_data);
1416 view->ds_string_data = NULL;
1417 break;
1418 default:
1419 assert (!"Unknown datasource type");
1421 view->datasource = DS_NONE;
1424 /* {{{ Cursor Movement }}} */
1427 The following variables have to do with the current position and are
1428 updated by the cursor movement functions.
1430 In hex view and wrapped text view mode, dpy_start marks the offset of
1431 the top-left corner on the screen, in non-wrapping text mode it is
1432 the beginning of the current line. In hex mode, hex_cursor is the
1433 offset of the cursor. In non-wrapping text mode, dpy_text_column is
1434 the number of columns that are hidden on the left side on the screen.
1436 In hex mode, dpy_start is updated by the view_fix_cursor_position()
1437 function in order to keep the other functions simple. In
1438 non-wrapping text mode dpy_start and dpy_text_column are normalized
1439 such that dpy_text_column < view_get_datacolumns().
1442 /* prototypes for functions used by view_moveto_bottom() */
1443 static void view_move_up (WView *, offset_type);
1444 static void view_moveto_bol (WView *);
1446 /* set view->first_showed_line and view->dpy_start
1447 * use view->dpy_text_column in nowrap mode */
1448 static void
1449 view_set_first_showed (WView *view, struct cache_line *line)
1451 if (view->text_wrap_mode) {
1452 view->dpy_start = line->start;
1453 view->first_showed_line = line;
1454 } else {
1455 view->dpy_start = view_column_to_offset (view, &line, view->dpy_text_column);
1456 view->first_showed_line = line;
1458 if (view->search_start == view->search_end) {
1459 view->search_start = view->dpy_start;
1460 view->search_end = view->dpy_start;
1464 static void
1465 view_scroll_to_cursor (WView *view)
1467 if (view->hex_mode) {
1468 const offset_type bytes = view->bytes_per_line;
1469 const offset_type displaysize = view->data_area.height * bytes;
1470 const offset_type cursor = view->hex_cursor;
1471 offset_type topleft = view->dpy_start;
1473 if (topleft + displaysize <= cursor)
1474 topleft = offset_rounddown (cursor, bytes)
1475 - (displaysize - bytes);
1476 if (cursor < topleft)
1477 topleft = offset_rounddown (cursor, bytes);
1478 view->dpy_start = topleft;
1479 } else if (view->text_wrap_mode) {
1480 view->dpy_text_column = 0;
1481 } else {
1485 static void
1486 view_movement_fixups (WView *view, gboolean reset_search)
1488 view_scroll_to_cursor (view);
1489 if (reset_search) {
1490 view->search_start = view->dpy_start;
1491 view->search_end = view->dpy_start;
1493 view->dirty++;
1496 static void
1497 view_moveto_top (WView *view)
1499 view->dpy_start = 0;
1500 view->hex_cursor = 0;
1501 view->first_showed_line = view_get_first_line (view);
1502 view->dpy_text_column = 0;
1503 view_movement_fixups (view, TRUE);
1506 static void
1507 view_moveto_bottom (WView *view)
1509 offset_type datalines, lines_up, filesize, last_offset;
1510 struct cache_line *line;
1512 if (view->growbuf_in_use)
1513 view_growbuf_read_until (view, OFFSETTYPE_MAX);
1515 filesize = view_get_filesize (view);
1516 last_offset = offset_doz(filesize, 1);
1517 datalines = view->data_area.height;
1518 lines_up = offset_doz(datalines, 1);
1520 if (view->hex_mode) {
1521 view->hex_cursor = filesize;
1522 view_move_up (view, lines_up);
1523 view->hex_cursor = last_offset;
1524 } else {
1525 line = view_get_last_line (view);
1526 if (!view->text_wrap_mode)
1527 line = view_get_start_of_whole_line (view, line);
1528 view_set_first_showed (view, line);
1529 view->dpy_text_column = 0;
1530 view_move_up (view, lines_up);
1532 view_movement_fixups (view, TRUE);
1535 static void
1536 view_moveto_bol (WView *view)
1538 struct cache_line *line;
1540 if (view->hex_mode) {
1541 view->hex_cursor -= view->hex_cursor % view->bytes_per_line;
1542 } else if (view->text_wrap_mode) {
1543 /* do nothing */
1544 } else {
1545 line = view_get_first_showed_line (view);
1546 line = view_get_start_of_whole_line (view, line);
1547 view->dpy_text_column = 0;
1548 view_set_first_showed (view, line);
1550 view_movement_fixups (view, TRUE);
1553 static void
1554 view_moveto_eol (WView *view)
1556 const screen_dimen width = view->data_area.width;
1557 struct cache_line *line;
1558 screen_dimen w;
1560 if (view->hex_mode) {
1561 offset_type filesize, bol;
1563 bol = offset_rounddown (view->hex_cursor, view->bytes_per_line);
1564 if (get_byte_indexed (view, bol, view->bytes_per_line - 1) != -1) {
1565 view->hex_cursor = bol + view->bytes_per_line - 1;
1566 } else {
1567 filesize = view_get_filesize (view);
1568 view->hex_cursor = offset_doz(filesize, 1);
1570 } else if (view->text_wrap_mode) {
1571 /* nothing to do */
1572 } else {
1573 line = view_get_first_showed_line (view);
1574 line = view_get_start_of_whole_line (view, line);
1575 w = view_width_of_whole_line (view, line);
1576 if (w > width) {
1577 view->dpy_text_column = w - width;
1578 } else {
1579 /* if (w + width <= view->dpy_text_column) {*/
1580 view->dpy_text_column = 0;
1581 /* }*/
1583 view_set_first_showed (view, line);
1585 view_movement_fixups (view, FALSE);
1588 static void
1589 view_moveto_offset (WView *view, offset_type offset)
1591 struct cache_line *line;
1593 if (view->hex_mode) {
1594 view->hex_cursor = offset;
1595 view->dpy_start = offset - offset % view->bytes_per_line;
1596 } else {
1597 line = view_offset_to_line (view, offset);
1598 view->dpy_start = (view->text_wrap_mode) ? line->start : offset;
1599 view->first_showed_line = line;
1600 view->dpy_text_column = (view->text_wrap_mode) ?
1601 0 : view_offset_to_column (view, line, offset);
1603 view_movement_fixups (view, TRUE);
1606 static void
1607 view_moveto (WView *view, offset_type row, offset_type col)
1609 struct cache_line *act;
1610 struct cache_line *t;
1612 act = view_get_first_line (view);
1613 view_move_to_stop (act, t, (off_t)act->number != row, view_get_next_line)
1615 view->dpy_text_column = (view->text_wrap_mode) ? 0 : col;
1616 view->dpy_start = view_column_to_offset (view, &act, col);
1617 view->dpy_start = (view->text_wrap_mode) ? act->start : view->dpy_start;
1618 view->first_showed_line = act;
1620 if (view->hex_mode)
1621 view_moveto_offset (view, view->dpy_start);
1624 /* extendet view_move_to_stop, now has counter, too */
1625 #define view_count_to_stop(l,t,i,stop,nf)\
1626 while (((t = nf(view, l)) != NULL) && (stop)) {\
1627 l = t;i++;}
1629 static void
1630 view_move_up (WView *view, offset_type lines)
1632 struct cache_line *line, *t;
1633 off_t li;
1635 if (view->hex_mode) {
1636 offset_type bytes = lines * view->bytes_per_line;
1637 if (view->hex_cursor >= bytes) {
1638 view->hex_cursor -= bytes;
1639 if (view->hex_cursor < view->dpy_start)
1640 view->dpy_start = offset_doz (view->dpy_start, bytes);
1641 } else {
1642 view->hex_cursor %= view->bytes_per_line;
1644 } else if (view->text_wrap_mode) {
1645 line = view_get_first_showed_line (view);
1646 li = 0;
1647 view_count_to_stop (line, t, li, (li < lines), view_get_previous_line)
1648 view_set_first_showed (view, line);
1649 } else {
1650 line = view_get_first_showed_line (view);
1651 li = 0;
1652 view_count_to_stop (line, t, li, (li < lines), view_get_previous_whole_line)
1653 view_set_first_showed (view, line);
1655 view_movement_fixups (view, (lines != 1));
1658 static void
1659 return_up (struct cache_line * (*ne) (WView *, struct cache_line *),
1660 struct cache_line * (*pr) (WView *, struct cache_line *),
1661 off_t *li, struct cache_line **t, struct cache_line **line,
1662 WView *view)
1664 *li = 0;
1665 *t = *line;
1666 while ((*t != NULL) && (*li < view->data_area.height)) {
1667 (*li)++;
1668 *t = ne (view, *t);
1670 *li = view->data_area.height - *li;
1671 *t = pr (view, *line);
1672 while ((*t != NULL) && (*li > 0)) {
1673 *line = *t;
1674 *t = pr (view, *line);
1675 (*li)--;
1679 static void
1680 view_move_down (WView *view, offset_type lines)
1682 struct cache_line *line;
1683 struct cache_line *t;
1684 off_t li;
1687 if (view->hex_mode) {
1688 offset_type i, limit, last_byte;
1690 last_byte = view_get_filesize (view);
1691 if (last_byte >= (offset_type) view->bytes_per_line)
1692 limit = last_byte - view->bytes_per_line;
1693 else
1694 limit = 0;
1695 for (i = 0; i < lines && view->hex_cursor < limit; i++) {
1696 view->hex_cursor += view->bytes_per_line;
1697 if (lines != 1)
1698 view->dpy_start += view->bytes_per_line;
1701 } else if (view->text_wrap_mode) {
1702 line = view_get_first_showed_line (view);
1703 li = 0;
1704 view_count_to_stop (line, t, li, (off_t)li < lines, view_get_next_line)
1706 /* return_up (view_get_next_line, view_get_previous_line); */
1707 view_set_first_showed (view, line);
1708 } else {
1709 line = view_get_first_showed_line (view);
1710 li = 0;
1711 view_count_to_stop (line, t, li, li < lines, view_get_next_whole_line)
1713 /* return_up (view_get_next_whole_line, view_get_previous_whole_line); */
1714 view_set_first_showed (view, line);
1716 view_movement_fixups (view, (lines != 1));
1719 static void
1720 view_move_left (WView *view, offset_type columns)
1722 struct cache_line *line;
1724 if (view->hex_mode) {
1725 assert (columns == 1);
1726 if (view->hexview_in_text || !view->hexedit_lownibble) {
1727 if (view->hex_cursor > 0)
1728 view->hex_cursor--;
1730 if (!view->hexview_in_text)
1731 view->hexedit_lownibble = !view->hexedit_lownibble;
1732 } else if (view->text_wrap_mode) {
1733 /* nothing to do */
1734 } else {
1735 if (view->dpy_text_column >= columns)
1736 view->dpy_text_column-= columns;
1737 else
1738 view->dpy_text_column = 0;
1740 line = view_get_first_showed_line (view);
1741 view_set_first_showed (view, line);
1743 view_movement_fixups (view, FALSE);
1746 static void
1747 view_move_right (WView *view, offset_type columns)
1749 struct cache_line *line;
1751 if (view->hex_mode) {
1752 assert (columns == 1);
1753 if (view->hexview_in_text || view->hexedit_lownibble) {
1754 if (get_byte_indexed (view, view->hex_cursor, 1) != -1)
1755 view->hex_cursor++;
1757 if (!view->hexview_in_text)
1758 view->hexedit_lownibble = !view->hexedit_lownibble;
1759 } else if (view->text_wrap_mode) {
1760 /* nothing to do */
1761 } else {
1762 view->dpy_text_column += columns;
1763 line = view_get_first_showed_line (view);
1764 view_set_first_showed (view, line);
1766 view_movement_fixups (view, FALSE);
1769 /* {{{ Toggling of viewer modes }}} */
1771 static void
1772 view_toggle_hex_mode (WView *view)
1774 struct cache_line *line;
1776 view->hex_mode = !view->hex_mode;
1777 view->search_type = (view->hex_mode)?MC_SEARCH_T_HEX:MC_SEARCH_T_NORMAL;
1779 if (view->hex_mode) {
1780 view->hex_cursor = view->dpy_start;
1781 view->dpy_start =
1782 offset_rounddown (view->dpy_start, view->bytes_per_line);
1783 view->widget.options |= W_WANT_CURSOR;
1784 } else {
1785 line = view_offset_to_line (view, view->hex_cursor);
1786 view->dpy_text_column = (view->text_wrap_mode) ? 0 :
1787 view_offset_to_column (view, line, view->hex_cursor);
1788 view_set_first_showed (view, line);
1789 view->widget.options &= ~W_WANT_CURSOR;
1791 altered_hex_mode = 1;
1792 view->dpy_bbar_dirty = TRUE;
1793 view->dirty++;
1796 static void
1797 view_toggle_hexedit_mode (WView *view)
1799 view->hexedit_mode = !view->hexedit_mode;
1800 view->dpy_bbar_dirty = TRUE;
1801 view->dirty++;
1804 static void
1805 view_toggle_wrap_mode (WView *view)
1807 struct cache_line *line;
1809 view->text_wrap_mode = !view->text_wrap_mode;
1810 if (view->text_wrap_mode) {
1811 view->dpy_text_column = 0;
1812 view->dpy_start = view_get_first_showed_line (view)->start;
1813 } else {
1814 line = view_get_first_showed_line (view);
1815 view->dpy_text_column = view_width_of_whole_line_before (view, line);
1817 view->dpy_bbar_dirty = TRUE;
1818 view->dirty++;
1821 static void
1822 view_toggle_nroff_mode (WView *view)
1824 struct cache_line *line;
1825 struct cache_line *next;
1827 view->text_nroff_mode = !view->text_nroff_mode;
1828 altered_nroff_flag = 1;
1829 view->dpy_bbar_dirty = TRUE;
1830 view->dirty++;
1832 line = view_get_first_line (view);
1833 view_move_to_stop (line, next, line->end <= view->dpy_start, view_get_next_line)
1835 view_set_first_showed (view, line);
1838 static void
1839 view_toggle_magic_mode (WView *view)
1841 char *filename, *command;
1843 altered_magic_flag = 1;
1844 view->magic_mode = !view->magic_mode;
1845 filename = g_strdup (view->filename);
1846 command = g_strdup (view->command);
1848 view_done (view);
1849 view_load (view, command, filename, 0);
1850 g_free (filename);
1851 g_free (command);
1852 view->dpy_bbar_dirty = TRUE;
1853 view->dirty++;
1856 /* {{{ Miscellaneous functions }}} */
1858 static void
1859 view_done (WView *view)
1861 /* Save current file position */
1862 if (mcview_remember_file_position && view->filename != NULL) {
1863 struct cache_line *line;
1864 char *canon_fname;
1865 offset_type row, col;
1867 canon_fname = vfs_canon (view->filename);
1868 line = view_get_first_showed_line (view);
1869 row = line->number + 1;
1870 col = view_offset_to_column (view, line, view->dpy_start);
1872 save_file_position (canon_fname, row, col);
1873 g_free (canon_fname);
1876 /* Write back the global viewer mode */
1877 default_hex_mode = view->hex_mode;
1878 default_nroff_flag = view->text_nroff_mode;
1879 default_magic_flag = view->magic_mode;
1880 global_wrap_mode = view->text_wrap_mode;
1883 /* view->widget needs no destructor */
1885 g_free (view->filename), view->filename = NULL;
1886 g_free (view->command), view->command = NULL;
1888 view_close_datasource (view);
1889 /* the growing buffer is freed with the datasource */
1891 view_hexedit_free_change_list (view);
1893 g_free(view->last_search_string);
1894 view->last_search_string = NULL;
1896 /* Free memory used by the viewer */
1897 view_reduce_cache_lines (view);
1898 if (view->converter != str_cnv_from_term) str_close_conv (view->converter);
1902 static void
1903 view_show_error (WView *view, const char *msg)
1905 view_close_datasource (view);
1906 if (view_is_in_panel (view)) {
1907 view_set_datasource_string (view, msg);
1908 } else {
1909 message (D_ERROR, MSG_ERROR, "%s", msg);
1913 static gboolean
1914 view_load_command_output (WView *view, const char *command)
1916 FILE *fp;
1918 view_close_datasource (view);
1920 open_error_pipe ();
1921 if ((fp = popen (command, "r")) == NULL) {
1922 /* Avoid two messages. Message from stderr has priority. */
1923 display (view);
1924 if (!close_error_pipe (view_is_in_panel (view) ? -1 : D_ERROR, NULL))
1925 view_show_error (view, _(" Cannot spawn child process "));
1926 return FALSE;
1929 /* First, check if filter produced any output */
1930 view_set_datasource_stdio_pipe (view, fp);
1931 if (get_byte (view, 0) == -1) {
1932 view_close_datasource (view);
1934 /* Avoid two messages. Message from stderr has priority. */
1935 display (view);
1936 if (!close_error_pipe (view_is_in_panel (view) ? -1 : D_ERROR, NULL))
1937 view_show_error (view, _("Empty output from child filter"));
1938 return FALSE;
1940 return TRUE;
1943 gboolean
1944 view_load (WView *view, const char *command, const char *file,
1945 int start_line)
1947 int i, type;
1948 int fd = -1;
1949 char tmp[BUF_MEDIUM];
1950 const char *enc;
1951 char *canon_fname;
1952 struct stat st;
1953 gboolean retval = FALSE;
1955 assert (view->bytes_per_line != 0);
1956 view_done (view);
1959 /* Set up the state */
1960 view_set_datasource_none (view);
1961 view->filename = g_strdup (file);
1962 view->command = 0;
1964 /* Clear the markers */
1965 view->marker = 0;
1966 for (i = 0; i < 10; i++)
1967 view->marks[i] = 0;
1969 if (!view_is_in_panel (view)) {
1970 view->dpy_text_column = 0;
1973 if (command && (view->magic_mode || file == NULL || file[0] == '\0')) {
1974 retval = view_load_command_output (view, command);
1975 } else if (file != NULL && file[0] != '\0') {
1976 /* Open the file */
1977 if ((fd = mc_open (file, O_RDONLY | O_NONBLOCK)) == -1) {
1978 g_snprintf (tmp, sizeof (tmp), _(" Cannot open \"%s\"\n %s "),
1979 file, unix_error_string (errno));
1980 view_show_error (view, tmp);
1981 g_free (view->filename);
1982 view->filename = NULL;
1983 goto finish;
1986 /* Make sure we are working with a regular file */
1987 if (mc_fstat (fd, &st) == -1) {
1988 mc_close (fd);
1989 g_snprintf (tmp, sizeof (tmp), _(" Cannot stat \"%s\"\n %s "),
1990 file, unix_error_string (errno));
1991 view_show_error (view, tmp);
1992 g_free (view->filename);
1993 view->filename = NULL;
1994 goto finish;
1997 if (!S_ISREG (st.st_mode)) {
1998 mc_close (fd);
1999 view_show_error (view, _(" Cannot view: not a regular file "));
2000 g_free (view->filename);
2001 view->filename = NULL;
2002 goto finish;
2005 if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1) {
2006 /* Must be one of those nice files that grow (/proc) */
2007 view_set_datasource_vfs_pipe (view, fd);
2008 } else {
2009 type = get_compression_type (fd);
2011 if (view->magic_mode && (type != COMPRESSION_NONE)) {
2012 g_free (view->filename);
2013 view->filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
2015 view_set_datasource_file (view, fd, &st);
2017 retval = TRUE;
2020 finish:
2021 view->command = g_strdup (command);
2022 view->dpy_start = 0;
2023 view->search_start = 0;
2024 view->search_end = 0;
2025 view->dpy_text_column = 0;
2027 view->converter = str_cnv_from_term;
2028 /* try detect encoding from path */
2029 if (view->filename != NULL) {
2030 canon_fname = vfs_canon (view->filename);
2031 enc = vfs_get_encoding (canon_fname);
2032 if (enc != NULL) {
2033 view->converter = str_crt_conv_from (enc);
2034 if (view->converter == INVALID_CONV)
2035 view->converter = str_cnv_from_term;
2037 g_free (canon_fname);
2040 view_compute_areas (view);
2041 view_reset_cache_lines (view);
2042 view->first_showed_line = view_get_first_line (view);
2044 assert (view->bytes_per_line != 0);
2045 if (mcview_remember_file_position && view->filename != NULL && start_line == 0) {
2046 long line, col;
2048 canon_fname = vfs_canon (view->filename);
2049 load_file_position (file, &line, &col);
2050 g_free (canon_fname);
2051 view_moveto (view, offset_doz(line, 1), col);
2052 } else if (start_line > 0) {
2053 view_moveto (view, start_line - 1, 0);
2056 view->hexedit_lownibble = FALSE;
2057 view->hexview_in_text = FALSE;
2058 view->change_list = NULL;
2060 return retval;
2063 /* {{{ Display management }}} */
2065 static void
2066 view_update_bytes_per_line (WView *view)
2068 const screen_dimen cols = view->data_area.width;
2069 int bytes;
2071 if (cols < 8 + 17)
2072 bytes = 4;
2073 else
2074 bytes = 4 * ((cols - 8) / ((cols < 80) ? 17 : 18));
2075 assert(bytes != 0);
2077 view->bytes_per_line = bytes;
2078 view->dirty = max_dirt_limit + 1; /* To force refresh */
2081 static void
2082 view_percent (WView *view, offset_type p)
2084 const screen_dimen top = view->status_area.top;
2085 const screen_dimen right = view->status_area.left + view->status_area.width;
2086 const screen_dimen height = view->status_area.height;
2087 int percent;
2088 offset_type filesize;
2090 if (height < 1 || right < 4)
2091 return;
2092 if (view_may_still_grow (view))
2093 return;
2094 filesize = view_get_filesize (view);
2096 if (filesize == 0 || view->dpy_end == filesize)
2097 percent = 100;
2098 else if (p > (INT_MAX / 100))
2099 percent = p / (filesize / 100);
2100 else
2101 percent = p * 100 / filesize;
2103 widget_move (view, top, right - 4);
2104 tty_printf ("%3d%%", percent);
2107 static void
2108 view_display_status (WView *view)
2110 const screen_dimen top = view->status_area.top;
2111 const screen_dimen left = view->status_area.left;
2112 const screen_dimen width = view->status_area.width;
2113 const screen_dimen height = view->status_area.height;
2114 const char *file_label, *file_name;
2115 screen_dimen file_label_width;
2116 int i;
2117 char *tmp;
2119 if (height < 1)
2120 return;
2122 tty_setcolor (SELECTED_COLOR);
2123 widget_move (view, top, left);
2124 hline (' ', width);
2126 file_label = _("File: %s");
2127 file_label_width = str_term_width1 (file_label) - 2;
2128 file_name = view->filename ? view->filename
2129 : view->command ? view->command
2130 : "";
2132 if (width < file_label_width + 6)
2133 addstr (str_fit_to_term (file_name, width, J_LEFT_FIT));
2134 else {
2135 i = (width > 22 ? 22 : width) - file_label_width;
2137 tmp = g_strdup_printf (file_label, str_fit_to_term (file_name, i, J_LEFT_FIT));
2138 addstr (tmp);
2139 g_free (tmp);
2140 if (width > 46) {
2141 widget_move (view, top, left + 24);
2142 /* FIXME: the format strings need to be changed when offset_type changes */
2143 if (view->hex_mode)
2144 tty_printf (_("Offset 0x%08lx"), (unsigned long) view->hex_cursor);
2145 else {
2146 screen_dimen row, col;
2147 struct cache_line *line;
2149 line = view_get_first_showed_line (view);
2150 row = line->number + 1;
2152 col = (view->text_wrap_mode) ?
2153 view_width_of_whole_line_before (view, line) :
2154 view->dpy_text_column;
2155 col++;
2157 tty_printf (_("Line %lu Col %lu"),
2158 (unsigned long) row, (unsigned long) col);
2161 if (width > 62) {
2162 offset_type filesize;
2163 filesize = view_get_filesize (view);
2164 widget_move (view, top, left + 43);
2165 if (!view_may_still_grow (view)) {
2166 tty_printf (_("%s bytes"), size_trunc (filesize));
2167 } else {
2168 tty_printf (_(">= %s bytes"), size_trunc (filesize));
2171 if (width > 26) {
2172 view_percent (view, view->hex_mode
2173 ? view->hex_cursor
2174 : view->dpy_end);
2177 tty_setcolor (SELECTED_COLOR);
2180 static inline void
2181 view_display_clean (WView *view)
2183 tty_setcolor (NORMAL_COLOR);
2184 widget_erase ((Widget *) view);
2185 if (view->dpy_frame_size != 0) {
2186 draw_double_box (view->widget.parent, view->widget.y,
2187 view->widget.x, view->widget.lines,
2188 view->widget.cols);
2192 typedef enum {
2193 MARK_NORMAL,
2194 MARK_SELECTED,
2195 MARK_CURSOR,
2196 MARK_CHANGED
2197 } mark_t;
2199 static inline int
2200 view_count_backspaces (WView *view, off_t offset)
2202 int backspaces = 0;
2203 while (offset >= 2 * backspaces
2204 && get_byte (view, offset - 2 * backspaces) == '\b')
2205 backspaces++;
2206 return backspaces;
2209 static void
2210 view_display_ruler (WView *view)
2212 static const char ruler_chars[] = "|----*----";
2213 const screen_dimen top = view->ruler_area.top;
2214 const screen_dimen left = view->ruler_area.left;
2215 const screen_dimen width = view->ruler_area.width;
2216 const screen_dimen height = view->ruler_area.height;
2217 const screen_dimen line_row = (ruler == RULER_TOP) ? 0 : 1;
2218 const screen_dimen nums_row = (ruler == RULER_TOP) ? 1 : 0;
2220 char r_buff[10];
2221 offset_type cl;
2222 screen_dimen c;
2224 if (ruler == RULER_NONE || height < 1)
2225 return;
2227 tty_setcolor (MARKED_COLOR);
2228 for (c = 0; c < width; c++) {
2229 cl = view->dpy_text_column + c;
2230 if (line_row < height) {
2231 widget_move (view, top + line_row, left + c);
2232 tty_print_char (ruler_chars[cl % 10]);
2235 if ((cl != 0) && (cl % 10) == 0) {
2236 g_snprintf (r_buff, sizeof (r_buff), "%"OFFSETTYPE_PRId, cl);
2237 if (nums_row < height) {
2238 widget_move (view, top + nums_row, left + c - 1);
2239 tty_print_string (r_buff);
2243 attrset (NORMAL_COLOR);
2246 static void
2247 view_display_hex (WView *view)
2249 const screen_dimen top = view->data_area.top;
2250 const screen_dimen left = view->data_area.left;
2251 const screen_dimen height = view->data_area.height;
2252 const screen_dimen width = view->data_area.width;
2253 const int ngroups = view->bytes_per_line / 4;
2254 const screen_dimen text_start =
2255 8 + 13 * ngroups + ((width < 80) ? 0 : (ngroups - 1 + 1));
2256 /* 8 characters are used for the file offset, and every hex group
2257 * takes 13 characters. On ``big'' screens, the groups are separated
2258 * by an extra vertical line, and there is an extra space before the
2259 * text column.
2262 screen_dimen row, col;
2263 offset_type from;
2264 int c;
2265 mark_t boldflag = MARK_NORMAL;
2266 struct hexedit_change_node *curr = view->change_list;
2267 size_t i;
2269 char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */
2270 int bytes; /* Number of bytes already printed on the line */
2272 view_display_clean (view);
2274 /* Find the first displayable changed byte */
2275 from = view->dpy_start;
2276 while (curr && (curr->offset < from)) {
2277 curr = curr->next;
2280 for (row = 0; get_byte (view, from) != -1 && row < height; row++) {
2281 col = 0;
2283 /* Print the hex offset */
2284 g_snprintf (hex_buff, sizeof (hex_buff), "%08"OFFSETTYPE_PRIX" ", from);
2285 widget_move (view, top + row, left);
2286 tty_setcolor (MARKED_COLOR);
2287 for (i = 0; col < width && hex_buff[i] != '\0'; i++) {
2288 addch (hex_buff[i]);
2289 /* tty_print_char(hex_buff[i]);*/
2290 col += 1;
2292 tty_setcolor (NORMAL_COLOR);
2294 for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++) {
2296 if ((c = get_byte (view, from)) == -1)
2297 break;
2299 /* Save the cursor position for view_place_cursor() */
2300 if (from == view->hex_cursor && !view->hexview_in_text) {
2301 view->cursor_row = row;
2302 view->cursor_col = col;
2305 /* Determine the state of the current byte */
2306 boldflag =
2307 (from == view->hex_cursor) ? MARK_CURSOR
2308 : (curr != NULL && from == curr->offset) ? MARK_CHANGED
2309 : (view->search_start <= from &&
2310 from < view->search_end) ? MARK_SELECTED
2311 : MARK_NORMAL;
2313 /* Determine the value of the current byte */
2314 if (curr != NULL && from == curr->offset) {
2315 c = curr->value;
2316 curr = curr->next;
2319 /* Select the color for the hex number */
2320 tty_setcolor (
2321 boldflag == MARK_NORMAL ? NORMAL_COLOR :
2322 boldflag == MARK_SELECTED ? MARKED_COLOR :
2323 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
2324 /* boldflag == MARK_CURSOR */
2325 view->hexview_in_text ? MARKED_SELECTED_COLOR :
2326 VIEW_UNDERLINED_COLOR);
2328 /* Print the hex number */
2329 widget_move (view, top + row, left + col);
2330 if (col < width) {
2331 tty_print_char (hex_char[c / 16]);
2332 col += 1;
2334 if (col < width) {
2335 tty_print_char (hex_char[c % 16]);
2336 col += 1;
2339 /* Print the separator */
2340 tty_setcolor (NORMAL_COLOR);
2341 if (bytes != view->bytes_per_line - 1) {
2342 if (col < width) {
2343 tty_print_char (' ');
2344 col += 1;
2347 /* After every four bytes, print a group separator */
2348 if (bytes % 4 == 3) {
2349 if (view->data_area.width >= 80 && col < width) {
2350 tty_print_one_vline ();
2351 col += 1;
2353 if (col < width) {
2354 tty_print_char (' ');
2355 col += 1;
2360 /* Select the color for the character; this differs from the
2361 * hex color when boldflag == MARK_CURSOR */
2362 tty_setcolor (
2363 boldflag == MARK_NORMAL ? NORMAL_COLOR :
2364 boldflag == MARK_SELECTED ? MARKED_COLOR :
2365 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
2366 /* boldflag == MARK_CURSOR */
2367 view->hexview_in_text ? VIEW_UNDERLINED_COLOR :
2368 MARKED_SELECTED_COLOR);
2370 c = convert_to_display_c (c);
2371 if (!g_ascii_isprint (c))
2372 c = '.';
2374 /* Print corresponding character on the text side */
2375 if (text_start + bytes < width) {
2376 widget_move (view, top + row, left + text_start + bytes);
2377 tty_print_char (c);
2380 /* Save the cursor position for view_place_cursor() */
2381 if (from == view->hex_cursor && view->hexview_in_text) {
2382 view->cursor_row = row;
2383 view->cursor_col = text_start + bytes;
2388 /* Be polite to the other functions */
2389 tty_setcolor (NORMAL_COLOR);
2391 view_place_cursor (view);
2392 view->dpy_end = from;
2395 static void
2396 view_display_text (WView * view)
2398 #define cmp(t1,t2) (strcmp((t1),(t2)) == 0)
2400 const screen_dimen left = view->data_area.left;
2401 const screen_dimen top = view->data_area.top;
2402 const screen_dimen width = view->data_area.width;
2403 const screen_dimen height = view->data_area.height;
2404 struct read_info info;
2405 offset_type row, col;
2406 int w;
2407 struct cache_line *line_act;
2408 struct cache_line *line_nxt;
2410 view_display_clean (view);
2411 view_display_ruler (view);
2413 tty_setcolor (NORMAL_COLOR);
2415 widget_move (view, top, left);
2417 line_act = view_get_first_showed_line (view);
2419 row = 0;
2420 /* set col correct value */
2421 col = (view->text_wrap_mode) ? 0 : view_width_of_whole_line_before (view, line_act);
2422 col+= line_act->left;
2424 view_read_start (view, &info, line_act->start);
2425 while ((info.result != -1) && (row < height)) {
2426 /* real detection of new line */
2427 if (info.next >= line_act->end) {
2428 line_nxt = view_get_next_line (view, line_act);
2429 if (line_nxt == NULL) break;
2431 if (view->text_wrap_mode || (line_act->number != line_nxt->number)){
2432 row++;
2433 col = line_nxt->left;
2435 line_act = line_nxt;
2437 continue;
2440 view_read_continue (view, &info);
2441 if (view_read_test_nroff_back (view, &info)) {
2442 int c;
2444 w = str_term_width1 (info.chi1);
2445 col-= w;
2446 if (col >= view->dpy_text_column
2447 && col + w - view->dpy_text_column <= width) {
2449 widget_move (view, top + row, left + (col - view->dpy_text_column));
2450 for (c = 0; c < w; c++) addch (' ');
2452 if (cmp (info.chi1, "_") && (!cmp (info.cnxt, "_") || !cmp (info.chi2, "\b")))
2453 tty_setcolor (VIEW_UNDERLINED_COLOR);
2454 else
2455 tty_setcolor (MARKED_COLOR);
2456 continue;
2459 if (view_read_test_new_line (view, &info))
2460 continue;
2463 if (view_read_test_tabulator (view, &info)) {
2464 col+= (8 - (col % 8));
2465 continue;
2468 if (view->search_start <= info.actual
2469 && info.actual < view->search_end) {
2470 tty_setcolor (SELECTED_COLOR);
2473 w = str_isprint (info.cact) ? str_term_width1 (info.cact) : 1;
2475 if (col >= view->dpy_text_column
2476 && col + w - view->dpy_text_column <= width) {
2477 widget_move (view, top + row, left + (col - view->dpy_text_column));
2479 if (!str_iscombiningmark (info.cnxt)) {
2480 if (str_isprint (info.cact)) {
2481 addstr (str_term_form (info.cact));
2482 } else {
2483 addch ('.');
2485 } else {
2486 GString *comb = g_string_new ("");
2487 if (str_isprint (info.cact)) {
2488 g_string_append(comb,info.cact);
2489 } else {
2490 g_string_append(comb,".");
2492 while (str_iscombiningmark (info.cnxt)) {
2493 view_read_continue (view, &info);
2494 g_string_append(comb,info.cact);
2496 addstr (str_term_form (comb->str));
2497 g_string_free (comb, TRUE);
2499 } else {
2500 while (str_iscombiningmark (info.cnxt)) {
2501 view_read_continue (view, &info);
2504 col+= w;
2506 tty_setcolor (NORMAL_COLOR);
2508 view->dpy_end = info.next;
2511 /* Displays as much data from view->dpy_start as fits on the screen */
2512 static void
2513 display (WView *view)
2515 view_compute_areas (view);
2516 if (view->hex_mode) {
2517 view_display_hex (view);
2518 } else {
2519 view_display_text (view);
2521 view_display_status (view);
2524 static void
2525 view_place_cursor (WView *view)
2527 const screen_dimen top = view->data_area.top;
2528 const screen_dimen left = view->data_area.left;
2529 screen_dimen col;
2531 col = view->cursor_col;
2532 if (!view->hexview_in_text && view->hexedit_lownibble)
2533 col++;
2534 widget_move (&view->widget, top + view->cursor_row, left + col);
2537 static void
2538 view_update (WView *view)
2540 static int dirt_limit = 1;
2542 if (view->dpy_bbar_dirty) {
2543 view->dpy_bbar_dirty = FALSE;
2544 view_labels (view);
2545 buttonbar_redraw (view->widget.parent);
2548 if (view->dirty > dirt_limit) {
2549 /* Too many updates skipped -> force a update */
2550 display (view);
2551 view->dirty = 0;
2552 /* Raise the update skipping limit */
2553 dirt_limit++;
2554 if (dirt_limit > max_dirt_limit)
2555 dirt_limit = max_dirt_limit;
2557 if (view->dirty) {
2558 if (is_idle ()) {
2559 /* We have time to update the screen properly */
2560 display (view);
2561 view->dirty = 0;
2562 if (dirt_limit > 1)
2563 dirt_limit--;
2564 } else {
2565 /* We are busy -> skipping full update,
2566 only the status line is updated */
2567 view_display_status (view);
2569 /* Here we had a refresh, if fast scrolling does not work
2570 restore the refresh, although this should not happen */
2574 /* {{{ Hex editor }}} */
2576 static void
2577 enqueue_change (struct hexedit_change_node **head,
2578 struct hexedit_change_node *node)
2580 /* chnode always either points to the head of the list or
2581 * to one of the ->next fields in the list. The value at
2582 * this location will be overwritten with the new node. */
2583 struct hexedit_change_node **chnode = head;
2585 while (*chnode != NULL && (*chnode)->offset < node->offset)
2586 chnode = &((*chnode)->next);
2588 node->next = *chnode;
2589 *chnode = node;
2592 static cb_ret_t
2593 view_handle_editkey (WView *view, int key)
2595 struct hexedit_change_node *node;
2596 byte byte_val;
2598 /* Has there been a change at this position? */
2599 node = view->change_list;
2600 while (node && (node->offset != view->hex_cursor))
2601 node = node->next;
2603 if (!view->hexview_in_text) {
2604 /* Hex editing */
2605 unsigned int hexvalue = 0;
2607 if (key >= '0' && key <= '9')
2608 hexvalue = 0 + (key - '0');
2609 else if (key >= 'A' && key <= 'F')
2610 hexvalue = 10 + (key - 'A');
2611 else if (key >= 'a' && key <= 'f')
2612 hexvalue = 10 + (key - 'a');
2613 else
2614 return MSG_NOT_HANDLED;
2616 if (node)
2617 byte_val = node->value;
2618 else
2619 byte_val = get_byte (view, view->hex_cursor);
2621 if (view->hexedit_lownibble) {
2622 byte_val = (byte_val & 0xf0) | (hexvalue);
2623 } else {
2624 byte_val = (byte_val & 0x0f) | (hexvalue << 4);
2626 } else {
2627 /* Text editing */
2628 if (key < 256 && (is_printable (key) || (key == '\n')))
2629 byte_val = key;
2630 else
2631 return MSG_NOT_HANDLED;
2633 if (!node) {
2634 node = g_new (struct hexedit_change_node, 1);
2635 node->offset = view->hex_cursor;
2636 node->value = byte_val;
2637 enqueue_change (&view->change_list, node);
2638 } else {
2639 node->value = byte_val;
2641 view->dirty++;
2642 view_update (view);
2643 view_move_right (view, 1);
2644 return MSG_HANDLED;
2647 static gboolean
2648 view_hexedit_save_changes (WView *view)
2650 struct hexedit_change_node *curr, *next;
2651 int fp, answer;
2652 char *text, *error;
2654 if (view->change_list == NULL)
2655 return TRUE;
2657 retry_save:
2658 assert (view->filename != NULL);
2659 fp = mc_open (view->filename, O_WRONLY);
2660 if (fp == -1)
2661 goto save_error;
2663 for (curr = view->change_list; curr != NULL; curr = next) {
2664 next = curr->next;
2666 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
2667 || mc_write (fp, &(curr->value), 1) != 1)
2668 goto save_error;
2670 /* delete the saved item from the change list */
2671 view->change_list = next;
2672 view->dirty++;
2673 view_set_byte (view, curr->offset, curr->value);
2674 g_free (curr);
2677 if (mc_close (fp) == -1) {
2678 error = g_strdup (strerror (errno));
2679 message (D_ERROR, _(" Save file "), _(
2680 " Error while closing the file: \n %s \n"
2681 " Data may have been written or not. "), error);
2682 g_free (error);
2684 view_update (view);
2685 return TRUE;
2687 save_error:
2688 error = g_strdup (strerror (errno));
2689 text = g_strdup_printf (_(" Cannot save file: \n %s "), error);
2690 g_free (error);
2691 (void) mc_close (fp);
2693 answer = query_dialog (_(" Save file "), text, D_ERROR,
2694 2, _("&Retry"), _("&Cancel"));
2695 g_free (text);
2697 if (answer == 0)
2698 goto retry_save;
2699 return FALSE;
2702 /* {{{ Miscellaneous functions }}} */
2704 static gboolean
2705 view_ok_to_quit (WView *view)
2707 int r;
2709 if (view->change_list == NULL)
2710 return TRUE;
2712 r = query_dialog (_("Quit"),
2713 _(" File was modified, Save with exit? "), D_NORMAL, 3,
2714 _("&Cancel quit"), _("&Yes"), _("&No"));
2716 switch (r) {
2717 case 1:
2718 return view_hexedit_save_changes (view);
2719 case 2:
2720 view_hexedit_free_change_list (view);
2721 return TRUE;
2722 default:
2723 return FALSE;
2727 static inline void
2728 my_define (Dlg_head *h, int idx, const char *text, void (*fn) (WView *),
2729 WView *view)
2731 buttonbar_set_label_data (h, idx, text, (buttonbarfn) fn, view);
2734 /* {{{ Searching }}} */
2736 /* read one whole line into buffer, return where line start and end */
2737 static int
2738 view_get_line_at (WView *view, offset_type from, GString * buffer,
2739 offset_type *buff_start, offset_type *buff_end)
2741 #define cmp(t1,t2) (strcmp((t1),(t2)) == 0)
2742 struct read_info info;
2743 struct cache_line *line;
2744 offset_type start;
2745 offset_type end;
2747 line = view_get_first_showed_line (view);
2749 line = view_offset_to_line_from (view, from, line);
2751 if (!view->search_backwards) {
2752 start = from;
2753 end = view_get_end_of_whole_line (view, line)->end;
2754 if (start >= end) return 0;
2755 } else {
2756 start = view_get_start_of_whole_line (view, line)->start;
2757 end = from;
2760 (*buff_start) = start;
2761 (*buff_end) = end;
2763 g_string_set_size(buffer,0);
2765 view_read_start (view, &info, start);
2766 while ((info.result != -1) && (info.next < end)) {
2767 view_read_continue (view, &info);
2769 /* if text contains '\0' */
2770 if (cmp (info.cact, "")) {
2771 if (info.actual < from) {
2772 /* '\0' before start offset, continue */
2773 g_string_set_size(buffer,0);
2774 (*buff_start) = info.next;
2775 continue;
2776 } else {
2777 /* '\0' after start offset, end */
2778 (*buff_end) = info.next;
2779 return 1;
2783 if (view_read_test_new_line (view, &info))
2784 continue;
2786 if (view_read_test_nroff_back (view, &info)) {
2787 g_string_truncate (buffer, buffer->len-1);
2788 continue;
2791 g_string_append(buffer,info.cact);
2794 return 1;
2797 /* map search result positions to offsets in text */
2798 void
2799 view_matchs_to_offsets (WView *view, offset_type start, offset_type end,
2800 size_t match_start, size_t match_end,
2801 offset_type *search_start, offset_type *search_end)
2803 struct read_info info;
2804 size_t c = 0;
2806 (*search_start) = INVALID_OFFSET;
2807 (*search_end) = INVALID_OFFSET;
2809 view_read_start (view, &info, start);
2811 while ((info.result != -1) && (info.next < end)) {
2812 view_read_continue (view, &info);
2814 if (view_read_test_nroff_back (view, &info)) {
2815 c-= 1;
2816 continue;
2818 if ((c == match_start) && (*search_start == INVALID_OFFSET))
2819 *search_start = info.actual;
2820 if (c == match_end) (*search_end) = info.actual;
2821 c+= !str_iscombiningmark (info.cact) || (c == 0);
2824 if ((c == match_start) && (*search_start == INVALID_OFFSET)) *search_start = info.next;
2825 if (c == match_end) (*search_end) = info.next;
2828 /* we have set view->search_start and view->search_end and must set
2829 * view->dpy_text_column, view->first_showed_line and view->dpy_start
2830 * try to displaye maximum of match */
2831 void
2832 view_moveto_match (WView *view)
2834 const screen_dimen height = view->data_area.height;
2835 const screen_dimen height3 = height / 3;
2836 const screen_dimen width = view->data_area.width;
2837 struct cache_line *line;
2838 struct cache_line *line_end, *line_start;
2839 struct cache_line *t;
2840 int start_off = -1;
2841 int end_off = -1;
2842 int off = 0;
2844 line = view_get_first_showed_line (view);
2845 if (view->text_wrap_mode) {
2846 if (line->start > view->search_start) {
2847 if (line->start <= view->search_start && line->end > view->search_start)
2848 start_off = 0;
2849 if (line->start <= view->search_end && line->end >= view->search_end)
2850 end_off = 0;
2851 t = view_get_previous_line (view, line);
2852 while ((t != NULL) && ((start_off == -1) || (end_off == -1))) {
2853 line = t;
2854 t = view_get_previous_line (view, line);
2855 off++;
2856 if (line->start <= view->search_start && line->end > view->search_start)
2857 start_off = off;
2858 if (line->start <= view->search_end && line->end >= view->search_end)
2859 end_off = off;
2862 line = view_get_first_showed_line (view);
2864 off = ((off_t)(start_off - end_off) < (off_t)(height - height3))
2865 ? (int)(start_off + height3)
2866 : (int)end_off;
2867 for (;off >= 0 && line->start > 0; off--)
2868 line = view_get_previous_line (view, line);
2869 } else {
2870 /* start_off, end_off - how many cache_lines far are
2871 * view->search_start, end from line */
2872 if (line->start <= view->search_start && line->end > view->search_start)
2873 start_off = 0;
2874 if (line->start <= view->search_end && line->end >= view->search_end)
2875 end_off = 0;
2876 t = view_get_next_line (view, line);
2877 while ((t != NULL) && ((start_off == -1) || (end_off == -1))) {
2878 line = t;
2879 t = view_get_next_line (view, line);
2880 off++;
2881 if (line->start <= view->search_start && line->end > view->search_start)
2882 start_off = off;
2883 if (line->start <= view->search_end && line->end >= view->search_end)
2884 end_off = off;
2887 line = view_get_first_showed_line (view);
2888 /* if view->search_end is farther then screen heigth */
2889 if ((off_t)end_off >= height) {
2890 off = ((off_t)(end_off - start_off) < (off_t)(height - height3))
2891 ? (int) (end_off - height + height3)
2892 : (int) start_off;
2894 for (;off >= 0; off--)
2895 line = view_get_next_line (view, line);
2898 } else {
2899 /* first part similar like in wrap mode,only wokrs with whole lines */
2900 line = view_get_first_showed_line (view);
2901 line = view_get_start_of_whole_line (view, line);
2902 if (line->start > view->search_start) {
2903 line_start = view_get_start_of_whole_line (view, line);
2904 if (line_start->start <= view->search_start && line->end > view->search_start)
2905 start_off = 0;
2906 if (line_start->start <= view->search_end && line->end >= view->search_end)
2907 end_off = 0;
2908 t = view_get_previous_whole_line (view, line_start);
2909 while ((t != NULL) && ((start_off == -1) || (end_off == -1))) {
2910 line = t;
2911 line_start = view_get_start_of_whole_line (view, line);
2912 t = view_get_previous_whole_line (view, line_start);
2913 off++;
2914 if (line_start->start <= view->search_start && line->end > view->search_start)
2915 start_off = off;
2916 if (line_start->start <= view->search_end && line->end >= view->search_end)
2917 end_off = off;
2920 line = view_get_first_showed_line (view);
2921 line = view_get_start_of_whole_line (view, line);
2922 off = ((off_t)(start_off - end_off) < (off_t)(height - height3))
2923 ? (int)(start_off + height3)
2924 : (int)end_off;
2925 for (;off >= 0 && line->start > 0; off--) {
2926 line = view_get_previous_whole_line (view, line);
2927 line = view_get_start_of_whole_line (view, line);
2929 } else {
2930 line_end = view_get_end_of_whole_line (view, line);
2931 if (line->start <= view->search_start && line_end->end > view->search_start)
2932 start_off = 0;
2933 if (line->start <= view->search_end && line_end->end >= view->search_end)
2934 end_off = 0;
2935 t = view_get_next_whole_line (view, line_end);
2936 while ((t != NULL) && ((start_off == -1) || (end_off == -1))) {
2937 line = t;
2938 line_end = view_get_end_of_whole_line (view, line);
2939 t = view_get_next_whole_line (view, line_end);
2940 off++;
2941 if (line->start <= view->search_start && line_end->end > view->search_start)
2942 start_off = off;
2943 if (line->start <= view->search_end && line_end->end >= view->search_end)
2944 end_off = off;
2947 line = view_get_first_showed_line (view);
2948 line = view_get_start_of_whole_line (view, line);
2949 if ((off_t)end_off >= height) {
2950 off = ((off_t)(end_off - start_off) < (off_t)(height - height3))
2951 ? (int)(end_off - height + height3)
2952 : (int)start_off;
2954 for (;off >= 0; off--)
2955 line = view_get_next_whole_line (view, line);
2958 /*now line point to begin of line, that we want show*/
2960 t = view_offset_to_line_from (view, view->search_start, line);
2961 start_off = view_offset_to_column (view, t, view->search_start);
2962 t = view_offset_to_line_from (view, view->search_end, line);
2963 end_off = view_offset_to_column (view, t, view->search_end);
2965 if ((off_t)(end_off - start_off) > width) end_off = start_off + width;
2966 if (view->dpy_text_column > (off_t)start_off) {
2967 view->dpy_text_column = start_off;
2968 } else {
2969 if (view->dpy_text_column + width < (off_t)end_off) {
2970 view->dpy_text_column = end_off - width;
2975 view_set_first_showed (view, line);
2978 static void
2979 search_update_steps (WView *view)
2981 offset_type filesize = view_get_filesize (view);
2982 if (filesize == 0)
2983 view->update_steps = 40000;
2984 else /* viewing a data stream, not a file */
2985 view->update_steps = filesize / 100;
2987 /* Do not update the percent display but every 20 ks */
2988 if (view->update_steps < 20000)
2989 view->update_steps = 20000;
2992 /* {{{ User-definable commands }}} */
2995 The functions in this section can be bound to hotkeys. They are all
2996 of the same type (taking a pointer to WView as parameter and
2997 returning void). TODO: In the not-too-distant future, these commands
2998 will become fully configurable, like they already are in the
2999 internal editor. By convention, all the function names end in
3000 "_cmd".
3003 static void
3004 view_help_cmd (void)
3006 interactive_display (NULL, "[Internal File Viewer]");
3009 /* Toggle between hexview and hexedit mode */
3010 static void
3011 view_toggle_hexedit_mode_cmd (WView *view)
3013 view_toggle_hexedit_mode (view);
3014 view_update (view);
3017 /* Toggle between wrapped and unwrapped view */
3018 static void
3019 view_toggle_wrap_mode_cmd (WView *view)
3021 view_toggle_wrap_mode (view);
3022 view_update (view);
3025 /* Toggle between hex view and text view */
3026 static void
3027 view_toggle_hex_mode_cmd (WView *view)
3029 view_toggle_hex_mode (view);
3030 view_update (view);
3033 static void
3034 view_moveto_line_cmd (WView *view)
3036 char *answer, *answer_end, prompt[BUF_SMALL];
3037 struct cache_line *line;
3038 offset_type row;
3040 line = view_get_first_showed_line (view);
3041 row = line->number + 1;
3043 g_snprintf (prompt, sizeof (prompt),
3044 _(" The current line number is %lu.\n"
3045 " Enter the new line number:"), line->number);
3046 answer = input_dialog (_(" Goto line "), prompt, MC_HISTORY_VIEW_GOTO_LINE, "");
3047 if (answer != NULL && answer[0] != '\0') {
3048 errno = 0;
3049 row = strtoul (answer, &answer_end, 10);
3050 if (*answer_end == '\0' && errno == 0 && row >= 1)
3051 view_moveto (view, row - 1, 0);
3053 g_free (answer);
3054 view->dirty++;
3055 view_update (view);
3058 static void
3059 view_moveto_addr_cmd (WView *view)
3061 char *line, *error, prompt[BUF_SMALL];
3062 offset_type addr;
3064 g_snprintf (prompt, sizeof (prompt),
3065 _(" The current address is 0x%08"OFFSETTYPE_PRIX".\n"
3066 " Enter the new address:"), view->hex_cursor);
3068 line = input_dialog (_(" Goto Address "), prompt, MC_HISTORY_VIEW_GOTO_ADDR, "");
3069 if (line != NULL) {
3070 if (*line != '\0') {
3071 addr = strtoul (line, &error, 0);
3072 if ((*error == '\0') && get_byte (view, addr) != -1) {
3073 view_moveto_offset (view, addr);
3074 } else {
3075 message (D_ERROR, _("Warning"), _(" Invalid address "));
3078 g_free (line);
3080 view->dirty++;
3081 view_update (view);
3084 static void
3085 view_hexedit_save_changes_cmd (WView *view)
3087 (void) view_hexedit_save_changes (view);
3090 static int
3091 view__get_nroff_real_len(WView *view, offset_type start, offset_type length)
3093 offset_type loop1 = 0;
3094 int nroff_seq = 0;
3095 struct read_info info;
3097 view_read_start (view, &info, start);
3098 while((loop1 < length ) && (info.result != -1))
3100 view_read_continue (view, &info);
3101 if (*info.cnxt == '\b')
3103 view_read_continue (view, &info);
3104 view_read_continue (view, &info);
3105 nroff_seq+=2;
3107 loop1++;
3109 return nroff_seq;
3113 static void
3114 do_search (WView *view)
3116 GString *buffer;
3117 offset_type search_start;
3118 int search_status;
3119 Dlg_head *d = NULL;
3121 offset_type line_start;
3122 offset_type line_end;
3123 size_t match_len;
3125 if (verbose) {
3126 d = create_message (D_NORMAL, _("Search"), _("Searching %s"), view->last_search_string);
3127 mc_refresh ();
3130 buffer = g_string_new ("");
3132 search_start = (view->search_backwards) ? view->search_start-1 : view->search_end;
3133 if (view->search_backwards && (int) search_start < 0 )
3134 search_start = 0;
3136 /* Compute the percent steps */
3137 search_update_steps (view);
3138 view->update_activate = 0;
3140 enable_interrupt_key ();
3141 search_status = -1;
3143 while(1){
3144 if (search_start >= view->update_activate) {
3145 view->update_activate += view->update_steps;
3146 if (verbose) {
3147 view_percent (view, search_start);
3148 mc_refresh ();
3150 if (got_interrupt ())
3151 break;
3154 if (!view_get_line_at (view, search_start, buffer, &line_start, &line_end))
3155 break;
3157 if (! mc_search_run( view->search, buffer->str, 0, buffer->len, &match_len )){
3158 if (view->search->error != MC_SEARCH_E_NOTFOUND) {
3159 search_status = -2;
3160 break;
3163 if (! view->search_backwards) {
3164 search_start = line_end;
3165 } else {
3166 if (line_start > 0) search_start = line_start - 1;
3167 else break;
3169 continue;
3171 search_status = 1;
3172 if (view->search_backwards){
3173 search_start = line_start;
3176 view->search_start = search_start + view->search->normal_offset +
3177 view__get_nroff_real_len(view, search_start, view->search->normal_offset);
3179 view->search_end = view->search_start + match_len +
3180 view__get_nroff_real_len(view, view->search_start, match_len);
3182 if (view->hex_mode){
3183 view->hex_cursor = view->search_start;
3184 view->hexedit_lownibble = FALSE;
3185 view->dpy_start = view->search_start - view->search_start % view->bytes_per_line;
3186 view->dpy_end = view->search_end - view->search_end % view->bytes_per_line;
3189 view_moveto_match (view);
3191 break;
3193 disable_interrupt_key ();
3194 if (verbose) {
3195 dlg_run_done (d);
3196 destroy_dlg (d);
3198 switch (search_status)
3200 case -1:
3201 message (D_NORMAL, _("Search"), _(" Search string not found "));
3202 view->search_end = view->search_start;
3203 break;
3204 case -2:
3205 message (D_NORMAL, _("Search"), "%s", view->search->error_str);
3206 view->search_end = view->search_start;
3207 break;
3209 g_string_free (buffer, TRUE);
3211 view->dirty++;
3212 view_update (view);
3216 /* Both views */
3217 static void
3218 view_search_cmd (WView *view)
3220 enum {
3221 SEARCH_DLG_MIN_HEIGHT = 10,
3222 SEARCH_DLG_HEIGHT_SUPPLY = 3,
3223 SEARCH_DLG_WIDTH = 58
3226 char *defval = g_strdup (view->last_search_string != NULL ? view->last_search_string : "");
3227 char *exp = NULL;
3229 int ttype_of_search = (int) view->search_type;
3230 int tall_codepages = (int) view->search_all_codepages;
3231 int tsearch_case = (int) view->search_case;
3232 int tsearch_backwards = (int) view->search_backwards;
3234 gchar **list_of_types = mc_search_get_types_strings_array();
3235 int SEARCH_DLG_HEIGHT = SEARCH_DLG_MIN_HEIGHT + g_strv_length (list_of_types) - SEARCH_DLG_HEIGHT_SUPPLY;
3237 QuickWidget quick_widgets[] = {
3239 {quick_button, 6, 10, SEARCH_DLG_HEIGHT - 3, SEARCH_DLG_HEIGHT, N_("&Cancel"), 0,
3240 B_CANCEL, 0, 0, NULL, NULL, NULL},
3242 {quick_button, 2, 10, SEARCH_DLG_HEIGHT - 3, SEARCH_DLG_HEIGHT , N_("&OK"), 0, B_ENTER,
3243 0, 0, NULL, NULL, NULL},
3245 #ifdef HAVE_CHARSET
3246 {quick_checkbox, SEARCH_DLG_WIDTH/2 + 3, SEARCH_DLG_WIDTH, 6, SEARCH_DLG_HEIGHT, N_("All charsets"), 0, 0,
3247 &tall_codepages, 0, NULL, NULL, NULL},
3248 #endif
3250 {quick_checkbox, SEARCH_DLG_WIDTH/2 + 3, SEARCH_DLG_WIDTH, 5, SEARCH_DLG_HEIGHT,
3251 N_("&Backwards"), 0, 0, &tsearch_backwards, 0, NULL, NULL, NULL},
3253 {quick_checkbox, SEARCH_DLG_WIDTH/2 + 3, SEARCH_DLG_WIDTH, 4, SEARCH_DLG_HEIGHT, N_("case &Sensitive"), 0, 0,
3254 &tsearch_case, 0, NULL, NULL, NULL},
3256 {quick_radio, 3, SEARCH_DLG_WIDTH, 4, SEARCH_DLG_HEIGHT, 0, g_strv_length (list_of_types), ttype_of_search,
3257 (void *) &ttype_of_search, const_cast (char **, list_of_types), NULL, NULL, NULL},
3260 {quick_input, 3, SEARCH_DLG_WIDTH, 3, SEARCH_DLG_HEIGHT, defval, 52, 0,
3261 0, &exp, N_("Search"), NULL, NULL},
3263 {quick_label, 2, SEARCH_DLG_WIDTH, 2, SEARCH_DLG_HEIGHT,
3264 N_(" Enter search string:"), 0, 0, 0, 0, 0, NULL, NULL},
3266 NULL_QuickWidget
3269 QuickDialog Quick_input = {
3270 SEARCH_DLG_WIDTH, SEARCH_DLG_HEIGHT, -1, 0, N_("Search"),
3271 "[Input Line Keys]", quick_widgets, 0
3274 convert_to_display (defval);
3277 if (quick_dialog (&Quick_input) == B_CANCEL)
3278 goto cleanup;
3280 view->search_backwards = tsearch_backwards;
3281 view->search_type = (mc_search_type_t) ttype_of_search;
3283 view->search_all_codepages = (gboolean) tall_codepages;
3284 view->search_case = (gboolean) tsearch_case;
3286 if (exp == NULL || exp[0] == '\0')
3287 goto cleanup;
3289 convert_from_input (exp);
3291 g_free (view->last_search_string);
3292 view->last_search_string = exp;
3293 exp = NULL;
3295 if (view->search)
3296 mc_search_free(view->search);
3298 view->search = mc_search_new(view->last_search_string, -1);
3299 if (! view->search)
3300 return;
3302 view->search->search_type = view->search_type;
3303 view->search->is_all_charsets = view->search_all_codepages;
3304 view->search->is_case_sentitive = view->search_case;
3306 do_search (view);
3308 cleanup:
3309 g_free (exp);
3310 g_free (defval);
3313 static void
3314 view_toggle_magic_mode_cmd (WView *view)
3316 view_toggle_magic_mode (view);
3317 view_update (view);
3320 static void
3321 view_toggle_nroff_mode_cmd (WView *view)
3323 view_toggle_nroff_mode (view);
3324 view_update (view);
3327 static void
3328 view_quit_cmd (WView *view)
3330 if (view_ok_to_quit (view))
3331 dlg_stop (view->widget.parent);
3334 /* {{{ Miscellaneous functions }}} */
3336 /* Define labels and handlers for functional keys */
3337 static void
3338 view_labels (WView *view)
3340 const char *text;
3341 Dlg_head *h = view->widget.parent;
3343 buttonbar_set_label (h, 1, Q_("ButtonBar|Help"), view_help_cmd);
3345 my_define (h, 10, Q_("ButtonBar|Quit"), view_quit_cmd, view);
3346 text = view->hex_mode ? "ButtonBar|Ascii" : "ButtonBar|Hex";
3347 my_define (h, 4, Q_(text), view_toggle_hex_mode_cmd, view);
3348 text = view->hex_mode ?"ButtonBar|Goto": "ButtonBar|Line";
3349 my_define (h, 5, Q_(text),
3350 view->hex_mode ? view_moveto_addr_cmd : view_moveto_line_cmd, view);
3352 if (view->hex_mode) {
3353 if (view->hexedit_mode) {
3354 my_define (h, 2, Q_("ButtonBar|View"),
3355 view_toggle_hexedit_mode_cmd, view);
3356 } else if (view->datasource == DS_FILE) {
3357 my_define (h, 2, Q_("ButtonBar|Edit"),
3358 view_toggle_hexedit_mode_cmd, view);
3359 } else {
3360 buttonbar_clear_label (h, 2);
3362 my_define (h, 6, Q_("ButtonBar|Save"),
3363 view_hexedit_save_changes_cmd, view);
3364 } else {
3365 text = view->text_wrap_mode ? "ButtonBar|UnWrap" : "ButtonBar|Wrap";
3366 my_define (h, 2, Q_(text), view_toggle_wrap_mode_cmd, view);
3369 text = view->hex_mode ? "ButtonBar|HxSrch" : "ButtonBar|Search";
3370 my_define (h, 7, Q_(text), view_search_cmd, view);
3371 text = view->magic_mode ? "ButtonBar|Raw" : "ButtonBar|Parse";
3372 my_define (h, 8, Q_(text), view_toggle_magic_mode_cmd, view);
3374 /* don't override the key to access the main menu */
3375 if (!view_is_in_panel (view)) {
3376 text = view->text_nroff_mode ? "ButtonBar|Unform" : "ButtonBar|Format";
3377 my_define (h, 9, Q_(text), view_toggle_nroff_mode_cmd, view);
3378 my_define (h, 3, Q_("ButtonBar|Quit"), view_quit_cmd, view);
3382 /* {{{ Event handling }}} */
3384 /* Check for left and right arrows, possibly with modifiers */
3385 static cb_ret_t
3386 check_left_right_keys (WView *view, int c)
3388 if (c == KEY_LEFT) {
3389 view_move_left (view, 1);
3390 return MSG_HANDLED;
3393 if (c == KEY_RIGHT) {
3394 view_move_right (view, 1);
3395 return MSG_HANDLED;
3398 /* Ctrl with arrows moves by 10 postions in the unwrap mode */
3399 if (view->hex_mode || view->text_wrap_mode)
3400 return MSG_NOT_HANDLED;
3402 if (c == (KEY_M_CTRL | KEY_LEFT)) {
3403 if (view->dpy_text_column >= 10)
3404 view->dpy_text_column -= 10;
3405 else
3406 view->dpy_text_column = 0;
3407 view->dirty++;
3408 return MSG_HANDLED;
3411 if (c == (KEY_M_CTRL | KEY_RIGHT)) {
3412 if (view->dpy_text_column <= OFFSETTYPE_MAX - 10)
3413 view->dpy_text_column += 10;
3414 else
3415 view->dpy_text_column = OFFSETTYPE_MAX;
3416 view->dirty++;
3417 return MSG_HANDLED;
3420 return MSG_NOT_HANDLED;
3423 /* {{{ User-definable commands }}} */
3425 static void
3426 view_continue_search_cmd (WView *view)
3428 if (view->last_search_string!=NULL) {
3429 do_search(view);
3430 } else {
3431 /* if not... then ask for an expression */
3432 view_search_cmd (view);
3436 static void
3437 view_toggle_ruler_cmd (WView *view)
3439 static const enum ruler_type next[3] = {
3440 RULER_TOP,
3441 RULER_BOTTOM,
3442 RULER_NONE
3445 assert ((size_t) ruler < 3);
3446 ruler = next[(size_t) ruler];
3447 view->dirty++;
3450 /* {{{ Event handling }}} */
3452 static void view_cmk_move_up (void *w, int n) {
3453 view_move_up ((WView *) w, n);
3455 static void view_cmk_move_down (void *w, int n) {
3456 view_move_down ((WView *) w, n);
3458 static void view_cmk_moveto_top (void *w, int n) {
3459 (void) &n;
3460 view_moveto_top ((WView *) w);
3462 static void view_cmk_moveto_bottom (void *w, int n) {
3463 (void) &n;
3464 view_moveto_bottom ((WView *) w);
3467 static void
3468 view_select_encoding (WView *view)
3470 #ifdef HAVE_CHARSET
3471 char *enc = NULL;
3473 if (!do_select_codepage ())
3474 return;
3476 enc = get_codepage_id (source_codepage);
3477 if (enc != NULL) {
3478 GIConv conv;
3479 struct cache_line *line;
3481 conv = str_crt_conv_from (enc);
3482 if (conv != INVALID_CONV) {
3483 if (view->converter != str_cnv_from_term)
3484 str_close_conv (view->converter);
3485 view->converter = conv;
3486 view_reset_cache_lines (view);
3487 line = view_offset_to_line (view, view->dpy_start);
3488 view_set_first_showed (view, line);
3491 #endif
3495 /* Both views */
3496 static cb_ret_t
3497 view_handle_key (WView *view, int c)
3499 c = convert_from_input_c (c);
3501 if (view->hex_mode) {
3502 switch (c) {
3503 case '\t':
3504 view->hexview_in_text = !view->hexview_in_text;
3505 view->dirty++;
3506 return MSG_HANDLED;
3508 case XCTRL ('a'):
3509 view_moveto_bol (view);
3510 view->dirty++;
3511 return MSG_HANDLED;
3513 case XCTRL ('b'):
3514 view_move_left (view, 1);
3515 return MSG_HANDLED;
3517 case XCTRL ('e'):
3518 view_moveto_eol (view);
3519 return MSG_HANDLED;
3521 case XCTRL ('f'):
3522 view_move_right (view, 1);
3523 return MSG_HANDLED;
3526 if (view->hexedit_mode
3527 && view_handle_editkey (view, c) == MSG_HANDLED)
3528 return MSG_HANDLED;
3531 if (check_left_right_keys (view, c))
3532 return MSG_HANDLED;
3534 if (check_movement_keys (c, view->data_area.height + 1, view,
3535 view_cmk_move_up, view_cmk_move_down,
3536 view_cmk_moveto_top, view_cmk_moveto_bottom))
3537 return MSG_HANDLED;
3539 switch (c) {
3541 case '?':
3542 case '/':
3543 view->search_type = MC_SEARCH_T_REGEX;
3544 view_search_cmd(view);
3545 return MSG_HANDLED;
3546 break;
3547 /* Continue search */
3548 case XCTRL ('r'):
3549 case XCTRL ('s'):
3550 case 'n':
3551 case KEY_F (17):
3552 view_continue_search_cmd (view);
3553 return MSG_HANDLED;
3555 /* toggle ruler */
3556 case ALT ('r'):
3557 view_toggle_ruler_cmd (view);
3558 return MSG_HANDLED;
3560 case 'h':
3561 view_move_left (view, 1);
3562 return MSG_HANDLED;
3564 case 'j':
3565 case '\n':
3566 case 'e':
3567 view_move_down (view, 1);
3568 return MSG_HANDLED;
3570 case 'd':
3571 view_move_down (view, (view->data_area.height + 1) / 2);
3572 return MSG_HANDLED;
3574 case 'u':
3575 view_move_up (view, (view->data_area.height + 1) / 2);
3576 return MSG_HANDLED;
3578 case 'k':
3579 case 'y':
3580 view_move_up (view, 1);
3581 return MSG_HANDLED;
3583 case 'l':
3584 view_move_right (view, 1);
3585 return MSG_HANDLED;
3587 case ' ':
3588 case 'f':
3589 view_move_down (view, view->data_area.height);
3590 return MSG_HANDLED;
3592 case XCTRL ('o'):
3593 view_other_cmd ();
3594 return MSG_HANDLED;
3596 /* Unlike Ctrl-O, run a new shell if the subshell is not running. */
3597 case '!':
3598 exec_shell ();
3599 return MSG_HANDLED;
3601 case 'b':
3602 view_move_up (view, view->data_area.height);
3603 return MSG_HANDLED;
3605 case KEY_IC:
3606 view_move_up (view, 2);
3607 return MSG_HANDLED;
3609 case KEY_DC:
3610 view_move_down (view, 2);
3611 return MSG_HANDLED;
3613 case 'm':
3614 view->marks[view->marker] = view->dpy_start;
3615 return MSG_HANDLED;
3617 case 'r':
3618 view->dpy_start = view->marks[view->marker];
3619 view->dirty++;
3620 return MSG_HANDLED;
3622 /* Use to indicate parent that we want to see the next/previous file */
3623 /* Does not work in panel mode */
3624 case XCTRL ('f'):
3625 case XCTRL ('b'):
3626 if (!view_is_in_panel (view))
3627 view->move_dir = c == XCTRL ('f') ? 1 : -1;
3628 /* FALLTHROUGH */
3629 case 'q':
3630 case XCTRL ('g'):
3631 case ESC_CHAR:
3632 if (view_ok_to_quit (view))
3633 view->want_to_quit = TRUE;
3634 return MSG_HANDLED;
3636 case XCTRL ('t'):
3637 view_select_encoding (view);
3638 view->dirty++;
3639 view_update (view);
3640 return MSG_HANDLED;
3642 #ifdef MC_ENABLE_DEBUGGING_CODE
3643 case 't': /* mnemonic: "test" */
3644 view_ccache_dump (view);
3645 return MSG_HANDLED;
3646 #endif
3648 if (c >= '0' && c <= '9')
3649 view->marker = c - '0';
3651 /* Key not used */
3652 return MSG_NOT_HANDLED;
3655 /* Both views */
3656 static int
3657 view_event (WView *view, Gpm_Event *event, int *result)
3659 screen_dimen y, x;
3661 *result = MOU_NORMAL;
3663 /* We are not interested in the release events */
3664 if (!(event->type & (GPM_DOWN | GPM_DRAG)))
3665 return 0;
3667 /* Wheel events */
3668 if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) {
3669 view_move_up (view, 2);
3670 return 1;
3672 if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
3673 view_move_down (view, 2);
3674 return 1;
3677 x = event->x;
3678 y = event->y;
3680 /* Scrolling left and right */
3681 if (!view->text_wrap_mode) {
3682 if (x < view->data_area.width * 1/4) {
3683 view_move_left (view, 1);
3684 goto processed;
3685 } else if (x < view->data_area.width * 3/4) {
3686 /* ignore the click */
3687 } else {
3688 view_move_right (view, 1);
3689 goto processed;
3693 /* Scrolling up and down */
3694 if (y < view->data_area.top + view->data_area.height * 1/3) {
3695 if (mouse_move_pages_viewer)
3696 view_move_up (view, view->data_area.height / 2);
3697 else
3698 view_move_up (view, 1);
3699 goto processed;
3700 } else if (y < view->data_area.top + view->data_area.height * 2/3) {
3701 /* ignore the click */
3702 } else {
3703 if (mouse_move_pages_viewer)
3704 view_move_down (view, view->data_area.height / 2);
3705 else
3706 view_move_down (view, 1);
3707 goto processed;
3710 return 0;
3712 processed:
3713 *result = MOU_REPEAT;
3714 return 1;
3717 /* Real view only */
3718 static int
3719 real_view_event (Gpm_Event *event, void *x)
3721 WView *view = (WView *) x;
3722 int result;
3724 if (view_event (view, event, &result))
3725 view_update (view);
3726 return result;
3729 static void
3730 view_adjust_size (Dlg_head *h)
3732 WView *view;
3733 WButtonBar *bar;
3735 /* Look up the viewer and the buttonbar, we assume only two widgets here */
3736 view = (WView *) find_widget_type (h, view_callback);
3737 bar = find_buttonbar (h);
3738 widget_set_size (&view->widget, 0, 0, LINES - 1, COLS);
3739 widget_set_size ((Widget *) bar, LINES - 1, 0, 1, COLS);
3741 view_compute_areas (view);
3742 view_update_bytes_per_line (view);
3745 /* Callback for the view dialog */
3746 static cb_ret_t
3747 view_dialog_callback (Dlg_head *h, dlg_msg_t msg, int parm)
3749 switch (msg) {
3750 case DLG_RESIZE:
3751 view_adjust_size (h);
3752 return MSG_HANDLED;
3754 default:
3755 return default_dlg_callback (h, msg, parm);
3759 /* {{{ External interface }}} */
3761 /* Real view only */
3763 mc_internal_viewer (const char *command, const char *file,
3764 int *move_dir_p, int start_line)
3766 gboolean succeeded;
3767 WView *wview;
3768 WButtonBar *bar;
3769 Dlg_head *view_dlg;
3771 /* Create dialog and widgets, put them on the dialog */
3772 view_dlg =
3773 create_dlg (0, 0, LINES, COLS, NULL, view_dialog_callback,
3774 "[Internal File Viewer]", NULL, DLG_WANT_TAB);
3776 wview = view_new (0, 0, COLS, LINES - 1, 0);
3778 bar = buttonbar_new (1);
3780 add_widget (view_dlg, bar);
3781 add_widget (view_dlg, wview);
3783 succeeded = view_load (wview, command, file, start_line);
3784 if (succeeded) {
3785 run_dlg (view_dlg);
3786 if (move_dir_p)
3787 *move_dir_p = wview->move_dir;
3788 } else {
3789 if (move_dir_p)
3790 *move_dir_p = 0;
3792 destroy_dlg (view_dlg);
3794 return succeeded;
3797 /* {{{ Miscellaneous functions }}} */
3799 static void
3800 view_hook (void *v)
3802 WView *view = (WView *) v;
3803 WPanel *panel;
3805 /* If the user is busy typing, wait until he finishes to update the
3806 screen */
3807 if (!is_idle ()) {
3808 if (!hook_present (idle_hook, view_hook))
3809 add_hook (&idle_hook, view_hook, v);
3810 return;
3813 delete_hook (&idle_hook, view_hook);
3815 if (get_current_type () == view_listing)
3816 panel = current_panel;
3817 else if (get_other_type () == view_listing)
3818 panel = other_panel;
3819 else
3820 return;
3822 view_load (view, 0, panel->dir.list[panel->selected].fname, 0);
3823 display (view);
3826 /* {{{ Event handling }}} */
3828 static cb_ret_t
3829 view_callback (Widget *w, widget_msg_t msg, int parm)
3831 WView *view = (WView *) w;
3832 cb_ret_t i;
3833 Dlg_head *h = view->widget.parent;
3835 view_compute_areas (view);
3836 view_update_bytes_per_line (view);
3838 switch (msg) {
3839 case WIDGET_INIT:
3840 if (view_is_in_panel (view))
3841 add_hook (&select_file_hook, view_hook, view);
3842 else
3843 view->dpy_bbar_dirty = TRUE;
3844 return MSG_HANDLED;
3846 case WIDGET_DRAW:
3847 display (view);
3848 return MSG_HANDLED;
3850 case WIDGET_CURSOR:
3851 if (view->hex_mode)
3852 view_place_cursor (view);
3853 return MSG_HANDLED;
3855 case WIDGET_KEY:
3856 i = view_handle_key ((WView *) view, parm);
3857 if (view->want_to_quit && !view_is_in_panel (view))
3858 dlg_stop (h);
3859 else {
3860 view_update (view);
3862 return i;
3864 case WIDGET_FOCUS:
3865 view->dpy_bbar_dirty = TRUE;
3866 view_update (view);
3867 return MSG_HANDLED;
3869 case WIDGET_DESTROY:
3870 view_done (view);
3871 if (view_is_in_panel (view))
3872 delete_hook (&select_file_hook, view_hook);
3873 return MSG_HANDLED;
3875 default:
3876 return default_proc (msg, parm);
3880 /* {{{ External interface }}} */
3882 WView *
3883 view_new (int y, int x, int cols, int lines, int is_panel)
3885 WView *view = g_new0 (WView, 1);
3886 size_t i;
3888 init_widget (&view->widget, y, x, lines, cols,
3889 view_callback,
3890 real_view_event);
3892 view->filename = NULL;
3893 view->command = NULL;
3895 view_set_datasource_none (view);
3897 view->growbuf_in_use = FALSE;
3898 /* leave the other growbuf fields uninitialized */
3900 view->hex_mode = FALSE;
3901 view->hexedit_mode = FALSE;
3902 view->hexview_in_text = FALSE;
3903 view->text_nroff_mode = FALSE;
3904 view->text_wrap_mode = FALSE;
3905 view->magic_mode = FALSE;
3907 view->hexedit_lownibble = FALSE;
3909 view->dpy_frame_size = is_panel ? 1 : 0;
3910 view->dpy_start = 0;
3911 view->dpy_text_column = 0;
3912 view->dpy_end = 0;
3913 view->hex_cursor = 0;
3914 view->cursor_col = 0;
3915 view->cursor_row = 0;
3916 view->change_list = NULL;
3917 view->converter = str_cnv_from_term;
3919 /* {status,ruler,data}_area are left uninitialized */
3921 view->dirty = 0;
3922 view->dpy_bbar_dirty = TRUE;
3923 view->bytes_per_line = 1;
3925 view->search_start = 0;
3926 view->search_end = 0;
3928 view->want_to_quit = FALSE;
3929 view->marker = 0;
3930 for (i = 0; i < sizeof(view->marks) / sizeof(view->marks[0]); i++)
3931 view->marks[i] = 0;
3933 view->move_dir = 0;
3934 view->update_steps = 0;
3935 view->update_activate = 0;
3937 if (default_hex_mode)
3938 view_toggle_hex_mode (view);
3939 if (default_nroff_flag)
3940 view_toggle_nroff_mode (view);
3941 if (global_wrap_mode)
3942 view_toggle_wrap_mode (view);
3943 if (default_magic_flag)
3944 view_toggle_magic_mode (view);
3946 return view;