Sanitize hed_block interface
[hed.git] / src / ui / fileshow.c
blob782d6ec159a089acb6aa8c199d7af3f80e506463
1 /* $Id$ */
3 /*
4 * hed - Hexadecimal editor
5 * Copyright (C) 2004 Petr Baudis <pasky@ucw.cz>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of version 2 of the GNU General Public License as
9 * published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 * As Beren looked into her eyes
23 * Within the shadows of her hair,
24 * The trembling starlight of the skies
25 * He saw there mirrored shimmering.
26 * Tinuviel the elven-fair,
27 * Immortal maiden elven-wise,
28 * About him cast her shadowy hair
29 * And arms like silver glimmering.
32 #include <config.h>
34 #include <ctype.h>
35 #include <curses.h>
36 #include <errno.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <sys/wait.h>
42 #include <hed.h>
44 #include <config/config.h>
45 #include <libhed/eval.h>
46 #include <libhed/file.h>
47 #include <term/term.h>
48 #include <ui/core.h>
49 #include <ui/error.h>
50 #include <ui/fileshow.h>
51 #include <ui/inputline.h>
52 #include <util/lists.h>
53 #include <main.h>
55 /* For now, we re-request the file fragment each time we redraw the thing.
56 * This might not be feasible in the future. */
58 /* Number of bytes to fetch at cursor position.
59 * Currently, at maximum 4 bytes are needed (for the s32/u32 display).
60 * If you need more in print_fileview_status, feel free to increase
61 * this constant and use the extra bytes.
63 #define CURSOR_LENGTH 4
65 /* TODO: Make the multiclipboard smarter - copy-on-write. */
66 /* TODO: Registers should work as in vim, esp. the unnamed (") register */
67 struct clipboard_register {
68 unsigned char *data;
69 off_t len;
71 static struct clipboard_register clipboard[1 + 'z' - 'a' + 1 + 'Z' - 'A' + 1 + '9' - '0' + 1];
73 static inline char
74 ch2mark(char ch)
76 return ('A' <= ch && ch <= 'Z') ? ch - 'A' :
77 ('a' <= ch && ch <= 'z') ? 26 + ch - 'a' :
78 -1;
81 static inline char
82 ch2reg(char ch)
84 return ('0' <= ch && ch <= '9') ? 52 + ch - '0' :
85 (ch == '"') ? 62 :
86 ch2mark(ch);
89 static bool subst_all;
92 struct fileshow_priv {
93 struct hed_file *file;
94 hed_cursor_t offset; /* Beginning of screen (file offset) */
96 int width, height; /* Width and height of the viewport */
97 ssize_t ssize; /* Size (in bytes) of the viewport */
99 hed_cursor_t cursor; /* Cursor position (file offset) */
100 hed_cursor_t visual; /* Visual mark */
101 int digitpos; /* Selected digit in the hexadecimal column. */
102 unsigned char curbyte; /* The value of the current byte under the cursor. */
104 hed_cursor_t insert;
106 char idmark; /* insertion/deletion mark at screen start */
108 struct expression *last_search;
109 enum { SDIR_FORWARD = 1, SDIR_BACK = -1 } last_search_dir;
111 enum { FSC_HEX, FSC_ASC } column;
112 enum ui_mode mode;
114 int reg;
116 int le; /* non-zero if little endian view */
119 static const char idmarks[] = {
120 '>', ' ', '<', /* insert start, normal, insert end */
121 ')', '|', '(', /* dtto after erase */
124 #define IDMARK_ERASE_IDX 3
126 static enum handler_result fileshow_handler(struct ui_component *, const struct ui_event *);
127 static long eval_reg_cb(void *hookdata, char reg, off_t ofs,
128 unsigned char *scramble, size_t len);
129 static long eval_mark_cb(void *hookdata, char mark,
130 unsigned char *scramble, size_t len);
132 enum ui_mode opt_defmode = -1;
134 static void
135 update_idmark(struct fileshow_priv *data)
137 int idx = 1;
139 /* marks can only happen at zero offset */
140 if (!data->offset.off) {
141 struct hed_tree *tree = file_blocks(data->file);
143 idx = !hed_block_is_inserted(data->offset.block);
144 if (hed_block_is_after_erase(tree, data->offset.block))
145 idx += IDMARK_ERASE_IDX;
146 if (hed_block_is_after_insert(tree, data->offset.block))
147 idx = 1;
149 data->idmark = idmarks[idx];
152 static unsigned
153 pos_column(struct fileshow_priv *data, uoff_t pos)
155 return pos % data->width;
158 static void
159 set_viewport(struct fileshow_priv *data, off_t pos)
161 file_get_cursor(data->file, pos, &data->offset);
162 update_idmark(data);
165 /* Update screen offset to make cursor fit into the current viewport */
166 static void
167 fixup_viewport(struct fileshow_priv *data)
169 off_t curline = data->cursor.pos - pos_column(data, data->cursor.pos);
171 if (curline < data->offset.pos)
172 set_viewport(data, curline);
173 else if (curline - data->ssize >= data->offset.pos)
174 set_viewport(data, curline - data->ssize + data->width);
177 static void
178 set_cursor(struct fileshow_priv *data, off_t pos)
180 file_get_cursor(data->file, pos, &data->cursor);
181 fixup_viewport(data);
184 /* Update cursor position to fit into the current viewport */
185 static void
186 fixup_cursor(struct fileshow_priv *data)
188 off_t curline = data->cursor.pos - pos_column(data, data->cursor.pos);
190 if (curline < data->offset.pos)
191 file_move_relative(&data->cursor,
192 data->offset.pos - curline);
193 else if (curline >= data->offset.pos + data->ssize)
194 file_move_relative(&data->cursor,
195 data->offset.pos + data->ssize - data->width - curline);
198 static void
199 slide_viewport(struct fileshow_priv *data, off_t num)
201 file_move_relative(&data->offset, num);
202 update_idmark(data);
203 fixup_cursor(data);
206 /* Initialize the component. */
207 void
208 fileshow_init(struct hed_file *file)
210 struct fileshow_priv *data;
211 struct ui_component *fileshow;
212 int width;
214 data = calloc(1, sizeof(struct fileshow_priv));
215 if (!data) goto nomem;
216 data->file = file;
217 data->reg = ch2reg('"');
218 data->le = arch_little_endian();
220 if ((int) opt_defmode >= 0) {
221 data->mode = opt_defmode;
222 } else {
223 char *defmode = get_opt_str("default_mode", "normal");
224 if (!strcmp(defmode, "normal")) {
225 data->mode = MODE_NORMAL;
226 } else if (!strcmp(defmode, "insert")) {
227 data->mode = MODE_INSERT;
228 } else if (!strcmp(defmode, "replace")) {
229 data->mode = MODE_REPLACE;
230 } else {
231 data->mode = MODE_NORMAL;
235 fileshow = ui_register(fileshow_handler, data);
236 if (!fileshow) {
237 free(data);
238 nomem:
239 sched_exit(EX_OSERR);
240 return;
243 /* TODO: Better height calculation. */
244 data->height = term_get_max_y() - 2;
245 width = (term_get_max_x() - /* ofs */ 8 - /* spaces */ 4);
246 width /= 4;
247 if (!opt_spreadview) {
248 width = 1 << bitsize(width);
249 width = get_opt_int("bytes_per_line", width);
251 data->width = width;
252 data->ssize = data->height * data->width;
254 file_get_cursor(data->file, 0, &data->cursor);
255 file_dup_cursor(&data->cursor, &data->offset);
256 data->idmark = ' ';
259 static void
260 fileshow_done(struct ui_component *comp)
262 free(comp->data);
263 ui_unregister(comp);
266 /* Check whether @pos is inside the visual block */
267 static int
268 is_inside_visual(struct fileshow_priv *data, off_t pos)
270 return (data->cursor.pos <= pos && pos <= data->visual.pos) ||
271 (data->visual.pos <= pos && pos <= data->cursor.pos);
274 /* Returns ZERO ON ERROR. */
275 static size_t
276 visual_extents(struct fileshow_priv *data, hed_cursor_t *base)
278 hed_cursor_t *basesrc;
279 size_t ret;
280 if (data->visual.pos < data->cursor.pos) {
281 ret = data->cursor.pos - data->visual.pos + 1;
282 basesrc = &data->visual;
283 } else {
284 ret = data->visual.pos - data->cursor.pos + 1;
285 basesrc = &data->cursor;
287 file_dup2_cursor(basesrc, base);
288 return ret;
291 #define BYTES_PER_LONG (sizeof(unsigned long))
293 #define DIGITS_MAX(num) ((num + 9)/10)
295 /* The decadic logarithm of 2^8 is approx. 2.408239965311849584,
296 * so 3 digits must be always sufficient to represent the largest
297 * value that can be held in one byte. */
298 #define DIGITS_PER_BYTE 3
300 static int
301 print_status_num(unsigned char *cursdata, int nbytes,
302 int little_endian, int x, int y, int width)
304 off_t num;
305 long flags;
307 flags = little_endian ? AEF_FORCELE : AEF_FORCEBE;
309 if (nbytes < 0) {
310 nbytes = -nbytes;
311 flags |= AEF_SIGNED;
314 assert(x && cursdata);
315 assert(nbytes * 8 <= _FILE_OFFSET_BITS);
317 num = bytestr2off(cursdata, nbytes, flags);
319 return term_printf(x, y, COLOR_STATUS, flags & AEF_SIGNED
320 ? "s%d:%*lld "
321 : "u%d:%*llu ",
322 nbytes * 8, width, (long long) num);
325 static void
326 print_fileview_status(struct fileshow_priv *data,
327 unsigned char *cursdata, int ofslen)
329 static char modechar[] = { 'N', 'R', 'I', 'V' };
330 int pos = 0;
331 const int y = data->height;
333 /* First, fill the status line with the status color */
334 term_set_bg_color(COLOR_STATUS);
335 term_clear_line(data->height);
337 /* We print the filename first so that it gets overwritten by
338 * the "more important" stuff. The most important part of the
339 * filename tends to be at the end anyway. */
340 term_print_string(term_get_max_x() - 1 - strlen(data->file->name),
341 y, COLOR_STATUS, data->file->name);
343 pos += term_printf(0, y, COLOR_STATUS, "%0*llx: %03d ", ofslen,
344 (unsigned long long) data->cursor.pos, *cursdata);
346 term_print_string(pos, y, COLOR_STATUS, data->le ? "LE: " : "BE: ");
347 pos += 4;
349 pos += print_status_num(cursdata, 2, data->le, pos, y, 6);
350 pos += print_status_num(cursdata, -2, data->le, pos, y, 6);
351 pos += print_status_num(cursdata, 4, data->le, pos, y, 11);
352 pos += print_status_num(cursdata, -4, data->le, pos, y, 11);
354 term_print_char(pos, y, COLOR_MODE,
355 file_is_modified(data->file) ? '*' : ' ');
356 term_print_char(pos + 1, y, COLOR_MODE, modechar[data->mode]);
360 static int
361 num_hex_width(uoff_t num)
363 /* Offset of the leftmost non-zero bit. */
364 int width = 0;
365 int i;
367 for (i = sizeof(uoff_t) * 8 / 2; i > 0; i /= 2) {
368 if (num & (~(uoff_t)0 << i)) {
369 num >>= i; width += i;
372 /* Round the width up to the hexadecimal places. */
373 return width / 4 + 1;
376 static void
377 fileshow_redraw(struct ui_component *comp)
379 struct fileshow_priv *data = comp->data;
380 hed_cursor_t pos;
381 char idmark; /* insertion/deletion marks */
383 uoff_t maxofs;
384 int ofslen;
385 int xindent, xascpos;
386 int row;
388 unsigned char *p;
389 unsigned char cursdata[CURSOR_LENGTH];
391 if (file_read_begin(data->file, &data->offset)) {
392 /* TODO: Better error reporting. */
393 perror("file_read_begin");
394 return;
397 /* Offset width is chosen to accomodate the whole file in its current
398 * size as well as the current screen. */
399 maxofs = data->offset.pos + data->ssize - 1;
400 if (maxofs < file_size(data->file))
401 maxofs = file_size(data->file);
402 ofslen = num_hex_width(maxofs);
403 /* Always print at least 4 digits. */
404 if (ofslen < 4)
405 ofslen = 4;
406 xindent = ofslen + 2;
407 xascpos = xindent + data->width * 3 + 1;
409 file_dup_cursor(&data->offset, &pos);
410 p = hed_block_data(pos.block) + pos.off;
411 idmark = data->idmark;
412 assert(pos.pos >= 0);
414 term_set_bg_color(COLOR_NEUTRAL);
415 erase();
417 for (row = 0; row < data->height; ++row) {
418 int col;
419 term_color oldv = 0;
421 term_printf(0, row, COLOR_OFFSET, "%0*llx", ofslen, pos.pos);
423 for (col = 0; col < data->width; ++col) {
424 char hex[3];
425 unsigned char asc;
426 term_color color, v;
428 v = (data->mode == MODE_VISUAL &&
429 is_inside_visual(data, pos.pos))
430 ? COLOR_VISUAL
431 : 0;
433 color = COLOR_DATA_BASE;
434 if (pos.pos >= file_size(data->file))
435 color |= COLOR_DATA_OUTSIDE;
437 term_color markcolor = (idmark == ' ')
438 ? (hed_block_is_dirty(pos.block)
439 ? COLOR_DIRTY
440 : color)
441 : COLOR_MARK;
442 term_print_char(xindent + col * 3 - 1, row,
443 markcolor | v | oldv, idmark);
445 asc = hed_block_is_virtual(pos.block)
446 ? 0x00 : *p++;
447 sprintf(hex, "%02x", asc);
448 asc = isprint(asc) ? asc : asc == '\0' ? '.' : ':';
450 if (pos.pos == data->cursor.pos) {
451 cursor_y = row;
452 cursor_x = data->column == FSC_HEX
453 ? xindent + col * 3 + data->digitpos
454 : xascpos + col;
455 color |= COLOR_DATA_CURSOR;
456 } else if (hed_block_is_dirty(pos.block))
457 color = COLOR_DIRTY;
458 color |= v;
460 term_print_string(xindent + col * 3, row,
461 color, hex);
462 if (color >= COLOR_DATA_BASE)
463 color |= COLOR_DATA_ASCII;
464 term_print_char(xascpos + col, row,
465 color, asc);
467 if (pos.pos >= OFF_MAX) {
468 term_print_char(xindent + col * 3 + 2, row,
469 COLOR_MARK | v, '!');
470 goto done;
473 ++pos.pos; ++pos.off;
474 if (pos.off >= hed_block_size(pos.block)) {
475 int idx = !!hed_block_is_inserted(pos.block);
476 file_next_block(data->file, &pos);
477 idx -= !!hed_block_is_inserted(pos.block);
478 if (hed_block_is_after_erase(
479 file_blocks(data->file),
480 pos.block))
481 idx += IDMARK_ERASE_IDX;
482 idmark = idmarks[idx + 1];
484 p = hed_block_data(pos.block);
485 } else
486 idmark = ' ';
488 oldv = v;
491 if (idmark == '<' || idmark == '(') {
492 term_print_char(xascpos - 2, row,
493 COLOR_MARK | oldv, idmark);
494 idmark = ' ';
495 } else if (oldv)
496 term_print_char(xascpos - 2, row,
497 COLOR_NEUTRAL | COLOR_VISUAL, ' ');
499 done:
500 file_put_cursor(&pos);
502 assert(data->cursor.pos >= 0);
504 memset(cursdata, 0, sizeof(cursdata));
505 file_cpin(data->file, cursdata, sizeof(cursdata), &data->cursor);
506 data->curbyte = cursdata[0];
508 file_read_end(data->file);
510 print_fileview_status(data, cursdata, ofslen);
511 term_unset_bg_color();
514 /* Jump to a position given by offset expression. */
515 static void
516 jump_offset(struct inputline_data *inpline, void *hookdata)
518 struct fileshow_priv *data = hookdata;
519 struct expression *expr = expr_compile(inpline->buf);
520 unsigned char *buf;
521 off_t pos;
523 if (!expr) {
524 errmsg("Invalid expression");
525 return;
527 buf = expr_eval(expr, eval_reg_cb, eval_mark_cb, data);
528 if (!buf) {
529 errmsg("Cannot evaluate expression");
530 expr_free(expr);
531 return;
533 pos = bytestr2off(buf, expr_len(expr), expr_flags(expr));
534 pos &= OFF_MAX;
535 expr_free(expr);
537 set_cursor(data, pos);
540 static int
541 jump_relative(struct fileshow_priv *data, int num)
543 /* Zero-length jump usually means an invalid jump out of bounds */
544 if (file_move_relative(&data->cursor, num) == 0)
545 return -1;
547 data->digitpos = 0;
548 fixup_viewport(data);
549 return 0;
552 static void
553 finish_insert(struct fileshow_priv *data)
555 if (is_a_cursor(&data->insert)) {
556 file_insert_end(data->file, &data->insert);
557 if (data->column == FSC_HEX && data->digitpos)
558 jump_relative(data, 1);
562 static void
563 erase_at_cursor(struct fileshow_priv *data, uoff_t len)
565 file_erase_block(data->file, &data->cursor, len);
566 if (!is_a_cursor(&data->offset)) {
567 file_dup2_cursor(&data->cursor, &data->offset);
568 file_move_relative(&data->offset,
569 -pos_column(data, data->offset.pos));
570 update_idmark(data);
574 static off_t
575 do_search(struct fileshow_priv *data)
577 hed_cursor_t off;
578 int res;
580 file_dup_cursor(&data->cursor, &off);
581 file_move_relative(&off, data->last_search_dir);
582 res = file_find_expr(data->file, &off, data->last_search_dir,
583 data->last_search, eval_reg_cb, data);
584 if (!res)
585 set_cursor(data, off.pos);
586 else if (!subst_all)
587 errmsg("No match found");
589 file_put_cursor(&off);
590 return off.pos;
593 /* Search for the given string. */
594 static void
595 expr_search(struct inputline_data *inpline, void *hookdata)
597 struct fileshow_priv *data = hookdata;
599 if (data->last_search)
600 expr_free(data->last_search);
601 data->last_search = expr_compile(inpline->buf);
602 if (!data->last_search) {
603 errmsg("Invalid expression");
604 return;
607 do_search(data);
610 /* Search and substitute the given string. */
611 static void
612 expr_subst(struct inputline_data *inpline, void *hookdata)
614 struct fileshow_priv *data = hookdata;
615 struct expression *subst;
616 unsigned char *buf;
618 subst = expr_compile(inpline->buf);
619 if (!subst) {
620 errmsg("Invalid expression");
621 return;
624 data->last_search_dir = SDIR_FORWARD;
625 while (do_search(data) != FINDOFF_NO_MATCH) {
626 buf = expr_eval(subst, eval_reg_cb, eval_mark_cb, data);
627 if (!buf) {
628 errmsg("Cannot evaluate expression");
629 break;
631 /* Cursor at the match */
632 erase_at_cursor(data, expr_len(data->last_search));
633 file_insert_once(data->file, &data->cursor,
634 buf, expr_len(subst));
635 if (!subst_all)
636 break;
638 expr_free(subst);
641 static void
642 expr_subst1(struct inputline_data *inpline, void *hookdata)
644 struct fileshow_priv *data = hookdata;
646 if (data->last_search)
647 expr_free(data->last_search);
648 data->last_search = expr_compile(inpline->buf);
649 if (!data->last_search) {
650 errmsg("Invalid expression");
651 return;
653 inputline_init("s/.../", expr_subst, data);
656 static void
657 clip_yank(struct fileshow_priv *data)
659 hed_cursor_t base = HED_NULL_CURSOR;
660 size_t len = visual_extents(data, &base);
662 file_put_cursor(&base);
663 if (clipboard[data->reg].data)
664 free(clipboard[data->reg].data);
665 clipboard[data->reg].data =
666 file_fetch_block(data->file, base.pos, &len);
667 clipboard[data->reg].len = len;
670 /* Those functions are totally horrible kludges. There should be specialized
671 * file methods for those operations. */
673 static void
674 clip_delete(struct fileshow_priv *data)
676 size_t len;
678 clip_yank(data);
679 len = visual_extents(data, &data->cursor);
680 erase_at_cursor(data, len);
683 static void
684 clip_put(struct fileshow_priv *data)
686 if (!clipboard[data->reg].data) {
687 errmsg("Register empty");
688 return;
690 file_insert_once(data->file, &data->cursor,
691 clipboard[data->reg].data, clipboard[data->reg].len);
692 if (data->offset.pos == data->cursor.pos)
693 file_move_relative(&data->offset, -clipboard[data->reg].len);
696 static void
697 clip_overwrite(struct fileshow_priv *data)
699 hed_cursor_t start = HED_NULL_CURSOR;
700 off_t i;
701 ssize_t len;
703 if (!clipboard[data->reg].data) {
704 errmsg("Register empty");
705 return;
708 if (data->mode == MODE_VISUAL) {
709 /* spill */
710 len = visual_extents(data, &start);
711 } else {
712 /* splat */
713 file_dup_cursor(&data->cursor, &start);
714 len = clipboard[data->reg].len;
717 i = min(len, clipboard[data->reg].len);
718 file_set_block(data->file, &start, clipboard[data->reg].data, i);
719 file_set_bytes(data->file, &start, 0, len - i);
720 file_put_cursor(&start);
723 static void
724 clip_swap(struct fileshow_priv *data)
726 struct clipboard_register orig, new;
728 new = clipboard[data->reg];
729 clipboard[data->reg].data = NULL;
730 clip_delete(data);
731 orig = clipboard[data->reg];
732 clipboard[data->reg] = new;
733 clip_put(data);
734 clipboard[data->reg] = orig;
738 static void
739 set_width(struct inputline_data *inpline, void *hookdata)
741 struct fileshow_priv *data = hookdata;
742 char *l;
743 int w;
744 w = strtol(inpline->buf, &l, 0);
745 if (*l) {
746 errmsg("Invalid width");
747 return;
749 data->width = w;
753 static void
754 read_region(struct inputline_data *inpline, void *hookdata)
756 struct fileshow_priv *data = hookdata;
757 char *fname = inpline->buf;
758 int flen = inpline->buf_len;
759 FILE *f;
760 int ch;
762 /* Trim leading/trailing whitespaces */
763 while (isspace(*fname))
764 fname++, flen--;
765 while (isspace(fname[flen - 1]) && flen > 0)
766 flen--;
767 if (!*fname)
768 return;
769 f = fopen(fname, "r");
770 if (!f) {
771 errmsg(strerror(errno));
772 return;
775 if (data->mode == MODE_VISUAL) {
776 size_t len = visual_extents(data, &data->cursor);
777 erase_at_cursor(data, len);
780 /* Ugh. :-) */
781 file_insert_begin(data->file, &data->cursor, &data->insert);
782 while ((ch = fgetc(f)) != EOF)
783 file_insert_byte(data->file, &data->insert, ch);
784 finish_insert(data);
785 fclose(f);
788 static void
789 write_region(struct inputline_data *inpline, void *hookdata)
791 struct fileshow_priv *data = hookdata;
792 hed_cursor_t base = HED_NULL_CURSOR;
793 size_t len = visual_extents(data, &base);
794 unsigned char *buf;
795 char *fname = inpline->buf;
796 int flen = inpline->buf_len;
797 FILE *f;
799 file_put_cursor(&base);
801 /* Trim leading/trailing whitespaces */
802 while (isspace(*fname))
803 fname++, flen--;
804 while (isspace(fname[flen - 1]) && flen > 0)
805 flen--;
806 if (!*fname)
807 return;
809 /* FIXME: Don't do it at once */
810 buf = file_fetch_block(data->file, base.pos, &len);
811 f = fopen(fname, "w");
812 if (!f) {
813 errmsg(strerror(errno));
814 free(buf);
815 return;
817 fwrite(buf, len, 1, f);
818 fclose(f);
819 free(buf);
823 /* Arbitrarily chosen... */
824 #define CHUNK_SIZE 1024
826 static int
827 filter_select(struct fileshow_priv *data, off_t base, ssize_t len, int fd_in, int fd_out)
829 fd_set fdr, fdw;
830 int ret;
832 file_insert_begin(data->file, &data->cursor, &data->insert);
833 while (fd_out || fd_in) {
834 FD_ZERO(&fdr);
835 FD_ZERO(&fdw);
836 if (fd_in) FD_SET(fd_in, &fdr);
837 if (fd_out) FD_SET(fd_out, &fdw);
839 ret = select(fd_in+fd_out+1, &fdr, &fdw, NULL, NULL);
841 if (ret <= 0)
842 continue;
844 if (FD_ISSET(fd_in, &fdr)) {
845 unsigned char buf[CHUNK_SIZE];
846 ret = read(fd_in, buf, CHUNK_SIZE);
847 if (ret < 0)
848 goto err;
849 if (ret == 0) {
850 close(fd_in);
851 fd_in = 0;
853 file_insert_block(data->file, &data->insert, buf, ret);
854 data->cursor.pos += ret;
857 if (FD_ISSET(fd_out, &fdw)) {
858 size_t blen = min(len, CHUNK_SIZE);
859 unsigned char *buf = file_fetch_block(data->file, base, &blen);
860 if (blen < CHUNK_SIZE) {
861 len = 0;
862 } else {
863 len -= blen;
865 ret = write(fd_out, buf, blen);
866 free(buf);
867 if (ret <= 0)
868 goto err;
869 if (ret < blen)
870 len += blen - ret;
871 base += ret;
873 if (!len) {
874 close(fd_out);
875 fd_out = 0;
879 finish_insert(data);
880 return 0;
881 err:
882 finish_insert(data);
883 if (fd_out)
884 close(fd_out);
885 if (fd_in)
886 close(fd_in);
887 return -1;
890 static void
891 pipe_region(struct inputline_data *inpline, void *hookdata)
893 struct fileshow_priv *data = hookdata;
894 hed_cursor_t base = HED_NULL_CURSOR;
895 size_t len = visual_extents(data, &base);
896 char *argv[4] = { "/bin/sh", "-c", inpline->buf, NULL };
897 int p_rd[2], p_wr[2];
898 pid_t pid;
900 file_put_cursor(&base);
902 file_put_cursor(&data->visual);
903 data->mode = MODE_NORMAL;
905 pipe(p_rd);
906 pipe(p_wr);
908 pid = fork();
909 if (pid == 0) {
910 close(p_rd[1]);
911 close(p_wr[0]);
913 dup2(p_rd[0], 0);
914 dup2(p_wr[1], 1);
916 exit(execvp(argv[0], argv));
918 } else if (pid > 0) {
919 int status, ret;
921 close(p_rd[0]);
922 close(p_wr[1]);
924 set_cursor(data, base.pos + len);
925 if (filter_select(data, base.pos, len, p_wr[0], p_rd[1]) < 0) {
926 errmsg("Filter failed");
927 return;
929 ret = waitpid(pid, &status, 0);
930 if (ret != pid) {
931 errmsg("Funny zombie");
932 return;
934 if (WIFEXITED(status)) {
935 if (WEXITSTATUS(status))
936 errmsg("Filter returned error");
937 set_cursor(data, base.pos);
938 erase_at_cursor(data, len);
939 return;
941 errmsg("Curious error");
942 return;
944 } else {
945 errmsg("Fork failed");
946 return;
950 static hed_cursor_t xre;
952 static long
953 eval_mark_cb(void *hookdata, char mark,
954 unsigned char *scramble, size_t len)
956 struct fileshow_priv *data = hookdata;
957 int m = ch2mark(mark);
958 if (m >= 0) {
959 if (file_mark_is_valid(data->file, m)) {
960 off2bytestr(scramble, len,
961 file_mark(data->file, m)->pos);
962 return 0;
964 } else {
965 if (mark == '$') {
966 off2bytestr(scramble, len, data->cursor.pos);
967 return 0;
970 memset(scramble, 0, len);
971 return 0;
975 static long
976 eval_reg_cb(void *hookdata, char reg, off_t ofs,
977 unsigned char *scramble, size_t len)
979 struct fileshow_priv *data = hookdata;
980 unsigned char *buf;
981 size_t blen;
982 hed_cursor_t blkpos = HED_NULL_CURSOR;
983 off_t skip;
984 int r;
985 long ret;
987 switch (reg) {
988 case ',':
989 file_dup_cursor(&data->cursor, &blkpos);
990 break;
991 case '_':
992 file_get_cursor(data->file, 0, &blkpos);
993 break;
994 case '.':
995 if (!is_a_cursor(&xre))
996 goto out_zero;
997 file_dup_cursor(&xre, &blkpos);
998 break;
999 default:
1000 r = ch2reg(reg);
1001 if (r < 0)
1002 goto out_zero;
1003 buf = clipboard[r].data; blen = clipboard[r].len;
1004 if (ofs < 0) {
1005 if (-ofs >= len)
1006 goto out_zero;
1007 memset(scramble, 0, -ofs);
1008 scramble -= ofs;
1009 len += ofs;
1010 } else {
1011 if (ofs >= blen)
1012 goto out_zero;
1013 buf += ofs;
1014 blen -= ofs;
1016 if (blen > len)
1017 blen = len;
1018 memcpy(scramble, buf, blen);
1019 if (blen < len)
1020 memset(scramble + blen, 0, len - blen);
1021 return 0;
1024 skip = file_move_relative(&blkpos, ofs) - ofs;
1025 if (skip) {
1026 if (ofs >= 0 || skip >= len)
1027 goto out_zero;
1028 memset(scramble, 0, skip);
1029 scramble += skip;
1030 len -= skip;
1032 ret = file_cpin(data->file, scramble, len, &blkpos)
1033 ? AEF_ERROR : 0;
1034 file_put_cursor(&blkpos);
1035 return ret;
1037 out_zero:
1038 memset(scramble, 0, len);
1039 return 0;
1043 static void
1044 ieval_expr(struct inputline_data *inpline, void *hookdata)
1046 struct fileshow_priv *data = hookdata;
1047 struct expression *expr = expr_compile(inpline->buf);
1048 unsigned char *buf;
1050 if (!expr) {
1051 errmsg("Invalid expression");
1052 return;
1054 buf = expr_eval(expr, eval_reg_cb, eval_mark_cb, data);
1055 if (!buf) {
1056 errmsg("Cannot evaluate expression");
1057 expr_free(expr);
1058 return;
1060 file_insert_once(data->file, &data->cursor, buf, expr_len(expr));
1061 expr_free(expr);
1064 static void
1065 peval_expr(struct inputline_data *inpline, void *hookdata)
1067 struct fileshow_priv *data = hookdata;
1068 struct expression *expr = expr_compile(inpline->buf);
1069 static char *resbuf;
1070 char *p;
1071 size_t i, len;
1072 unsigned char *buf;
1074 if (!expr) {
1075 errmsg("Invalid expression");
1076 return;
1078 buf = expr_eval(expr, eval_reg_cb, eval_mark_cb, data);
1079 if (!buf) {
1080 errmsg("Cannot evaluate expression");
1081 expr_free(expr);
1082 return;
1084 len = expr_len(expr);
1085 p = realloc(resbuf, sizeof("Result: -") + 3 * len);
1086 if (!p) {
1087 errmsg("Cannot allocate buffer");
1088 return;
1091 resbuf = p;
1092 p = stpcpy(resbuf, "Result:");
1093 if (expr_flags(expr) & AEF_NEGATIVE)
1094 p = stpcpy(p, " -");
1095 for (i = 0; i < len; ++i)
1096 p += sprintf(p, " %02x", buf[i]);
1097 expr_free(expr);
1098 errmsg(resbuf);
1102 static void
1103 reval_expr(struct inputline_data *inpline, void *hookdata)
1105 struct fileshow_priv *data = hookdata;
1106 struct expression *expr = expr_compile(inpline->buf);
1107 int elen;
1108 unsigned char *ebuf;
1109 size_t i, len;
1111 if (!expr) {
1112 errmsg("Invalid expression");
1113 goto out;
1115 elen = expr_len(expr);
1116 len = visual_extents(data, &xre);
1117 for (i = 0; i < len; i += elen) {
1118 size_t rlen;
1119 ebuf = expr_eval(expr, eval_reg_cb, eval_mark_cb, data);
1120 if (!ebuf) {
1121 errmsg("Cannot evaluate expression");
1122 break;
1124 rlen = elen;
1125 if (rlen > len - i)
1126 rlen = len - i;
1127 file_set_block(data->file, &xre, ebuf, rlen);
1129 expr_free(expr);
1130 file_put_cursor(&xre);
1131 xre.block = NULL;
1132 out:
1133 if (data->mode != MODE_VISUAL)
1134 file_put_cursor(&data->visual);
1137 static void exmode(struct inputline_data *inpline, void *hookdata);
1139 /* Evaluate an exmode command. */
1140 static void
1141 exmode_cmd_write(struct fileshow_priv *data)
1143 if (data->mode == MODE_VISUAL) {
1144 /* Write just the visual */
1145 inputline_init("File: ", write_region, data);
1146 } else
1147 file_commit(data->file);
1149 static void
1150 exmode_cmd_read(struct fileshow_priv *data)
1152 inputline_init("File: ", read_region, data);
1154 static void
1155 exmode_cmd_pipe(struct fileshow_priv *data)
1157 inputline_init("Command: ", pipe_region, data);
1159 static void
1160 exmode_cmd_subst(struct fileshow_priv *data)
1162 subst_all = false;
1163 inputline_init("s/", expr_subst1, data);
1165 static void
1166 exmode_cmd_Subst(struct fileshow_priv *data)
1168 subst_all = true;
1169 inputline_init("S/", expr_subst1, data);
1171 static void
1172 exmode_cmd_swp(struct fileshow_priv *data)
1174 file_write_swap(data->file);
1176 static void
1177 exmode_cmd_ieval(struct fileshow_priv *data)
1179 inputline_init("Expression: ", ieval_expr, data);
1181 static void
1182 exmode_cmd_reval(struct fileshow_priv *data)
1184 if (data->mode != MODE_VISUAL)
1185 file_dup_cursor(&data->cursor, &data->visual);
1186 inputline_init("Expression: ", reval_expr, data);
1188 static void
1189 exmode_cmd_peval(struct fileshow_priv *data)
1191 inputline_init("Expression: ", peval_expr, data);
1193 static void
1194 exmode_cmd_width(struct fileshow_priv *data)
1196 inputline_init("Bytes per line: ", set_width, data);
1198 static void
1199 exmode_cmd_quit(struct fileshow_priv *data)
1201 terminus = true;
1203 static void
1204 exmode_cmd_exit(struct fileshow_priv *data)
1206 if (file_is_modified(data->file))
1207 file_commit(data->file);
1208 exmode_cmd_quit(data);
1211 enum command {
1212 cmd_backspace,
1213 cmd_clip_delete,
1214 cmd_clip_overwrite,
1215 cmd_clip_paste,
1216 cmd_clip_paste_after,
1217 cmd_clip_yank,
1218 cmd_delchar,
1219 cmd_delchar_back,
1220 cmd_endian,
1221 cmd_exit,
1222 cmd_exmode,
1223 cmd_file_begin,
1224 cmd_file_end,
1225 cmd_go_down,
1226 cmd_go_left,
1227 cmd_go_right,
1228 cmd_goto,
1229 cmd_go_up,
1230 cmd_ieval,
1231 cmd_insert,
1232 cmd_line_begin,
1233 cmd_line_end,
1234 cmd_normal_mode,
1235 cmd_page_down,
1236 cmd_page_up,
1237 cmd_peval,
1238 cmd_pipe,
1239 cmd_prefix,
1240 cmd_quit,
1241 cmd_read,
1242 cmd_replace,
1243 cmd_reval,
1244 cmd_scroll_down,
1245 cmd_scroll_up,
1246 cmd_search,
1247 cmd_search_back,
1248 cmd_search_next,
1249 cmd_search_prev,
1250 cmd_subst,
1251 cmd_substall,
1252 cmd_switch_column,
1253 cmd_swp,
1254 cmd_visual,
1255 cmd_width,
1256 cmd_write
1259 static enum handler_result
1260 do_command(struct fileshow_priv *data, enum command cmd)
1262 switch (cmd) {
1264 /* CURSOR MOVEMENT */
1266 case cmd_go_down:
1267 jump_relative(data, data->width);
1268 break;
1270 case cmd_go_up:
1271 jump_relative(data, -data->width);
1272 break;
1274 case cmd_go_left:
1275 if (data->column == FSC_HEX && data->digitpos > 0) {
1276 data->digitpos--;
1277 } else if (!jump_relative(data, -1) &&
1278 data->column == FSC_HEX)
1279 data->digitpos = 1;
1280 break;
1282 case cmd_go_right:
1283 if (data->column == FSC_HEX && data->digitpos < 1) {
1284 data->digitpos++;
1285 } else
1286 jump_relative(data, 1);
1287 break;
1289 case cmd_line_begin:
1290 if (!jump_relative(data, -pos_column(data, data->cursor.pos)))
1291 data->digitpos = 0;
1292 break;
1294 case cmd_line_end:
1295 if (!jump_relative(data, data->width -
1296 pos_column(data, data->cursor.pos) - 1))
1297 data->digitpos = 0;
1298 break;
1300 case cmd_file_end:
1301 if (file_size(data->file)) {
1302 set_cursor(data, file_size(data->file) - 1);
1303 data->digitpos = 0;
1304 break;
1306 /* else fall-through */
1308 case cmd_file_begin:
1309 set_cursor(data, 0);
1310 data->digitpos = 0;
1311 break;
1313 case cmd_page_down:
1314 slide_viewport(data, data->ssize - data->width);
1315 break;
1317 case cmd_page_up:
1318 slide_viewport(data, -data->ssize + data->width);
1319 break;
1321 case cmd_scroll_down:
1322 slide_viewport(data, data->width);
1323 break;
1325 case cmd_scroll_up:
1326 slide_viewport(data, -data->width);
1327 break;
1329 /* SIMPLE COMMANDS */
1331 case cmd_switch_column:
1332 data->column ^= 1;
1333 break;
1335 case cmd_visual:
1336 if (data->mode == MODE_VISUAL) {
1337 file_put_cursor(&data->visual);
1338 data->mode = MODE_NORMAL;
1339 } else {
1340 data->mode = MODE_VISUAL;
1341 file_dup_cursor(&data->cursor, &data->visual);
1343 break;
1345 case cmd_normal_mode:
1346 if (data->mode == MODE_VISUAL)
1347 file_put_cursor(&data->visual);
1348 data->mode = MODE_NORMAL;
1349 break;
1351 case cmd_insert:
1352 if (data->mode == MODE_INSERT)
1353 data->mode = MODE_REPLACE;
1354 else
1355 data->mode = MODE_INSERT;
1356 break;
1358 case cmd_replace:
1359 data->mode = MODE_REPLACE;
1360 break;
1362 case cmd_delchar:
1363 erase_at_cursor(data, 1);
1364 break;
1366 case cmd_delchar_back:
1367 if (data->cursor.pos > 0) {
1368 jump_relative(data, -1);
1369 erase_at_cursor(data, 1);
1371 break;
1373 case cmd_backspace:
1374 return do_command(data, (cmd = data->mode == MODE_INSERT
1375 ? cmd_delchar_back : cmd_go_left));
1377 /* CLIPBOARD */
1379 case cmd_clip_yank:
1380 if (data->mode != MODE_VISUAL) {
1381 errmsg("No region selected");
1382 return EVH_SILENTHOLD;
1384 clip_yank(data);
1385 file_put_cursor(&data->visual);
1386 data->mode = MODE_NORMAL;
1387 break;
1389 case cmd_clip_delete:
1390 if (data->mode != MODE_VISUAL) {
1391 errmsg("No region selected");
1392 return EVH_SILENTHOLD;
1394 clip_delete(data);
1395 file_put_cursor(&data->visual);
1396 data->mode = MODE_NORMAL;
1397 break;
1399 case cmd_clip_paste_after:
1400 if (data->mode != MODE_VISUAL)
1401 file_move_relative(&data->cursor, 1);
1402 /* fall through */
1403 case cmd_clip_paste:
1404 if (data->mode == MODE_VISUAL) {
1405 clip_swap(data);
1406 file_put_cursor(&data->visual);
1407 data->mode = MODE_NORMAL;
1408 } else {
1409 clip_put(data);
1410 file_move_relative(&data->cursor, -1);
1412 break;
1414 case cmd_clip_overwrite:
1415 clip_overwrite(data);
1416 if (data->mode == MODE_VISUAL) {
1417 file_put_cursor(&data->visual);
1418 data->mode = MODE_NORMAL;
1420 break;
1422 /* INPUTLINE */
1424 case cmd_exmode:
1425 inputline_init(":", exmode, data);
1426 return EVH_SILENTHOLD;
1428 case cmd_goto:
1429 inputline_init("#", jump_offset, data);
1430 return EVH_SILENTHOLD;
1432 case cmd_search:
1433 data->last_search_dir = SDIR_FORWARD;
1434 inputline_init("/", expr_search, data);
1435 return EVH_SILENTHOLD;
1437 case cmd_search_back:
1438 data->last_search_dir = SDIR_BACK;
1439 inputline_init("?", expr_search, data);
1440 return EVH_SILENTHOLD;
1442 case cmd_search_prev:
1443 data->last_search_dir = -data->last_search_dir;
1444 /* fall through */
1445 case cmd_search_next:
1446 if (data->last_search)
1447 do_search(data);
1448 else
1449 errmsg("No previous search");
1451 if (cmd == cmd_search_prev)
1452 data->last_search_dir = -data->last_search_dir;
1453 break;
1455 /* EXMODE */
1457 case cmd_quit:
1458 exmode_cmd_quit(data);
1459 break;
1461 case cmd_exit:
1462 exmode_cmd_exit(data);
1463 break;
1465 case cmd_write:
1466 exmode_cmd_write(data);
1467 break;
1469 case cmd_read:
1470 exmode_cmd_read(data);
1471 break;
1473 case cmd_pipe:
1474 if (data-> mode == MODE_VISUAL)
1475 exmode_cmd_pipe(data);
1476 break;
1478 case cmd_subst:
1479 exmode_cmd_subst(data);
1480 break;
1482 case cmd_substall:
1483 exmode_cmd_Subst(data);
1484 break;
1486 case cmd_swp:
1487 exmode_cmd_swp(data);
1488 break;
1490 case cmd_ieval:
1491 exmode_cmd_ieval(data);
1492 break;
1494 case cmd_reval:
1495 exmode_cmd_reval(data);
1496 break;
1498 case cmd_peval:
1499 exmode_cmd_peval(data);
1500 break;
1502 case cmd_width:
1503 exmode_cmd_width(data);
1504 break;
1506 case cmd_endian:
1507 data->le ^= 1;
1508 break;
1510 /* PREFIX */
1512 case cmd_prefix:
1513 return EVH_XPREFIX;
1517 return EVH_HOLD;
1520 struct longcmd {
1521 const char *name;
1522 enum command cmd;
1525 static void
1526 exmode(struct inputline_data *inpline, void *hookdata)
1528 static const struct longcmd cmds[] = {
1529 { "!", cmd_pipe },
1530 { "endian", cmd_endian },
1531 { "exit", cmd_exit },
1532 { "ie", cmd_ieval },
1533 { "ieval", cmd_ieval },
1534 { "pe", cmd_peval },
1535 { "peval", cmd_peval },
1536 { "pipe", cmd_pipe },
1537 { "q", cmd_quit },
1538 { "quit", cmd_quit },
1539 { "r", cmd_read },
1540 { "read", cmd_read },
1541 { "re", cmd_reval },
1542 { "reval", cmd_reval },
1543 { "s", cmd_subst },
1544 { "S", cmd_substall },
1545 { "substall", cmd_substall },
1546 { "substitute", cmd_subst },
1547 { "swp", cmd_swp },
1548 { "w", cmd_write },
1549 { "width", cmd_width },
1550 { "write", cmd_write },
1551 { "x", cmd_exit },
1552 { NULL }
1555 struct fileshow_priv *data = hookdata;
1556 char *cmd = inpline->buf;
1557 int len = inpline->buf_len;
1558 const struct longcmd *p;
1560 /* Trim leading/trailing whitespaces */
1561 while (isspace(*cmd))
1562 cmd++, len--;
1563 while (isspace(cmd[len - 1]) && len > 0)
1564 len--;
1566 for (p = cmds; p->name; ++p)
1567 if (!strcmp(cmd, p->name)) {
1568 do_command(data, p->cmd);
1569 return;
1572 errmsg("Invalid command");
1575 struct shortcmd {
1576 int ch;
1577 enum command cmd;
1580 #define CTRL(ch) ((ch) - 'A' + 1)
1582 static const struct shortcmd commoncmds[] = {
1583 { '\e', cmd_normal_mode },
1584 { KEY_NPAGE, cmd_page_down },
1585 { CTRL('F'), cmd_page_down },
1586 { KEY_PPAGE, cmd_page_up },
1587 { CTRL('B'), cmd_page_up },
1588 { CTRL('E'), cmd_scroll_down },
1589 { CTRL('Y'), cmd_scroll_up },
1590 { KEY_HOME, cmd_file_begin },
1591 { KEY_END, cmd_file_end },
1592 { '\t', cmd_switch_column },
1593 { KEY_IC, cmd_insert },
1594 { KEY_DC, cmd_delchar },
1595 { KEY_BACKSPACE, cmd_backspace },
1596 { KEY_LEFT, cmd_go_left },
1597 { KEY_DOWN, cmd_go_down },
1598 { KEY_UP, cmd_go_up },
1599 { KEY_RIGHT, cmd_go_right },
1600 { KEY_F(2), cmd_write },
1601 { KEY_F(10), cmd_exit },
1602 { 0 }
1605 static const struct shortcmd cmds[] = {
1606 { 'G', cmd_file_end },
1607 { '0', cmd_line_begin },
1608 { '^', cmd_line_begin },
1609 { '$', cmd_line_end },
1610 { 'h', cmd_go_left },
1611 { 'j', cmd_go_down },
1612 { 'k', cmd_go_up },
1613 { 'l', cmd_go_right },
1614 { 'W', cmd_write },
1615 { 'Z', cmd_exit },
1616 { 'Q', cmd_quit },
1617 { ':', cmd_exmode },
1618 { '#', cmd_goto },
1619 { '/', cmd_search },
1620 { '?', cmd_search_back },
1621 { 'n', cmd_search_next },
1622 { 'N', cmd_search_prev },
1623 { 'x', cmd_delchar },
1624 { 'X', cmd_delchar_back },
1625 { 'v', cmd_visual },
1626 { 'y', cmd_clip_yank },
1627 { 'd', cmd_clip_delete },
1628 { 'p', cmd_clip_paste_after },
1629 { 'P', cmd_clip_paste },
1630 { 'o', cmd_clip_overwrite },
1631 { 'i', cmd_insert },
1632 { 'e', cmd_replace },
1633 { 'R', cmd_replace },
1634 { 'g', cmd_prefix },
1635 { 'r', cmd_prefix },
1636 { 'm', cmd_prefix },
1637 { '\'', cmd_prefix },
1638 { '\"', cmd_prefix },
1639 { 0 }
1642 static const struct shortcmd cmds_g[] = {
1643 { 'g', cmd_file_begin },
1644 { KEY_ENTER, cmd_goto },
1645 { 0 }
1648 static enum handler_result
1649 handle_shortcmd(struct fileshow_priv *data, int ch,
1650 const struct shortcmd *table)
1652 const struct shortcmd *p;
1654 for (p = table; p->ch; ++p)
1655 if (ch == p->ch)
1656 return do_command(data, p->cmd);
1658 return EVH_PASS;
1661 static enum handler_result
1662 fileshow_normal_in(struct fileshow_priv *data, int ch)
1664 enum handler_result ret;
1666 finish_insert(data);
1667 ret = handle_shortcmd(data, ch, commoncmds);
1668 if (ret == EVH_PASS)
1669 ret = handle_shortcmd(data, ch, cmds);
1670 return ret;
1673 static enum handler_result
1674 fileshow_normal_g_in(struct fileshow_priv *data, int ch)
1676 return handle_shortcmd(data, ch, cmds_g);
1679 static void
1680 hexdigit_in(struct fileshow_priv *data, int ch, int *byte)
1682 static const char hx[] = "0123456789abcdef";
1683 int nibble;
1685 nibble = strchr(hx, ch) - hx;
1686 assert(nibble < 16);
1687 if (data->digitpos == 0) {
1688 *byte = (data->curbyte & 0xf) | (nibble << 4);
1689 } else {
1690 *byte = (data->curbyte & 0xf0) | nibble;
1692 data->digitpos++;
1695 static enum handler_result
1696 fileshow_normal_r_in(struct fileshow_priv *data, int ch)
1698 enum handler_result res = EVH_PASS;
1699 int byte = -1;
1701 if (data->column == FSC_HEX) {
1702 if (isxdigit(ch)) {
1703 hexdigit_in(data, tolower(ch), &byte);
1704 if (data->digitpos > 1) {
1705 data->digitpos = 0;
1706 res = EVH_HOLD;
1707 } else {
1708 /* Keep @xprefix for the second digit. */
1709 res = EVH_CUSTOM_XPREFIX;
1712 } else if (ch == KEY_BACKSPACE && data->digitpos > 0) {
1713 data->digitpos--;
1714 res = EVH_CUSTOM_XPREFIX;
1715 } else if (ch == '\r' || ch == '\033') {
1716 /* Cancel the replace. */
1717 data->digitpos = 0;
1718 res = EVH_HOLD;
1719 } else {
1720 /* Persist until the user types something sensible. */
1721 res = EVH_CUSTOM_XPREFIX;
1724 } else {
1725 byte = ch;
1726 res = EVH_HOLD;
1729 if (byte >= 0) {
1730 if (data->mode == MODE_VISUAL) {
1731 hed_cursor_t base = HED_NULL_CURSOR;
1732 size_t len = visual_extents(data, &base);
1733 file_set_bytes(data->file, &base, byte, len);
1734 file_put_cursor(&base);
1735 } else {
1736 file_set_byte(data->file, &data->cursor, byte);
1738 data->curbyte = byte;
1741 return res;
1744 static enum handler_result
1745 fileshow_normal_m_in(struct fileshow_priv *data, int ch)
1747 int mark = ch2mark(ch);
1748 if (mark >= 0)
1749 file_dup2_cursor(&data->cursor, file_mark(data->file, mark));
1750 else
1751 errmsg("Invalid mark");
1752 return EVH_SILENTHOLD;
1755 static enum handler_result
1756 fileshow_normal_Qq_in(struct fileshow_priv *data, int ch)
1758 int mark = ch2mark(ch);
1759 if (mark >= 0) {
1760 if (file_mark_is_valid(data->file, mark))
1761 set_cursor(data, file_mark(data->file, mark)->pos);
1762 else
1763 errmsg("No such mark");
1764 } else {
1765 errmsg("Invalid mark");
1767 return EVH_HOLD;
1770 static enum handler_result
1771 fileshow_normal_QQ_in(struct fileshow_priv *data, int ch)
1773 char reg = ch2reg(ch);
1774 if (reg >= 0) {
1775 data->reg = reg;
1776 } else {
1777 errmsg("Invalid register");
1779 return EVH_SILENTHOLD;
1782 static enum handler_result
1783 fileshow_byte_in(struct fileshow_priv *data, int ch)
1785 enum handler_result ret = EVH_PASS;
1786 bool insert;
1787 int cursmove;
1788 int byte = -1;
1790 ret = handle_shortcmd(data, ch, commoncmds);
1791 if (ret != EVH_PASS)
1792 return ret;
1794 insert = (data->mode == MODE_INSERT && data->cursor.pos < OFF_MAX);
1796 if (insert && !is_a_cursor(&data->insert)) {
1797 file_insert_begin(data->file, &data->cursor, &data->insert);
1798 data->digitpos = 0;
1799 if (data->offset.pos == data->cursor.pos) {
1800 file_dup2_cursor(&data->insert, &data->offset);
1801 update_idmark(data);
1805 cursmove = 0;
1806 if (data->column == FSC_HEX) {
1807 if (isxdigit(ch)) {
1808 if (insert && data->digitpos == 0)
1809 data->curbyte = 0;
1810 hexdigit_in(data, tolower(ch), &byte);
1811 if (data->digitpos > 1) {
1812 data->digitpos = 0;
1813 cursmove = 1;
1815 ret = EVH_HOLD;
1817 } else {
1818 cursmove = 1;
1819 byte = ch;
1820 ret = EVH_HOLD;
1823 if (byte < 0)
1824 return ret;
1826 if (insert) {
1827 if (data->column != FSC_HEX) {
1828 file_insert_byte(data->file, &data->insert, byte);
1829 } else if (data->digitpos > 0) {
1830 file_dup2_cursor(&data->insert, &data->cursor);
1831 file_insert_byte(data->file, &data->insert, byte);
1832 } else {
1833 file_set_byte(data->file, &data->cursor, byte);
1834 file_move_relative(&data->cursor, 1);
1836 data->curbyte = byte;
1838 if (data->cursor.pos - data->ssize >= data->offset.pos)
1839 slide_viewport(data, data->width);
1840 else
1841 assert(data->cursor.pos >= data->offset.pos);
1842 } else {
1843 file_set_byte(data->file, &data->cursor, byte);
1844 data->curbyte = byte;
1846 jump_relative(data, cursmove);
1849 return ret;
1852 static enum handler_result
1853 fileshow_handler(struct ui_component *comp, const struct ui_event *event)
1855 struct fileshow_priv *data = comp->data;
1856 enum handler_result res = EVH_PASS;
1858 switch (event->type) {
1859 case EV_REDRAW:
1860 fileshow_redraw(comp);
1861 break;
1862 case EV_KEYBOARD:
1864 int ch = event->v.keyboard.ch;
1866 if (data->mode == MODE_NORMAL || data->mode == MODE_VISUAL) {
1867 if (xprefix == 'g') {
1868 res = fileshow_normal_g_in(data, ch);
1869 } else if (xprefix == 'r') {
1870 res = fileshow_normal_r_in(data, ch);
1871 } else if (xprefix == 'm') {
1872 res = fileshow_normal_m_in(data, ch);
1873 } else if (xprefix == '\'') {
1874 res = fileshow_normal_Qq_in(data, ch);
1875 } else if (xprefix == '"') {
1876 res = fileshow_normal_QQ_in(data, ch);
1877 } else {
1878 res = fileshow_normal_in(data, ch);
1880 } else
1881 res = fileshow_byte_in(data, ch);
1883 if (res != EVH_PASS && res != EVH_SILENTHOLD)
1884 fileshow_redraw(comp);
1885 break;
1887 case EV_DONE:
1888 fileshow_done(comp);
1889 break;
1890 default:
1891 fprintf(stderr, "Bug: fileshow_handler E.T. %d\n", event->type);
1892 break;
1895 return res;