/* View file module for the Midnight Commander Copyright (C) 1994, 1995, 1996 The Free Software Foundation Written by: 1994, 1995, 1998 Miguel de Icaza 1994, 1995 Janne Kukonlehto 1995 Jakub Jelinek 1996 Joseph M. Hinkle 1997 Norbert Warmuth 1998 Pavel Machek This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include #ifdef HAVE_MMAP # include #endif #include /* For toupper() */ #include #include #include "global.h" #include "tty.h" #include "cmd.h" /* For view_other_cmd */ #include "dialog.h" /* Needed by widget.h */ #include "widget.h" /* Needed for buttonbar_new */ #include "color.h" #include "mouse.h" #include "help.h" #include "key.h" /* For mi_getch() */ #include "layout.h" #include "setup.h" #include "wtools.h" /* For query_set_sel() */ #include "dir.h" #include "panel.h" /* Needed for current_panel and other_panel */ #include "win.h" #include "execute.h" #include "main.h" /* For the externs */ #define WANT_WIDGETS #include "view.h" #include "charsets.h" #include "selcodepage.h" #ifndef MAP_FILE #define MAP_FILE 0 #endif /* Block size for reading files in parts */ #define READ_BLOCK 8192 #define VIEW_PAGE_SIZE 8192 #ifdef IS_AIX # define IFAIX(x) case (x): #else # define IFAIX(x) #endif #define vwidth (view->widget.cols - (view->have_frame ? 2 : 0)) #define vheight (view->widget.lines - (view->have_frame ? 2 : 0)) /* Offset in bytes into a file */ typedef unsigned long offset_type; #define INVALID_OFFSET ((offset_type) -1) /* A width or height on the screen */ typedef unsigned int screen_dimen; /* A node for building a change list on change_list */ struct hexedit_change_node { struct hexedit_change_node *next; offset_type offset; unsigned char value; }; struct WView { Widget widget; char *filename; /* Name of the file */ char *command; /* Command used to pipe data in */ int view_active; int have_frame; unsigned char *data; /* Memory area for the file to be viewed */ /* File information */ int file; /* File descriptor (for mmap and munmap) */ FILE *stdfile; /* Stdio struct for reading file in parts */ int reading_pipe; /* Flag: Reading from pipe(use popen/pclose) */ offset_type bytes_read; /* How much of file is read */ int mmapping; /* Did we use mmap on the file? */ /* Display information */ offset_type last; /* Last byte shown */ offset_type last_byte; /* Last byte of file */ offset_type first; /* First byte in file */ offset_type bottom_first; /* First byte shown when very last page is displayed */ /* For the case of WINCH we should reset it to -1 */ offset_type start_display; /* First char displayed */ int start_col; /* First displayed column, negative */ offset_type edit_cursor; /* HexEdit cursor position in file */ int hexedit_mode:1; /* Hexidecimal editing mode flag */ int nib_shift:1; /* Set if editing the least significant nibble */ int hexedit_text:1; /* Set if hexedit is in the text mode */ screen_dimen start_save; /* Line start shift between text and hex */ screen_dimen cursor_col; /* Cursor column */ screen_dimen cursor_row; /* Cursor row */ struct hexedit_change_node *change_list; /* Linked list of changes */ int dirty; /* Number of skipped updates */ int wrap_mode:1; /* wrap_mode */ /* Mode variables */ int hex_mode:1; /* Hexadecimal mode flag */ int bytes_per_line; /* Number of bytes per line in hex mode */ int viewer_magic_flag:1; /* Selected viewer */ int viewer_nroff_flag:1; /* Do we do nroff style highlighting? */ /* Growing buffers information */ int growing_buffer; /* Use the growing buffers? */ char **block_ptr; /* Pointer to the block pointers */ int blocks; /* The number of blocks in *block_ptr */ /* Search variables */ offset_type search_start; /* First character to start searching from */ offset_type found_len; /* Length of found string or 0 if none was found */ char *search_exp; /* The search expression */ int direction; /* 1= forward; -1 backward */ void (*last_search)(void *, char *); /* Pointer to the last search command */ int view_quit:1; /* Quit flag */ int monitor; /* Monitor file growth (like tail -f) */ /* Markers */ int marker; /* mark to use */ offset_type marks [10]; /* 10 marks: 0..9 */ int move_dir; /* return value from widget: * 0 do nothing * -1 view previous file * 1 view next file */ struct stat s; /* stat for file */ }; static void view_move_cursor_to_eol(WView *view) { offset_type last_line = (view->last_byte - 1) / view->bytes_per_line; offset_type current_line = view->edit_cursor / view->bytes_per_line; if (current_line == last_line) { view->edit_cursor = view->last_byte - 1; } else { view->edit_cursor = (1 + current_line) * view->bytes_per_line - 1; } view->dirty++; } /* Maxlimit for skipping updates */ int max_dirt_limit = 10; /* If set, show a ruler */ static int ruler = 0; /* Scrolling is done in pages or line increments */ int mouse_move_pages_viewer = 1; /* Used to compute the bottom first variable */ int have_fast_cpu = 0; /* wrap mode default */ int global_wrap_mode = 1; int default_hex_mode = 0; static int default_hexedit_mode = 0; int default_magic_flag = 1; int default_nroff_flag = 1; int altered_hex_mode = 0; int altered_magic_flag = 0; int altered_nroff_flag = 0; static const char hex_char[] = "0123456789ABCDEF"; /* Our widget callback */ static cb_ret_t view_callback (WView *view, widget_msg_t msg, int parm); static int regexp_view_search (WView * view, char *pattern, char *string, int match_type); static void view_move_forward (WView * view, int i); static void view_labels (WView * view); static void set_monitor (WView * view, int set_on); static void view_update (WView * view, gboolean update_gui); static void close_view_file (WView *view) { if (view->file != -1) { mc_close (view->file); view->file = -1; } } static void free_file (WView *view) { int i; #ifdef HAVE_MMAP if (view->mmapping) { mc_munmap (view->data, view->s.st_size); close_view_file (view); } else #endif /* HAVE_MMAP */ { if (view->reading_pipe) { /* Close pipe */ pclose (view->stdfile); view->stdfile = NULL; /* Close error pipe and show warnings if any */ close_error_pipe (0, NULL); } else close_view_file (view); } /* Block_ptr may be zero if the file was a file with 0 bytes */ if (view->growing_buffer && view->block_ptr) { for (i = 0; i < view->blocks; i++) { g_free (view->block_ptr[i]); } g_free (view->block_ptr); } } /* Valid parameters for second parameter to set_monitor */ enum { off, on }; /* Both views */ static void view_done (WView *view) { set_monitor (view, off); /* alex: release core, used to replace mmap */ if (!view->mmapping && !view->growing_buffer && view->data != NULL) { g_free (view->data); view->data = NULL; } if (view->view_active) { free_file (view); g_free (view->filename); g_free (view->command); } view->view_active = 0; default_hex_mode = view->hex_mode; default_nroff_flag = view->viewer_nroff_flag; default_magic_flag = view->viewer_magic_flag; global_wrap_mode = view->wrap_mode; } static void view_hook (void *); static int get_byte (WView *view, unsigned int byte_index) { int page = byte_index / VIEW_PAGE_SIZE + 1; int offset = byte_index % VIEW_PAGE_SIZE; int i, n; if (view->growing_buffer) { if (page > view->blocks) { view->block_ptr = g_realloc (view->block_ptr, page * sizeof (char *)); for (i = view->blocks; i < page; i++) { char *p = g_try_malloc (VIEW_PAGE_SIZE); view->block_ptr[i] = p; if (!p) return '\n'; if (view->stdfile != NULL) n = fread (p, 1, VIEW_PAGE_SIZE, view->stdfile); else n = mc_read (view->file, p, VIEW_PAGE_SIZE); /* * FIXME: Errors are ignored at this point * Also should report preliminary EOF */ if (n != -1) view->bytes_read += n; if (view->s.st_size < view->bytes_read) { view->bottom_first = INVALID_OFFSET; /* Invalidate cache */ view->s.st_size = view->bytes_read; view->last_byte = view->bytes_read; if (view->reading_pipe) view->last_byte = view->first + view->bytes_read; } /* To force loading the next page */ if (n == VIEW_PAGE_SIZE && view->reading_pipe) { view->last_byte++; } } view->blocks = page; } if (byte_index >= view->bytes_read) { return -1; } else return view->block_ptr[page - 1][offset]; } else { if (byte_index >= view->last_byte) return -1; else return view->data[byte_index]; } } static void enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node) { struct hexedit_change_node *curr = *head; while (curr) { if (node->offset < curr->offset) { *head = node; node->next = curr; return; } head = (struct hexedit_change_node **) curr; curr = curr->next; } *head = node; node->next = curr; } static void move_right (WView *); static cb_ret_t view_handle_editkey (WView *view, int key) { struct hexedit_change_node *node; unsigned char byte_val; /* Has there been a change at this position ? */ node = view->change_list; while (node && (node->offset != view->edit_cursor)) node = node->next; if (!view->hexedit_text) { /* Hex editing */ if (key >= '0' && key <= '9') key -= '0'; else if (key >= 'A' && key <= 'F') key -= '7'; else if (key >= 'a' && key <= 'f') key -= 'W'; else return MSG_NOT_HANDLED; if (node) byte_val = node->value; else byte_val = get_byte (view, view->edit_cursor); if (!view->nib_shift) { byte_val = (byte_val & 0x0f) | (key << 4); } else { byte_val = (byte_val & 0xf0) | (key); } } else { /* Text editing */ if (key < 256 && (is_printable (key) || (key == '\n'))) byte_val = key; else return MSG_NOT_HANDLED; } if (!node) { node = (struct hexedit_change_node *) g_new (struct hexedit_change_node, 1); if (node) { #ifndef HAVE_MMAP /* alex@bcs.zaporizhzhe.ua: here we are using file copy * completely loaded into memory, so we can replace bytes in * view->data array to allow changes to be reflected when * user switches back to text mode */ view->data[view->edit_cursor] = byte_val; #endif /* !HAVE_MMAP */ node->offset = view->edit_cursor; node->value = byte_val; enqueue_change (&view->change_list, node); } } else { node->value = byte_val; } view->dirty++; view_update (view, TRUE); move_right (view); return MSG_HANDLED; } static void free_change_list (WView *view) { struct hexedit_change_node *n = view->change_list; while (n) { view->change_list = n->next; g_free (n); n = view->change_list; } view->dirty++; } static void save_edit_changes (WView *view) { struct hexedit_change_node *node = view->change_list; int fp; do { fp = open (view->filename, O_WRONLY); if (fp >= 0) { while (node) { if (lseek (fp, node->offset, SEEK_SET) == -1 || write (fp, &node->value, 1) != 1) { close (fp); fp = -1; break; } node = node->next; } if (fp != -1) close (fp); } if (fp == -1) { fp = query_dialog (_(" Save file "), _(" Cannot save file. "), 2, 2, _("&Retry"), _("&Cancel")) - 1; } } while (fp == -1); free_change_list (view); } static int view_ok_to_quit (WView *view) { int r; if (!view->change_list) return 1; r = query_dialog (_("Quit"), _(" File was modified, Save with exit? "), 2, 3, _("&Cancel quit"), _("&Yes"), _("&No")); switch (r) { case 1: save_edit_changes (view); return 1; case 2: free_change_list (view); return 1; default: return 0; } } static char * set_view_init_error (WView *view, const char *msg) { view->growing_buffer = 0; view->reading_pipe = 0; view->first = 0; view->last_byte = 0; if (msg) { view->bytes_read = strlen (msg); return g_strdup (msg); } return NULL; } /* return values: NULL for success, else points to error message */ static char * init_growing_view (WView *view, const char *name, const char *filename) { const char *err_msg = NULL; view->growing_buffer = 1; if (name) { view->reading_pipe = 1; view->s.st_size = 0; open_error_pipe (); if ((view->stdfile = popen (name, "r")) == NULL) { /* Avoid two messages. Message from stderr has priority. */ if (!close_error_pipe (view->have_frame ? -1 : 1, view->data)) err_msg = _(" Cannot spawn child program "); return set_view_init_error (view, err_msg); } /* First, check if filter produced any output */ get_byte (view, 0); if (view->bytes_read <= 0) { pclose (view->stdfile); view->stdfile = NULL; /* Avoid two messages. Message from stderr has priority. */ if (!close_error_pipe (view->have_frame ? -1 : 1, view->data)) err_msg = _("Empty output from child filter"); return set_view_init_error (view, err_msg); } } else { view->stdfile = NULL; if ((view->file = mc_open (filename, O_RDONLY)) == -1) return set_view_init_error (view, _(" Cannot open file ")); } return NULL; } /* Load filename into core */ /* returns: -1 on failure. if (have_frame), we return success, but data points to a error message instead of the file buffer (quick_view feature). */ static char * load_view_file (WView *view, int fd) { view->file = fd; if (view->s.st_size == 0) { /* Must be one of those nice files that grow (/proc) */ close_view_file (view); return init_growing_view (view, 0, view->filename); } #ifdef HAVE_MMAP if ((size_t) view->s.st_size == view->s.st_size) view->data = mc_mmap (0, view->s.st_size, PROT_READ, MAP_FILE | MAP_SHARED, view->file, 0); else view->data = (caddr_t) -1; if ((caddr_t) view->data != (caddr_t) - 1) { /* mmap worked */ view->first = 0; view->bytes_read = view->s.st_size; view->mmapping = 1; return NULL; } #endif /* HAVE_MMAP */ /* For the OSes that don't provide mmap call, try to load all the * file into memory (alex@bcs.zaporizhzhe.ua). Also, mmap can fail * for any reason, so we use this as fallback (pavel@ucw.cz) */ /* Make sure view->s.st_size is not truncated when passed to g_malloc */ if ((gulong) view->s.st_size == view->s.st_size) view->data = (unsigned char *) g_try_malloc ((gulong) view->s.st_size); else view->data = NULL; if (view->data == NULL || mc_lseek (view->file, 0, SEEK_SET) != 0 || mc_read (view->file, view->data, view->s.st_size) != view->s.st_size) { g_free (view->data); close_view_file (view); return init_growing_view (view, 0, view->filename); } view->first = 0; view->bytes_read = view->s.st_size; return NULL; } /* Return zero on success, -1 on failure */ static int do_view_init (WView *view, const char *_command, const char *_file, int start_line) { char *error = NULL; int i, type; int fd = -1; char tmp[BUF_MEDIUM]; if (view->view_active) view_done (view); /* Set up the state */ view->block_ptr = 0; view->data = NULL; view->growing_buffer = 0; view->reading_pipe = 0; view->mmapping = 0; view->blocks = 0; view->block_ptr = 0; view->first = view->bytes_read = 0; view->last_byte = 0; view->filename = g_strdup (_file); view->command = 0; view->last = view->first + ((LINES - 2) * view->bytes_per_line); /* Clear the markers */ view->marker = 0; for (i = 0; i < 10; i++) view->marks[i] = 0; if (!view->have_frame) { view->start_col = 0; } if (_command && (view->viewer_magic_flag || _file[0] == '\0')) { error = init_growing_view (view, _command, view->filename); } else if (_file[0]) { int cntlflags; /* Open the file */ if ((fd = mc_open (_file, O_RDONLY | O_NONBLOCK)) == -1) { g_snprintf (tmp, sizeof (tmp), _(" Cannot open \"%s\"\n %s "), _file, unix_error_string (errno)); error = set_view_init_error (view, tmp); goto finish; } /* Make sure we are working with a regular file */ if (mc_fstat (fd, &view->s) == -1) { mc_close (fd); g_snprintf (tmp, sizeof (tmp), _(" Cannot stat \"%s\"\n %s "), _file, unix_error_string (errno)); error = set_view_init_error (view, tmp); goto finish; } if (!S_ISREG (view->s.st_mode)) { mc_close (fd); g_snprintf (tmp, sizeof (tmp), _(" Cannot view: not a regular file ")); error = set_view_init_error (view, tmp); goto finish; } /* We don't need O_NONBLOCK after opening the file, unset it */ cntlflags = fcntl (fd, F_GETFL, 0); if (cntlflags != -1) { cntlflags &= ~O_NONBLOCK; fcntl (fd, F_SETFL, cntlflags); } type = get_compression_type (fd); if (view->viewer_magic_flag && (type != COMPRESSION_NONE)) { g_free (view->filename); view->filename = g_strconcat (_file, decompress_extension (type), (char *) NULL); } error = load_view_file (view, fd); } finish: if (error) { if (!view->have_frame) { message (1, MSG_ERROR, "%s", error); g_free (error); return -1; } } view->view_active = 1; if (_command) view->command = g_strdup (_command); else view->command = 0; view->search_start = view->start_display = view->start_save = view->first; view->found_len = 0; view->start_col = 0; view->last_search = 0; /* Start a new search */ /* Special case: The data points to the error message */ if (error) { view->data = error; view->file = -1; view->s.st_size = view->bytes_read = strlen (view->data); } view->last_byte = view->first + view->s.st_size; if (start_line > 1 && !error) { int saved_wrap_mode = view->wrap_mode; view->wrap_mode = 0; get_byte (view, 0); view_move_forward (view, start_line - 1); view->wrap_mode = saved_wrap_mode; } view->edit_cursor = view->first; view->nib_shift = 0; view->hexedit_text = 0; view->change_list = NULL; return 0; } void view_update_bytes_per_line (WView *view) { int cols; if (view->have_frame) cols = view->widget.cols - 2; else cols = view->widget.cols; view->bottom_first = INVALID_OFFSET; if (cols < 80) view->bytes_per_line = ((cols - 8) / 17) * 4; else view->bytes_per_line = ((cols - 8) / 18) * 4; if (view->bytes_per_line == 0) view->bytes_per_line++; /* To avoid division by 0 */ view->dirty = max_dirt_limit + 1; /* To force refresh */ } /* Both views */ /* Return zero on success, -1 on failure */ int view_init (WView *view, const char *_command, const char *_file, int start_line) { if (!view->view_active || strcmp (_file, view->filename) || altered_magic_flag) return do_view_init (view, _command, _file, start_line); else return 0; } static void view_percent (WView *view, int p, int w, gboolean update_gui) { int percent; percent = (view->s.st_size == 0 || view->last_byte == view->last) ? 100 : (p > (INT_MAX / 100) ? p / (view->s. st_size / 100) : p * 100 / view->s.st_size); #if 0 percent = view->s.st_size == 0 ? 100 : (view->last_byte == view->last ? 100 : (p) * 100 / view->s.st_size); #endif widget_move (view, view->have_frame, w - 5); printw ("%3d%%", percent); } static void view_status (WView *view, gboolean update_gui) { static int i18n_adjust = 0; static const char *file_label; int w = view->widget.cols - (view->have_frame * 2); int i; attrset (SELECTED_COLOR); widget_move (view, view->have_frame, view->have_frame); hline (' ', w); if (!i18n_adjust) { file_label = _("File: %s"); i18n_adjust = strlen (file_label) - 2; } if (w < i18n_adjust + 6) addstr ((char *) name_trunc (view->filename ? view->filename : view->command ? view->command : "", w)); else { i = (w > 22 ? 22 : w) - i18n_adjust; printw (const_cast(char *, file_label), name_trunc (view->filename ? view->filename : view->command ? view->command : "", i)); if (w > 46) { widget_move (view, view->have_frame, 24 + view->have_frame); if (view->hex_mode) printw (const_cast(char *, _("Offset 0x%08lx")), view->edit_cursor); else printw (const_cast(char *, _("Col %d")), -view->start_col); } if (w > 62) { widget_move (view, view->have_frame, 43 + view->have_frame); printw (const_cast(char *, _("%s bytes")), size_trunc (view->s.st_size)); } if (w > 70) { printw (" "); if (view->growing_buffer) addstr (_(" [grow]")); } if (w > 26) { view_percent (view, view->hex_mode ? view->edit_cursor : view-> start_display, view->widget.cols - view->have_frame + 1, update_gui); } } attrset (SELECTED_COLOR); } static inline void view_display_clean (WView *view, int height, int width) { /* FIXME: Should I use widget_erase only and repaint the box? */ if (view->have_frame) { int i; draw_double_box (view->widget.parent, view->widget.y, view->widget.x, view->widget.lines, view->widget.cols); for (i = 1; i < height; i++) { widget_move (view, i, 1); printw ("%*s", width - 1, ""); } } else widget_erase ((Widget *) view); } #define view_add_character(view,c) addch (c) #define view_add_one_vline() one_vline() #define view_add_string(view,s) addstr (s) #define view_gotoyx(v,r,c) widget_move (v,r,c) #define view_freeze(view) #define view_thaw(view) #define STATUS_LINES 1 typedef enum { MARK_NORMAL = 0, MARK_SELECTED = 1, MARK_CURSOR = 2, MARK_CHANGED = 3 } mark_t; static inline int view_count_backspaces (WView *view, off_t offset) { int backspaces = 0; while (get_byte (view, offset - 2 * backspaces) == '\b') backspaces++; return backspaces; } /* Shows the file pointed to by *start_display on view_win */ static offset_type display (WView *view) { const int frame_shift = view->have_frame; int col = 0 + frame_shift; int row = STATUS_LINES + frame_shift; int height, width; offset_type from; int c; mark_t boldflag = MARK_NORMAL; struct hexedit_change_node *curr = view->change_list; height = view->widget.lines - frame_shift; width = view->widget.cols - frame_shift; from = view->start_display; attrset (NORMAL_COLOR); view_freeze (view); view_display_clean (view, height, width); /* Optionally, display a ruler */ if ((!view->hex_mode) && (ruler)) { char r_buff[10]; int cl; attrset (MARKED_COLOR); for (c = frame_shift; c < width; c++) { cl = c - view->start_col; if (ruler == 1) view_gotoyx (view, row, c); else view_gotoyx (view, row + height - 2, c); r_buff[0] = '-'; if ((cl % 10) == 0) r_buff[0] = '|'; else if ((cl % 5) == 0) r_buff[0] = '*'; view_add_character (view, r_buff[0]); if ((cl != 0) && (cl % 10) == 0) { g_snprintf (r_buff, sizeof (r_buff), "%03d", cl); if (ruler == 1) { widget_move (view, row + 1, c - 1); } else { widget_move (view, row + height - 3, c - 1); } view_add_string (view, r_buff); } } attrset (NORMAL_COLOR); if (ruler == 1) row += 2; else height -= 2; } /* Find the first displayable changed byte */ while (curr && (curr->offset < from)) { curr = curr->next; } if (view->hex_mode) { char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */ int bytes; /* Number of bytes already printed on the line */ /* Start of text column */ int text_start = width - view->bytes_per_line - 1 + frame_shift; for (; (void) get_byte (view, from + view->bytes_per_line), row < height && from < view->last_byte; row++) { /* Print the hex offset */ attrset (MARKED_COLOR); g_snprintf (hex_buff, sizeof (hex_buff), "%08X", (int) (from - view->first)); view_gotoyx (view, row, frame_shift); view_add_string (view, hex_buff); attrset (NORMAL_COLOR); /* Hex dump starts from column nine */ if (view->have_frame) col = 10; else col = 9; /* Each hex number is two digits */ hex_buff[2] = 0; for (bytes = 0; bytes < view->bytes_per_line && from < view->last_byte; bytes++, from++) { /* Display and mark changed bytes */ if (curr && from == curr->offset) { c = curr->value; curr = curr->next; boldflag = MARK_CHANGED; attrset (VIEW_UNDERLINED_COLOR); } else c = (unsigned char) get_byte (view, from); if (view->found_len && from >= view->search_start && from < view->search_start + view->found_len) { boldflag = MARK_SELECTED; attrset (MARKED_COLOR); } /* Display the navigation cursor */ if (from == view->edit_cursor) { if (!view->hexedit_text) { view->cursor_row = row; view->cursor_col = col; } boldflag = MARK_CURSOR; attrset (view-> hexedit_text ? MARKED_SELECTED_COLOR : VIEW_UNDERLINED_COLOR); } /* Print a hex number (sprintf is too slow) */ hex_buff[0] = hex_char[(c >> 4)]; hex_buff[1] = hex_char[c & 15]; view_gotoyx (view, row, col); view_add_string (view, hex_buff); col += 3; /* Turn off the cursor or changed byte highlighting here */ if (boldflag == MARK_CURSOR || boldflag == MARK_CHANGED) attrset (NORMAL_COLOR); if ((bytes & 3) == 3 && bytes + 1 < view->bytes_per_line) { /* Turn off the search highlighting */ if (boldflag == MARK_SELECTED && from == view->search_start + view->found_len - 1) attrset (NORMAL_COLOR); /* Hex numbers are printed in the groups of four */ /* Groups are separated by a vline */ view_gotoyx (view, row, col - 1); view_add_character (view, ' '); view_gotoyx (view, row, col); if ((view->have_frame && view->widget.cols < 82) || view->widget.cols < 80) col += 1; else { view_add_one_vline (); col += 2; } if (boldflag != MARK_NORMAL && from == view->search_start + view->found_len - 1) attrset (MARKED_COLOR); } if (boldflag != MARK_NORMAL && from < view->search_start + view->found_len - 1 && bytes != view->bytes_per_line - 1) { view_gotoyx (view, row, col); view_add_character (view, ' '); } /* Print corresponding character on the text side */ view_gotoyx (view, row, text_start + bytes); c = convert_to_display_c (c); if (!is_printable (c)) c = '.'; switch (boldflag) { case MARK_NORMAL: break; case MARK_SELECTED: attrset (MARKED_COLOR); break; case MARK_CURSOR: if (view->hexedit_text) { /* Our side is active */ view->cursor_col = text_start + bytes; view->cursor_row = row; attrset (VIEW_UNDERLINED_COLOR); } else { /* Other side is active */ attrset (MARKED_SELECTED_COLOR); } break; case MARK_CHANGED: attrset (VIEW_UNDERLINED_COLOR); break; } view_add_character (view, c); if (boldflag != MARK_NORMAL) { boldflag = MARK_NORMAL; attrset (NORMAL_COLOR); } } } } else { if (view->growing_buffer && from == view->last_byte) get_byte (view, from); for (; row < height && from < view->last_byte; from++) { c = get_byte (view, from); if ((c == '\n') || (col >= width && view->wrap_mode)) { col = frame_shift; row++; if (c == '\n' || row >= height) continue; } if (c == '\r') continue; if (c == '\t') { col = ((col - frame_shift) / 8) * 8 + 8 + frame_shift; continue; } if (view->viewer_nroff_flag && c == '\b') { int c_prev; int c_next; if (from + 1 < view->last_byte && is_printable ((c_next = get_byte (view, from + 1))) && from > view->first && is_printable ((c_prev = get_byte (view, from - 1))) && (c_prev == c_next || c_prev == '_' || (c_prev == '+' && c_next == 'o'))) { if (col <= frame_shift) { /* So it has to be wrap_mode - do not need to check for it */ if (row == 1 + frame_shift) { from++; continue; /* There had to be a bold character on the rightmost position of the previous undisplayed line */ } row--; col = width; } col--; boldflag = MARK_SELECTED; if (c_prev == '_' && (c_next != '_' || view_count_backspaces (view, from) == 1)) attrset (VIEW_UNDERLINED_COLOR); else attrset (MARKED_COLOR); continue; } } if (view->found_len && from >= view->search_start && from < view->search_start + view->found_len) { boldflag = MARK_SELECTED; attrset (SELECTED_COLOR); } if (col >= frame_shift - view->start_col && col < width - view->start_col) { view_gotoyx (view, row, col + view->start_col); c = convert_to_display_c (c); if (!is_printable (c)) c = '.'; view_add_character (view, c); } col++; if (boldflag != MARK_NORMAL) { boldflag = MARK_NORMAL; attrset (NORMAL_COLOR); } /* Very last thing */ if (view->growing_buffer && from + 1 == view->last_byte) get_byte (view, from + 1); } } view->last = from; view_thaw (view); return from; } static void view_place_cursor (WView *view) { int shift; if (!view->hexedit_text && view->nib_shift) shift = 1; else shift = 0; widget_move (&view->widget, view->cursor_row, view->cursor_col + shift); } static void view_update (WView *view, gboolean update_gui) { static int dirt_limit = 1; if (view->dirty > dirt_limit) { /* Too many updates skipped -> force a update */ display (view); view_status (view, update_gui); view->dirty = 0; /* Raise the update skipping limit */ dirt_limit++; if (dirt_limit > max_dirt_limit) dirt_limit = max_dirt_limit; } if (view->dirty) { if (is_idle ()) { /* We have time to update the screen properly */ display (view); view_status (view, update_gui); view->dirty = 0; if (dirt_limit > 1) dirt_limit--; } else { /* We are busy -> skipping full update, only the status line is updated */ view_status (view, update_gui); } /* Here we had a refresh, if fast scrolling does not work restore the refresh, although this should not happen */ } } static inline void my_define (Dlg_head *h, int idx, const char *text, void (*fn) (WView *), WView *view) { define_label_data (h, idx, text, (buttonbarfn) fn, view); } /* If the last parameter is nonzero, it means we want get the count of lines from current up to the the upto position inclusive */ static offset_type move_forward2 (WView *view, offset_type current, int lines, offset_type upto) { offset_type q, p; int line; int col = 0; if (view->hex_mode) { p = current + lines * view->bytes_per_line; p = (p >= view->last_byte) ? current : p; if (lines == 1) { q = view->edit_cursor + view->bytes_per_line; line = q / view->bytes_per_line; col = (view->last_byte - 1) / view->bytes_per_line; view->edit_cursor = (line > col) ? view->edit_cursor : q; view->edit_cursor = (view->edit_cursor < view->last_byte) ? view->edit_cursor : view->last_byte - 1; q = current + ((LINES - 2) * view->bytes_per_line); p = (view->edit_cursor < q) ? current : p; } else { view->edit_cursor = (view->edit_cursor < p) ? p : view->edit_cursor; } return p; } else { if (upto) { lines = -1; q = upto; } else q = view->last_byte; if (get_byte (view, q) != '\n') q++; for (line = col = 0, p = current; p < q; p++) { int c; if (lines != -1 && line >= lines) return p; c = get_byte (view, p); if (view->wrap_mode) { if (c == '\r') continue; /* This characters is never displayed */ else if (c == '\t') col = ((col - view->have_frame) / 8) * 8 + 8 + view->have_frame; else col++; if (view->viewer_nroff_flag && c == '\b') { if (p + 1 < view->last_byte && is_printable (get_byte (view, p + 1)) && p > view->first && is_printable (get_byte (view, p - 1))) col -= 2; } else if (col == vwidth) { /* FIXME: the c in is_printable was a p, that is a bug, I suspect I got that fix from Jakub, same applies for d. */ int d = get_byte (view, p + 2); if (p + 2 >= view->last_byte || !is_printable (c) || !view->viewer_nroff_flag || get_byte (view, p + 1) != '\b' || !is_printable (d)) { col = 0; if (c == '\n' || get_byte (view, p + 1) != '\n') line++; } } else if (c == '\n') { line++; col = 0; } } else if (c == '\n') line++; } if (upto) return line; } return current; } /* special case for text (non-hex) mode with line wrapping. */ static offset_type move_backward2_textmode_wrap (WView * view, offset_type current, int lines) { offset_type p, q, pm; int line; if (current == view->last_byte && get_byte (view, current - 1) != '\n') /* There is one virtual '\n' at the end, so that the last line is shown */ line = 1; else line = 0; for (q = p = current - 1; p > view->first; p--) { if (get_byte (view, p) == '\n' || p == view->first) { pm = p > view->first ? p + 1 : view->first; line += move_forward2 (view, pm, 0, q); if (line >= lines) { if (line == lines) return pm; else return move_forward2 (view, pm, line - lines, 0); } q = p + 1; } } return p > view->first ? p : view->first; } /* returns the new current pointer */ /* Cause even the forward routine became very complex, we in the wrap_mode just find the nearest '\n', use move_forward2(p, 0, q) to get the count of lines up to there and then use move_forward2(p, something, 0), which we return */ static offset_type move_backward2 (WView *view, offset_type current, int lines) { if (view->hex_mode) { if (view->edit_cursor >= lines * view->bytes_per_line) { view->edit_cursor -= lines * view->bytes_per_line; } else { view->edit_cursor %= view->bytes_per_line; } if (current > view->edit_cursor) { /* cursor is out-of-view -- adjust the view */ if (current >= lines * view->bytes_per_line) { current -= lines * view->bytes_per_line; } else { current %= view->bytes_per_line; } } return current; } else { if (current == view->first) return current; if (view->wrap_mode) return move_backward2_textmode_wrap (view, current, lines); /* There is one virtual '\n' at the end, * so that the last line is shown */ if (current == view->last_byte && get_byte (view, current - 1) != '\n') lines--; while (current > view->first && get_byte(view, current - 1) != '\n') current--; while (lines > 0) { if (current > view->first) current--; lines--; while (current > view->first && get_byte(view, current - 1) != '\n') current--; } return current; } return current; /* unreached */ } static void view_move_backward (WView *view, int i) { view->search_start = view->start_display = move_backward2 (view, view->start_display, i); view->found_len = 0; view->last = view->first + ((LINES - 2) * view->bytes_per_line); view->dirty++; } static offset_type get_bottom_first (WView *view, int do_not_cache, int really) { offset_type bottom_first; if (!have_fast_cpu && !really) return INT_MAX; if (!do_not_cache && view->bottom_first != INVALID_OFFSET) return view->bottom_first; /* Force loading */ if (view->growing_buffer) { offset_type old_last_byte; old_last_byte = INVALID_OFFSET; while (old_last_byte != view->last_byte) { old_last_byte = view->last_byte; get_byte (view, view->last_byte + VIEW_PAGE_SIZE); } } bottom_first = move_backward2 (view, view->last_byte, vheight - 1); if (view->hex_mode) bottom_first = (bottom_first + view->bytes_per_line - 1) / view->bytes_per_line * view->bytes_per_line; view->bottom_first = bottom_first; return view->bottom_first; } static void view_move_forward (WView *view, int i) { view->start_display = move_forward2 (view, view->start_display, i, 0); if (!view->reading_pipe && view->start_display > get_bottom_first (view, 0, 0)) view->start_display = view->bottom_first; view->search_start = view->start_display; view->found_len = 0; view->last = view->first + ((LINES - 2) * view->bytes_per_line); view->dirty++; } static void move_to_top (WView *view) { view->search_start = view->start_display = view->first; view->found_len = 0; view->last = view->first + ((LINES - 2) * view->bytes_per_line); view->nib_shift = 0; view->edit_cursor = view->start_display; view->dirty++; } static void move_to_bottom (WView *view) { view->search_start = view->start_display = get_bottom_first (view, 0, 1); view->found_len = 0; view->last = view->first + ((LINES - 2) * view->bytes_per_line); view->edit_cursor = (view->edit_cursor < view->start_display) ? view->start_display : view->edit_cursor; view->dirty++; } /* Scroll left/right the view panel functions */ static void move_right (WView *view) { if (view->wrap_mode && !view->hex_mode) return; if (view->hex_mode) { view->last = view->first + ((LINES - 2) * view->bytes_per_line); if (view->hex_mode && !view->hexedit_text) { view->nib_shift = !view->nib_shift; if (view->nib_shift) return; } view->edit_cursor = (++view->edit_cursor < view->last_byte) ? view->edit_cursor : view->last_byte - 1; if (view->edit_cursor >= view->last) { view->edit_cursor -= view->bytes_per_line; view_move_forward (view, 1); } } else if (--view->start_col > 0) view->start_col = 0; view->dirty++; } static void move_left (WView *view) { if (view->wrap_mode && !view->hex_mode) return; if (view->hex_mode) { if (view->hex_mode && !view->hexedit_text) { view->nib_shift = !view->nib_shift; if (!view->nib_shift) return; } if (view->edit_cursor > view->first) --view->edit_cursor; if (view->edit_cursor < view->start_display) { view->edit_cursor += view->bytes_per_line; view_move_backward (view, 1); } } else if (++view->start_col > 0) view->start_col = 0; view->dirty++; } /* Case insensitive search of text in data */ static int icase_search_p (WView *view, char *text, char *data, int nothing) { const char *q; int lng; const int direction = view->direction; /* If we are searching backwards, reverse the string */ if (direction == -1) { g_strreverse (text); g_strreverse (data); } q = _icase_search (text, data, &lng); if (direction == -1) { g_strreverse (text); g_strreverse (data); } if (q != 0) { if (direction > 0) view->search_start = q - data - lng; else view->search_start = strlen (data) - (q - data); view->found_len = lng; return 1; } return 0; } static char * grow_string_buffer (char *text, int *size) { char *new; /* The grow steps */ *size += 160; new = g_realloc (text, *size); if (!text) { *new = 0; } return new; } static char * get_line_at (WView *view, offset_type *p, offset_type *skipped) { char *buffer = NULL; int buffer_size = 0; offset_type usable_size = 0; int ch; const int direction = view->direction; offset_type pos = *p; offset_type i = 0; int prev = 0; if (!pos && direction == -1) return 0; /* skip over all the possible zeros in the file */ while ((ch = get_byte (view, pos)) == 0) { if (!pos && direction == -1) return 0; pos += direction; i++; } *skipped = i; if (!i && (pos || direction == -1)) { prev = get_byte (view, pos - direction); if ((prev == -1) || (prev == '\n')) prev = 0; } for (i = 1; ch != -1; ch = get_byte (view, pos)) { if (i >= usable_size) { buffer = grow_string_buffer (buffer, &buffer_size); usable_size = buffer_size - 2; /* prev & null terminator */ } buffer[i++] = ch; if (!pos && direction == -1) break; pos += direction; if (ch == '\n' || !ch) { i--; /* Strip newline/zero */ break; } } if (buffer) { buffer[0] = prev; buffer[i] = 0; /* If we are searching backwards, reverse the string */ if (direction < 0) { g_strreverse (buffer + 1); } } *p = pos; return buffer; } /** Search status optmizations **/ /* The number of bytes between percent increments */ static offset_type update_steps; /* Last point where we updated the status */ static offset_type update_activate; static void search_update_steps (WView *view) { if (view->s.st_size) update_steps = 40000; else /* viewing a data stream, not a file */ update_steps = view->last_byte / 100; /* Do not update the percent display but every 20 ks */ if (update_steps < 20000) update_steps = 20000; } static void search (WView *view, char *text, int (*search) (WView *, char *, char *, int)) { const int w = view->widget.cols - view->have_frame + 1; char *s = NULL; /* The line we read from the view buffer */ offset_type p, beginning, search_start; int found_len; int search_status; Dlg_head *d = 0; /* Used to keep track of where the line starts, when looking forward */ /* is the index before transfering the line; the reverse case uses */ /* the position returned after the line has been read */ offset_type forward_line_start; offset_type reverse_line_start; offset_type t; /* Clear interrupt status */ got_interrupt (); if (verbose) { d = create_message (D_NORMAL, _("Search"), _("Searching %s"), text); mc_refresh (); } found_len = view->found_len; search_start = view->search_start; if (view->direction == 1) { p = found_len ? search_start + 1 : search_start; } else { p = (found_len && search_start) ? search_start - 1 : search_start; } beginning = p; /* Compute the percent steps */ search_update_steps (view); update_activate = 0; for (;; g_free (s)) { if (p >= update_activate) { update_activate += update_steps; if (verbose) { view_percent (view, p, w, TRUE); mc_refresh (); } if (got_interrupt ()) break; } forward_line_start = p; disable_interrupt_key (); s = get_line_at (view, &p, &t); reverse_line_start = p; enable_interrupt_key (); if (!s) break; search_status = (*search) (view, text, s + 1, match_normal); if (search_status < 0) { g_free (s); break; } if (search_status == 0) continue; /* We found the string */ /* Handle ^ and $ when regexp search starts at the middle of the line */ if (*s && !view->search_start && (search == regexp_view_search)) { if ((*text == '^' && view->direction == 1) || (view->direction == -1 && text[strlen (text) - 1] == '$') ) { continue; } } /* Record the position used to continue the search */ if (view->direction == 1) t += forward_line_start; else t = reverse_line_start ? reverse_line_start + 3 : 0; view->search_start += t; if (t != beginning) { if (t > get_bottom_first (view, 0, 0)) view->start_display = view->bottom_first; else view->start_display = t; } g_free (s); break; } disable_interrupt_key (); if (verbose) { dlg_run_done (d); destroy_dlg (d); } if (!s) { message (0, _("Search"), _(" Search string not found ")); view->found_len = 0; } } /* Search buffer (its size is len) in the complete buffer * returns the position where the block was found or INVALID_OFFSET * if not found */ static offset_type block_search (WView *view, const char *buffer, int len) { const int w = view->widget.cols - view->have_frame + 1; int direction = view->direction; const char *d = buffer; char b; offset_type e; /* clear interrupt status */ got_interrupt (); enable_interrupt_key (); if (direction == 1) e = view->found_len ? view->search_start + 1 : view->search_start; else e = (view->found_len && view->search_start) ? view->search_start - 1 : view->search_start; search_update_steps (view); update_activate = 0; if (direction == -1) { for (d += len - 1;; e--) { if (e <= update_activate) { update_activate -= update_steps; if (verbose) { view_percent (view, e, w, TRUE); mc_refresh (); } if (got_interrupt ()) break; } b = get_byte (view, e); if (*d == b) { if (d == buffer) { disable_interrupt_key (); return e; } d--; } else { e += buffer + len - 1 - d; d = buffer + len - 1; } if (e == 0) break; } } else { while (e < view->last_byte) { if (e >= update_activate) { update_activate += update_steps; if (verbose) { view_percent (view, e, w, TRUE); mc_refresh (); } if (got_interrupt ()) break; } b = get_byte (view, e++); if (*d == b) { d++; if (d - buffer == len) { disable_interrupt_key (); return e - len; } } else { e -= d - buffer; d = buffer; } } } disable_interrupt_key (); return INVALID_OFFSET; } /* * Search in the hex mode. Supported input: * - numbers (oct, dec, hex). Each of them matches one byte. * - strings in double quotes. Matches exactly without quotes. */ static void hex_search (WView *view, const char *text) { char *buffer; /* Parsed search string */ char *cur; /* Current position in it */ int block_len; /* Length of the search string */ offset_type pos; /* Position of the string in the file */ int parse_error = 0; if (!*text) { view->found_len = 0; return; } /* buffer will never be longer that text */ buffer = g_new (char, strlen (text)); cur = buffer; /* First convert the string to a stream of bytes */ while (*text) { int val; int ptr; /* Skip leading spaces */ if (*text == ' ' || *text == '\t') { text++; continue; } /* %i matches octal, decimal, and hexadecimal numbers */ if (sscanf (text, "%i%n", &val, &ptr) > 0) { /* Allow signed and unsigned char in the user input */ if (val < -128 || val > 255) { parse_error = 1; break; } *cur++ = (char) val; text += ptr; continue; } /* Try quoted string, strip quotes */ if (*text == '"') { const char *next_quote; text++; next_quote = strchr (text, '"'); if (next_quote) { memcpy (cur, text, next_quote - text); cur += next_quote - text; text = next_quote + 1; continue; } /* fall through */ } parse_error = 1; break; } block_len = cur - buffer; /* No valid bytes in the user input */ if (block_len <= 0 || parse_error) { message (0, _("Search"), _("Invalid hex search expression")); g_free (buffer); view->found_len = 0; return; } /* Then start the search */ pos = block_search (view, buffer, block_len); g_free (buffer); if (pos == INVALID_OFFSET) { message (0, _("Search"), _(" Search string not found ")); view->found_len = 0; return; } view->search_start = pos; view->found_len = block_len; /* Set the edit cursor to the search position, left nibble */ view->edit_cursor = view->search_start; view->nib_shift = 0; /* Adjust the file offset */ view->start_display = (pos & (~(view->bytes_per_line - 1))); if (view->start_display > get_bottom_first (view, 0, 0)) view->start_display = view->bottom_first; } static int regexp_view_search (WView *view, char *pattern, char *string, int match_type) { static regex_t r; static char *old_pattern = NULL; static int old_type; regmatch_t pmatch[1]; int i, flags = REG_ICASE; if (!old_pattern || strcmp (old_pattern, pattern) || old_type != match_type) { if (old_pattern) { regfree (&r); g_free (old_pattern); old_pattern = 0; } for (i = 0; pattern[i] != 0; i++) { if (isupper ((unsigned char) pattern[i])) { flags = 0; break; } } flags |= REG_EXTENDED; if (regcomp (&r, pattern, flags)) { message (1, MSG_ERROR, _(" Invalid regular expression ")); return -1; } old_pattern = g_strdup (pattern); old_type = match_type; } if (regexec (&r, string, 1, pmatch, 0) != 0) return 0; view->found_len = pmatch[0].rm_eo - pmatch[0].rm_so; view->search_start = pmatch[0].rm_so; return 1; } static void do_regexp_search (void *xview, char *regexp) { WView *view = (WView *) xview; view->search_exp = regexp; search (view, regexp, regexp_view_search); /* Had a refresh here */ view->dirty++; view_update (view, TRUE); } static void do_normal_search (void *xview, char *text) { WView *view = (WView *) xview; view->search_exp = text; if (view->hex_mode) hex_search (view, text); else search (view, text, icase_search_p); /* Had a refresh here */ view->dirty++; view_update (view, TRUE); } /* Real view only */ static void view_help_cmd (void) { interactive_display (NULL, "[Internal File Viewer]"); /* view_refresh (0); */ } /* Toggle between text edit and hex edit */ static void toggle_hexedit_mode (WView *view) { get_bottom_first (view, 1, 1); if (view->hexedit_mode) { view->hexedit_text = !view->hexedit_text; } else { view->hexedit_mode = !view->hexedit_mode; } view_labels (view); view->dirty++; view_update (view, TRUE); } /* Toggle between wrapped and unwrapped view */ static void toggle_wrap_mode (WView *view) { view->wrap_mode = 1 - view->wrap_mode; get_bottom_first (view, 1, 1); if (view->wrap_mode) view->start_col = 0; else { if (have_fast_cpu) { if (view->bottom_first < view->start_display) view->search_start = view->start_display = view->bottom_first; view->found_len = 0; } } view_labels (view); view->dirty++; view_update (view, TRUE); } /* Toggle between hex view and text view */ static void toggle_hex_mode (WView *view) { view->hex_mode = 1 - view->hex_mode; if (view->hex_mode) { /* Shift the line start to 0x____0 on entry, restore it for text */ view->start_save = view->start_display; view->start_display -= view->start_display % view->bytes_per_line; view->edit_cursor = view->start_display; view->widget.options |= W_WANT_CURSOR; } else { view->start_display = view->start_save; view->widget.options &= ~W_WANT_CURSOR; } altered_hex_mode = 1; get_bottom_first (view, 1, 1); view_labels (view); view->dirty++; view_update (view, TRUE); } /* Text view */ static void goto_line (WView *view) { char *line, prompt[BUF_SMALL]; int oldline = 1; int saved_wrap_mode = view->wrap_mode; offset_type i; view->wrap_mode = 0; for (i = view->first; i < view->start_display; i++) if (get_byte (view, i) == '\n') oldline++; g_snprintf (prompt, sizeof (prompt), _(" The current line number is %d.\n" " Enter the new line number:"), oldline); line = input_dialog (_(" Goto line "), prompt, ""); if (line) { if (*line) { move_to_top (view); view_move_forward (view, atol (line) - 1); } g_free (line); } view->dirty++; view->wrap_mode = saved_wrap_mode; view_update (view, TRUE); } /* Hex view */ static void goto_addr (WView *view) { char *line, *error, prompt[BUF_SMALL]; offset_type addr; g_snprintf (prompt, sizeof (prompt), _(" The current address is 0x%lx.\n" " Enter the new address:"), view->edit_cursor); line = input_dialog (_(" Goto Address "), prompt, ""); if (line) { if (*line) { addr = strtoul (line, &error, 0); if ((*error == '\0') && (addr <= view->last_byte)) { move_to_top (view); view_move_forward (view, addr / view->bytes_per_line); view->edit_cursor = addr; } } g_free (line); } view->dirty++; view_update (view, TRUE); } /* Both views */ static void regexp_search (WView *view, int direction) { char *regexp = ""; static char *old = 0; /* This is really an F6 key handler */ if (view->hex_mode) { /* Save it without a confirmation prompt */ if (view->change_list) save_edit_changes (view); return; } regexp = old ? old : regexp; regexp = input_dialog (_("Search"), _(" Enter regexp:"), regexp); if ((!regexp)) { return; } if ((!*regexp)) { g_free (regexp); return; } g_free (old); old = regexp; #if 0 /* Mhm, do we really need to load all the file in the core? */ if (view->bytes_read < view->last_byte) get_byte (view, view->last_byte - 1); /* Get the whole file in to memory */ #endif view->direction = direction; do_regexp_search (view, regexp); view->last_search = do_regexp_search; } static void regexp_search_cmd (WView *view) { regexp_search (view, 1); } /* Both views */ static void normal_search (WView *view, int direction) { static char *old; char *exp = old ? old : ""; enum { SEARCH_DLG_HEIGHT = 8, SEARCH_DLG_WIDTH = 58 }; static int replace_backwards; int treplace_backwards = replace_backwards; static QuickWidget quick_widgets[] = { {quick_button, 6, 10, 5, SEARCH_DLG_HEIGHT, N_("&Cancel"), 0, B_CANCEL, 0, 0, NULL}, {quick_button, 2, 10, 5, SEARCH_DLG_HEIGHT, N_("&OK"), 0, B_ENTER, 0, 0, NULL}, {quick_checkbox, 3, SEARCH_DLG_WIDTH, 4, SEARCH_DLG_HEIGHT, N_("&Backwards"), 0, 0, 0, 0, NULL}, {quick_input, 3, SEARCH_DLG_WIDTH, 3, SEARCH_DLG_HEIGHT, "", 52, 0, 0, 0, N_("Search")}, {quick_label, 2, SEARCH_DLG_WIDTH, 2, SEARCH_DLG_HEIGHT, N_(" Enter search string:"), 0, 0, 0, 0, 0}, NULL_QuickWidget }; static QuickDialog Quick_input = { SEARCH_DLG_WIDTH, SEARCH_DLG_HEIGHT, -1, 0, N_("Search"), "[Input Line Keys]", quick_widgets, 0 }; convert_to_display (old); quick_widgets[2].result = &treplace_backwards; quick_widgets[3].str_result = &exp; quick_widgets[3].text = exp; if (quick_dialog (&Quick_input) == B_CANCEL) { convert_from_input (old); return; } replace_backwards = treplace_backwards; convert_from_input (old); if ((!exp)) { return; } if ((!*exp)) { g_free (exp); return; } g_free (old); old = exp; convert_from_input (exp); view->direction = replace_backwards ? -1 : 1; do_normal_search (view, exp); view->last_search = do_normal_search; } static void normal_search_cmd (WView *view) { normal_search (view, 1); } static void change_viewer (WView *view) { char *s; char *t; if (*view->filename) { altered_magic_flag = 1; view->viewer_magic_flag = !view->viewer_magic_flag; s = g_strdup (view->filename); if (view->command) t = g_strdup (view->command); else t = 0; view_done (view); view_init (view, t, s, 0); g_free (s); g_free (t); view_labels (view); view->dirty++; view_update (view, TRUE); } } static void change_nroff (WView *view) { view->viewer_nroff_flag = !view->viewer_nroff_flag; altered_nroff_flag = 1; view_labels (view); view->dirty++; view_update (view, TRUE); } /* Real view only */ static void view_quit_cmd (WView *view) { if (view_ok_to_quit (view)) dlg_stop (view->widget.parent); } /* Define labels and handlers for functional keys */ static void view_labels (WView *view) { Dlg_head *h = view->widget.parent; define_label (h, 1, _("Help"), view_help_cmd); my_define (h, 10, _("Quit"), view_quit_cmd, view); my_define (h, 4, view->hex_mode ? _("Ascii") : _("Hex"), toggle_hex_mode, view); my_define (h, 5, view->hex_mode ? _("Goto") : _("Line"), view->hex_mode ? goto_addr : goto_line, view); my_define (h, 6, view->hex_mode ? _("Save") : _("RxSrch"), regexp_search_cmd, view); if (view->hex_mode) if (view->hexedit_mode) my_define (h, 2, view->hexedit_text ? _("EdHex") : _("EdText"), toggle_hexedit_mode, view); else { if (view->growing_buffer || view->have_frame) my_define (h, 2, "", NULL, view); else my_define (h, 2, _("Edit"), toggle_hexedit_mode, view); } else my_define (h, 2, view->wrap_mode ? _("UnWrap") : _("Wrap"), toggle_wrap_mode, view); my_define (h, 7, view->hex_mode ? _("HxSrch") : _("Search"), normal_search_cmd, view); my_define (h, 8, view->viewer_magic_flag ? _("Raw") : _("Parse"), change_viewer, view); if (!view->have_frame) { my_define (h, 9, view->viewer_nroff_flag ? _("Unform") : _("Format"), change_nroff, view); my_define (h, 3, _("Quit"), view_quit_cmd, view); } redraw_labels (h); } /* Check for left and right arrows, possibly with modifiers */ static cb_ret_t check_left_right_keys (WView *view, int c) { if (c == KEY_LEFT) { move_left (view); return MSG_HANDLED; } if (c == KEY_RIGHT) { move_right (view); return MSG_HANDLED; } /* Ctrl with arrows moves by 10 postions in the unwrap mode */ if (view->hex_mode || view->wrap_mode) return MSG_NOT_HANDLED; if (c == (KEY_M_CTRL | KEY_LEFT)) { view->start_col = view->start_col + 10; if (view->start_col > 0) view->start_col = 0; view->dirty++; return MSG_HANDLED; } if (c == (KEY_M_CTRL | KEY_RIGHT)) { view->start_col = view->start_col - 10; view->dirty++; return MSG_HANDLED; } return MSG_NOT_HANDLED; } static void set_monitor (WView *view, int set_on) { int old = view->monitor; view->monitor = set_on; if (view->monitor) { move_to_bottom (view); view->bottom_first = INVALID_OFFSET; set_idle_proc (view->widget.parent, 1); } else { if (old) set_idle_proc (view->widget.parent, 0); } } static void continue_search (WView *view) { if (view->last_search) { (*view->last_search) (view, view->search_exp); } else { /* if not... then ask for an expression */ normal_search (view, 1); } } /* Both views */ static cb_ret_t view_handle_key (WView *view, int c) { int prev_monitor = view->monitor; set_monitor (view, off); c = convert_from_input_c (c); if (view->hex_mode) { switch (c) { case 0x09: /* Tab key */ view->hexedit_text = !view->hexedit_text; view->dirty++; return MSG_HANDLED; case XCTRL ('a'): /* Beginning of line */ view->edit_cursor -= view->edit_cursor % view->bytes_per_line; view->dirty++; return MSG_HANDLED; case XCTRL ('b'): /* Character back */ move_left (view); return MSG_HANDLED; case XCTRL ('e'): /* End of line */ view_move_cursor_to_eol (view); return MSG_HANDLED; case XCTRL ('f'): /* Character forward */ move_right (view); return MSG_HANDLED; } if (view->hexedit_mode && view_handle_editkey (view, c) == MSG_HANDLED) return MSG_HANDLED; } if (check_left_right_keys (view, c)) return MSG_HANDLED; if (check_movement_keys (c, vheight, view, (movefn) view_move_backward, (movefn) view_move_forward, (movefn) move_to_top, (movefn) move_to_bottom)) { return MSG_HANDLED; } switch (c) { case '?': regexp_search (view, -1); return MSG_HANDLED; case '/': regexp_search (view, 1); return MSG_HANDLED; /* Continue search */ case XCTRL ('s'): case 'n': case KEY_F (17): continue_search (view); return MSG_HANDLED; case XCTRL ('r'): if (view->last_search) { (*view->last_search) (view, view->search_exp); } else { normal_search (view, -1); } return MSG_HANDLED; /* toggle ruler */ case ALT ('r'): switch (ruler) { case 0: ruler = 1; break; case 1: ruler = 2; break; default: ruler = 0; break; } view->dirty++; return MSG_HANDLED; case 'h': move_left (view); return MSG_HANDLED; case 'j': case '\n': case 'e': view_move_forward (view, 1); return MSG_HANDLED; case 'd': view_move_forward (view, vheight / 2); return MSG_HANDLED; case 'u': view_move_backward (view, vheight / 2); return MSG_HANDLED; case 'k': case 'y': view_move_backward (view, 1); return MSG_HANDLED; case 'l': move_right (view); return MSG_HANDLED; case ' ': case 'f': view_move_forward (view, vheight - 1); return MSG_HANDLED; case XCTRL ('o'): view_other_cmd (); return MSG_HANDLED; /* Unlike Ctrl-O, run a new shell if the subshell is not running. */ case '!': exec_shell (); return MSG_HANDLED; case 'F': set_monitor (view, on); return MSG_HANDLED; case 'b': view_move_backward (view, vheight - 1); return MSG_HANDLED; case KEY_IC: view_move_backward (view, 2); return MSG_HANDLED; case KEY_DC: view_move_forward (view, 2); return MSG_HANDLED; case 'm': view->marks[view->marker] = view->start_display; return MSG_HANDLED; case 'r': view->start_display = view->marks[view->marker]; view->dirty++; return MSG_HANDLED; /* Use to indicate parent that we want to see the next/previous file */ /* Only works on full screen mode */ case XCTRL ('f'): case XCTRL ('b'): if (!view->have_frame) view->move_dir = c == XCTRL ('f') ? 1 : -1; /* fall */ case 'q': case XCTRL ('g'): case ESC_CHAR: if (view_ok_to_quit (view)) view->view_quit = 1; return MSG_HANDLED; #ifdef HAVE_CHARSET case XCTRL ('t'): do_select_codepage (); view->dirty++; view_update (view, TRUE); return MSG_HANDLED; #endif /* HAVE_CHARSET */ } if (c >= '0' && c <= '9') view->marker = c - '0'; /* Restore the monitor status */ set_monitor (view, prev_monitor); /* Key not used */ return MSG_NOT_HANDLED; } /* Both views */ static int view_event (WView *view, Gpm_Event *event, int *result) { *result = MOU_NORMAL; /* We are not interested in the release events */ if (!(event->type & (GPM_DOWN | GPM_DRAG))) return 0; /* Wheel events */ if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) { view_move_backward (view, 2); return 1; } if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) { view_move_forward (view, 2); return 1; } /* Scrolling left and right */ if (!view->wrap_mode) { if (event->x < view->widget.cols / 4) { move_left (view); goto processed; } if (event->x > 3 * vwidth / 4) { move_right (view); goto processed; } } /* Scrolling up and down */ if (event->y < view->widget.lines / 3) { if (mouse_move_pages_viewer) view_move_backward (view, view->widget.lines / 2 - 1); else view_move_backward (view, 1); goto processed; } else if (event->y > 2 * vheight / 3) { if (mouse_move_pages_viewer) view_move_forward (view, vheight / 2 - 1); else view_move_forward (view, 1); goto processed; } return 0; processed: *result = MOU_REPEAT; return 1; } /* Real view only */ static int real_view_event (Gpm_Event *event, void *x) { int result; if (view_event ((WView *) x, event, &result)) view_update ((WView *) x, TRUE); return result; } static void view_adjust_size (Dlg_head *h) { WView *view; WButtonBar *bar; /* Look up the viewer and the buttonbar, we assume only two widgets here */ view = (WView *) find_widget_type (h, (callback_fn) view_callback); bar = find_buttonbar (h); widget_set_size (&view->widget, 0, 0, LINES - 1, COLS); widget_set_size (&bar->widget, LINES - 1, 0, 1, COLS); view_update_bytes_per_line (view); } /* Callback for the view dialog */ static cb_ret_t view_dialog_callback (Dlg_head *h, dlg_msg_t msg, int parm) { switch (msg) { case DLG_RESIZE: view_adjust_size (h); return MSG_HANDLED; default: return default_dlg_callback (h, msg, parm); } } /* Real view only */ int view (const char *_command, const char *_file, int *move_dir_p, int start_line) { int error; WView *wview; WButtonBar *bar; Dlg_head *view_dlg; /* Create dialog and widgets, put them on the dialog */ view_dlg = create_dlg (0, 0, LINES, COLS, NULL, view_dialog_callback, "[Internal File Viewer]", NULL, DLG_WANT_TAB); wview = view_new (0, 0, COLS, LINES - 1, 0); bar = buttonbar_new (1); add_widget (view_dlg, bar); add_widget (view_dlg, wview); error = view_init (wview, _command, _file, start_line); if (move_dir_p) *move_dir_p = 0; /* Please note that if you add another widget, * you have to modify view_adjust_size to * be aware of it */ if (!error) { run_dlg (view_dlg); if (move_dir_p) *move_dir_p = wview->move_dir; } destroy_dlg (view_dlg); return !error; } static void view_hook (void *v) { WView *view = (WView *) v; WPanel *panel; /* If the user is busy typing, wait until he finishes to update the screen */ if (!is_idle ()) { if (!hook_present (idle_hook, view_hook)) add_hook (&idle_hook, view_hook, v); return; } delete_hook (&idle_hook, view_hook); if (get_current_type () == view_listing) panel = current_panel; else if (get_other_type () == view_listing) panel = other_panel; else return; view_init (view, 0, panel->dir.list[panel->selected].fname, 0); display (view); view_status (view, TRUE); } static cb_ret_t view_callback (WView *view, widget_msg_t msg, int parm) { cb_ret_t i; Dlg_head *h = view->widget.parent; switch (msg) { case WIDGET_INIT: view_update_bytes_per_line (view); if (view->have_frame) add_hook (&select_file_hook, view_hook, view); else view_labels (view); return MSG_HANDLED; case WIDGET_DRAW: display (view); view_status (view, TRUE); return MSG_HANDLED; case WIDGET_CURSOR: if (view->hex_mode) view_place_cursor (view); return MSG_HANDLED; case WIDGET_KEY: i = view_handle_key ((WView *) view, parm); if (view->view_quit && !view->have_frame) dlg_stop (h); else { view_update (view, TRUE); } return i; case WIDGET_IDLE: /* This event is generated when the user is using the 'F' flag */ view->bottom_first = INVALID_OFFSET; move_to_bottom (view); display (view); view_status (view, TRUE); sleep (1); return MSG_HANDLED; case WIDGET_FOCUS: view_labels (view); return MSG_HANDLED; case WIDGET_DESTROY: view_done (view); if (view->have_frame) delete_hook (&select_file_hook, view_hook); return MSG_HANDLED; default: return default_proc (msg, parm); } } WView * view_new (int y, int x, int cols, int lines, int is_panel) { WView *view = g_new0 (WView, 1); init_widget (&view->widget, y, x, lines, cols, (callback_fn) view_callback, (mouse_h) real_view_event); view->hex_mode = default_hex_mode; view->hexedit_mode = default_hexedit_mode; view->viewer_magic_flag = default_magic_flag; view->viewer_nroff_flag = default_nroff_flag; view->have_frame = is_panel; view->last_byte = -1; view->wrap_mode = global_wrap_mode; widget_want_cursor (view->widget, 0); return view; }