Mark bad blocks with HED_BLOCK_BAD
[hed.git] / src / ui / fileshow.c
blob0ed517dafeafb9a613a9bbea208f3f28e2dc6083
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 /* Feature macros needed for:
33 * - stpcpy
35 #define _GNU_SOURCE
37 #include <config.h>
39 #include <ctype.h>
40 #include <curses.h>
41 #include <errno.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <sys/wait.h>
47 #include <hed.h>
49 #include <config/config.h>
50 #include <libhed/expr.h>
51 #include <libhed/file.h>
52 #include <term/term.h>
53 #include <ui/core.h>
54 #include <ui/error.h>
55 #include <ui/fileshow.h>
56 #include <ui/inputline.h>
57 #include <util/lists.h>
58 #include <main.h>
60 /* For now, we re-request the file fragment each time we redraw the thing.
61 * This might not be feasible in the future. */
63 /* Number of bytes to fetch at cursor position.
64 * Currently, at maximum 4 bytes are needed (for the s32/u32 display).
65 * If you need more in print_fileview_status, feel free to increase
66 * this constant and use the extra bytes.
68 #define CURSOR_LENGTH 4
70 /* TODO: Make the multiclipboard smarter - copy-on-write. */
71 /* TODO: Registers should work as in vim, esp. the unnamed (") register */
72 struct clipboard_register {
73 unsigned char *data;
74 off_t len;
76 static struct clipboard_register clipboard[1 + 'z' - 'a' + 1 + 'Z' - 'A' + 1 + '9' - '0' + 1];
78 static inline char
79 ch2mark(char ch)
81 return ('A' <= ch && ch <= 'Z') ? ch - 'A' :
82 ('a' <= ch && ch <= 'z') ? 26 + ch - 'a' :
83 -1;
86 static inline char
87 ch2reg(char ch)
89 return ('0' <= ch && ch <= '9') ? 52 + ch - '0' :
90 (ch == '"') ? 62 :
91 ch2mark(ch);
94 static bool subst_all;
97 struct fileshow_priv {
98 struct hed_file *file;
99 hed_cursor_t offset; /* Beginning of screen (file offset) */
101 int width, height; /* Width and height of the viewport */
102 ssize_t ssize; /* Size (in bytes) of the viewport */
104 hed_cursor_t cursor; /* Cursor position (file offset) */
105 hed_cursor_t visual; /* Visual mark */
106 int digitpos; /* Selected digit in the hexadecimal column. */
107 unsigned char curbyte; /* The value of the current byte under the cursor. */
109 hed_cursor_t insert;
111 char idmark; /* insertion/deletion mark at screen start */
113 struct hed_expr *last_search;
114 enum { SDIR_FORWARD = 1, SDIR_BACK = -1 } last_search_dir;
116 enum { FSC_HEX, FSC_ASC } column;
117 enum ui_mode mode;
119 int reg;
121 int le; /* non-zero if little endian view */
124 static const char idmarks[] = {
125 '>', ' ', '<', /* insert start, normal, insert end */
126 ')', '|', '(', /* dtto after erase */
129 #define IDMARK_ERASE_IDX 3
131 static enum handler_result fileshow_handler(struct ui_component *, const struct ui_event *);
132 static long eval_reg_cb(void *hookdata, char reg, hed_off_t ofs,
133 unsigned char *scramble, size_t len);
134 static long eval_mark_cb(void *hookdata, char mark,
135 unsigned char *scramble, size_t len);
137 enum ui_mode opt_defmode = -1;
139 static void
140 update_idmark(struct fileshow_priv *data)
142 int idx = 1;
144 /* marks can only happen at zero offset */
145 if (!data->offset.off) {
146 struct hed_tree *tree = hed_file_blocks(data->file);
148 idx = !hed_block_is_inserted(data->offset.block);
149 if (hed_block_is_after_erase(tree, data->offset.block))
150 idx += IDMARK_ERASE_IDX;
151 if (hed_block_is_after_insert(tree, data->offset.block))
152 idx = 1;
154 data->idmark = idmarks[idx];
157 static unsigned
158 pos_column(struct fileshow_priv *data, hed_uoff_t pos)
160 return pos % data->width;
163 static void
164 set_viewport(struct fileshow_priv *data, off_t pos)
166 hed_get_cursor(data->file, pos, &data->offset);
167 update_idmark(data);
170 /* Update screen offset to make cursor fit into the current viewport */
171 static void
172 fixup_viewport(struct fileshow_priv *data)
174 off_t curline = data->cursor.pos - pos_column(data, data->cursor.pos);
176 if (curline < data->offset.pos)
177 set_viewport(data, curline);
178 else if (curline - data->ssize >= data->offset.pos)
179 set_viewport(data, curline - data->ssize + data->width);
182 static void
183 set_cursor(struct fileshow_priv *data, off_t pos)
185 hed_get_cursor(data->file, pos, &data->cursor);
186 fixup_viewport(data);
189 /* Update cursor position to fit into the current viewport */
190 static void
191 fixup_cursor(struct fileshow_priv *data)
193 off_t curline = data->cursor.pos - pos_column(data, data->cursor.pos);
195 if (curline < data->offset.pos)
196 hed_move_relative(&data->cursor,
197 data->offset.pos - curline);
198 else if (curline >= data->offset.pos + data->ssize)
199 hed_move_relative(&data->cursor,
200 data->offset.pos + data->ssize -
201 data->width - curline);
204 static void
205 slide_viewport(struct fileshow_priv *data, off_t num)
207 hed_move_relative(&data->offset, num);
208 update_idmark(data);
209 fixup_cursor(data);
212 /* Initialize the component. */
213 void
214 fileshow_init(struct hed_file *file)
216 struct fileshow_priv *data;
217 struct ui_component *fileshow;
218 int width;
220 data = calloc(1, sizeof(struct fileshow_priv));
221 if (!data) goto nomem;
222 data->file = file;
223 data->reg = ch2reg('"');
224 data->le = arch_little_endian();
226 if ((int) opt_defmode >= 0) {
227 data->mode = opt_defmode;
228 } else {
229 char *defmode = get_opt_str("default_mode", "normal");
230 if (!strcmp(defmode, "normal")) {
231 data->mode = MODE_NORMAL;
232 } else if (!strcmp(defmode, "insert")) {
233 data->mode = MODE_INSERT;
234 } else if (!strcmp(defmode, "replace")) {
235 data->mode = MODE_REPLACE;
236 } else {
237 data->mode = MODE_NORMAL;
241 fileshow = ui_register(fileshow_handler, data);
242 if (!fileshow) {
243 free(data);
244 nomem:
245 sched_exit(EX_OSERR);
246 return;
249 /* TODO: Better height calculation. */
250 data->height = term_get_max_y() - 2;
251 width = (term_get_max_x() - /* ofs */ 8 - /* spaces */ 4);
252 width /= 4;
253 if (!opt_spreadview) {
254 width = 1 << bitsize(width);
255 width = get_opt_int("bytes_per_line", width);
257 data->width = width;
258 data->ssize = data->height * data->width;
260 hed_get_cursor(data->file, 0, &data->cursor);
261 hed_dup_cursor(&data->cursor, &data->offset);
262 data->idmark = ' ';
265 static void
266 fileshow_done(struct ui_component *comp)
268 free(comp->data);
269 ui_unregister(comp);
272 /* Check whether @pos is inside the visual block */
273 static int
274 is_inside_visual(struct fileshow_priv *data, off_t pos)
276 return (data->cursor.pos <= pos && pos <= data->visual.pos) ||
277 (data->visual.pos <= pos && pos <= data->cursor.pos);
280 /* Returns ZERO ON ERROR. */
281 static size_t
282 visual_extents(struct fileshow_priv *data, hed_cursor_t *base)
284 hed_cursor_t *basesrc;
285 size_t ret;
286 if (data->visual.pos < data->cursor.pos) {
287 ret = data->cursor.pos - data->visual.pos + 1;
288 basesrc = &data->visual;
289 } else {
290 ret = data->visual.pos - data->cursor.pos + 1;
291 basesrc = &data->cursor;
293 hed_dup2_cursor(basesrc, base);
294 return ret;
297 #define BYTES_PER_LONG (sizeof(unsigned long))
299 #define DIGITS_MAX(num) ((num + 9)/10)
301 /* The decadic logarithm of 2^8 is approx. 2.408239965311849584,
302 * so 3 digits must be always sufficient to represent the largest
303 * value that can be held in one byte. */
304 #define DIGITS_PER_BYTE 3
306 static int
307 print_status_num(unsigned char *cursdata, int nbytes,
308 int little_endian, int x, int y, int width)
310 off_t num;
311 long flags;
313 flags = little_endian ? HED_AEF_FORCELE : HED_AEF_FORCEBE;
315 if (nbytes < 0) {
316 nbytes = -nbytes;
317 flags |= HED_AEF_SIGNED;
320 assert(x && cursdata);
321 assert(nbytes * 8 <= _FILE_OFFSET_BITS);
323 num = hed_bytestr2off(cursdata, nbytes, flags);
325 return term_printf(x, y, COLOR_STATUS, flags & HED_AEF_SIGNED
326 ? "s%d:%*lld "
327 : "u%d:%*llu ",
328 nbytes * 8, width, (long long) num);
331 static void
332 print_fileview_status(struct fileshow_priv *data,
333 unsigned char *cursdata, int ofslen)
335 static char modechar[] = { 'N', 'R', 'I', 'V' };
336 int pos = 0;
337 const int y = data->height;
339 /* First, fill the status line with the status color */
340 term_set_bg_color(COLOR_STATUS);
341 term_clear_line(data->height);
343 /* We print the filename first so that it gets overwritten by
344 * the "more important" stuff. The most important part of the
345 * filename tends to be at the end anyway. */
346 term_print_string(term_get_max_x() - 1 - strlen(data->file->name),
347 y, COLOR_STATUS, data->file->name);
349 pos += term_printf(0, y, COLOR_STATUS, "%0*llx: %03d ", ofslen,
350 (unsigned long long) data->cursor.pos, *cursdata);
352 term_print_string(pos, y, COLOR_STATUS, data->le ? "LE: " : "BE: ");
353 pos += 4;
355 pos += print_status_num(cursdata, 2, data->le, pos, y, 6);
356 pos += print_status_num(cursdata, -2, data->le, pos, y, 6);
357 pos += print_status_num(cursdata, 4, data->le, pos, y, 11);
358 pos += print_status_num(cursdata, -4, data->le, pos, y, 11);
360 term_print_char(pos, y, COLOR_MODE,
361 hed_file_is_modified(data->file) ? '*' : ' ');
362 term_print_char(pos + 1, y, COLOR_MODE, modechar[data->mode]);
366 static int
367 num_hex_width(hed_uoff_t num)
369 /* Offset of the leftmost non-zero bit. */
370 int width = 0;
371 int i;
373 for (i = sizeof(hed_uoff_t) * 8 / 2; i > 0; i /= 2) {
374 if (num & (~(hed_uoff_t)0 << i)) {
375 num >>= i; width += i;
378 /* Round the width up to the hexadecimal places. */
379 return width / 4 + 1;
382 static void
383 fileshow_redraw(struct ui_component *comp)
385 struct fileshow_priv *data = comp->data;
386 hed_cursor_t pos;
387 char idmark; /* insertion/deletion marks */
389 hed_uoff_t maxofs;
390 int ofslen;
391 int xindent, xascpos;
392 int row;
394 unsigned char *p;
395 unsigned char cursdata[CURSOR_LENGTH];
397 if (!hed_prepare_read(data->file, &data->offset, 1)) {
398 /* This is a fatal error (e.g. no memory) */
399 errmsg("Cannot display file content");
400 return;
403 /* Offset width is chosen to accomodate the whole file in its current
404 * size as well as the current screen. */
405 maxofs = data->offset.pos + data->ssize - 1;
406 if (maxofs < hed_file_size(data->file))
407 maxofs = hed_file_size(data->file);
408 ofslen = num_hex_width(maxofs);
409 /* Always print at least 4 digits. */
410 if (ofslen < 4)
411 ofslen = 4;
412 xindent = ofslen + 2;
413 xascpos = xindent + data->width * 3 + 1;
415 hed_dup_cursor(&data->offset, &pos);
416 idmark = data->idmark;
417 assert(pos.pos >= 0);
419 term_set_bg_color(COLOR_NEUTRAL);
420 erase();
422 p = hed_cursor_data(&pos);
424 for (row = 0; row < data->height; ++row) {
425 int col;
426 term_color oldv = 0;
428 term_printf(0, row, COLOR_OFFSET, "%0*llx", ofslen, pos.pos);
430 for (col = 0; col < data->width; ++col) {
431 char hex[3];
432 unsigned char asc;
433 term_color color, v;
435 v = (data->mode == MODE_VISUAL &&
436 is_inside_visual(data, pos.pos))
437 ? COLOR_VISUAL
438 : 0;
440 color = COLOR_DATA_BASE;
441 if (pos.pos >= hed_file_size(data->file))
442 color |= COLOR_DATA_OUTSIDE;
444 term_color markcolor = (idmark == ' ')
445 ? (hed_block_is_dirty(pos.block)
446 ? COLOR_DIRTY
447 : color)
448 : COLOR_MARK;
449 term_print_char(xindent + col * 3 - 1, row,
450 markcolor | v | oldv, idmark);
452 if (pos.pos == data->cursor.pos) {
453 cursor_y = row;
454 cursor_x = data->column == FSC_HEX
455 ? xindent + col * 3 + data->digitpos
456 : xascpos + col;
457 color |= COLOR_DATA_CURSOR;
458 } else if (hed_block_is_dirty(pos.block))
459 color = COLOR_DIRTY;
460 if (hed_block_is_bad(pos.block))
461 color = COLOR_ERROR;
462 color |= v;
464 asc = p ? *p++ : 0x00;
465 sprintf(hex,
466 hed_block_is_bad(pos.block) ? "XX" : "%02x",
467 asc);
468 asc = isprint(asc) ? asc : asc == '\0' ? '.' : ':';
470 term_print_string(xindent + col * 3, row,
471 color, hex);
472 if (color >= COLOR_DATA_BASE)
473 color |= COLOR_DATA_ASCII;
474 term_print_char(xascpos + col, row,
475 color, (hed_block_is_bad(pos.block)
476 ? '?' : asc));
478 if (pos.pos >= OFF_MAX) {
479 term_print_char(xindent + col * 3 + 2, row,
480 COLOR_MARK | v, '!');
481 goto done;
484 int idx = !!hed_block_is_inserted(pos.block);
485 hed_move_relative(&pos, 1);
486 if (!pos.off) {
487 idx -= !!hed_block_is_inserted(pos.block);
488 if (hed_block_is_after_erase(
489 hed_file_blocks(data->file),
490 pos.block))
491 idx += IDMARK_ERASE_IDX;
492 idmark = idmarks[idx + 1];
494 if (!hed_prepare_read(data->file, &pos, 1)) {
495 /* This is a fatal error */
496 errmsg("Cannot display file content");
497 return;
500 p = hed_cursor_data(&pos);
501 } else
502 idmark = ' ';
504 oldv = v;
507 if (idmark == '<' || idmark == '(') {
508 term_print_char(xascpos - 2, row,
509 COLOR_MARK | oldv, idmark);
510 idmark = ' ';
511 } else if (oldv)
512 term_print_char(xascpos - 2, row,
513 COLOR_NEUTRAL | COLOR_VISUAL, ' ');
515 done:
516 hed_put_cursor(&pos);
518 assert(data->cursor.pos >= 0);
520 memset(cursdata, 0, sizeof(cursdata));
521 hed_file_cpin(data->file, cursdata, sizeof(cursdata), &data->cursor);
522 data->curbyte = cursdata[0];
524 print_fileview_status(data, cursdata, ofslen);
525 term_unset_bg_color();
528 /* Jump to a position given by offset expression. */
529 static void
530 jump_offset(struct inputline_data *inpline, void *hookdata)
532 struct fileshow_priv *data = hookdata;
533 struct hed_expr *expr = hed_expr_compile(inpline->buf);
534 unsigned char *buf;
535 off_t pos;
537 if (!expr) {
538 errmsg("Invalid expression");
539 return;
541 buf = hed_expr_eval(expr, eval_reg_cb, eval_mark_cb, data);
542 if (!buf) {
543 errmsg("Cannot evaluate expression");
544 hed_expr_free(expr);
545 return;
547 pos = hed_bytestr2off(buf, hed_expr_len(expr), hed_expr_flags(expr));
548 pos &= OFF_MAX;
549 hed_expr_free(expr);
551 set_cursor(data, pos);
554 static int
555 jump_relative(struct fileshow_priv *data, int num)
557 /* Zero-length jump usually means an invalid jump out of bounds */
558 if (hed_move_relative(&data->cursor, num) == 0)
559 return -1;
561 data->digitpos = 0;
562 fixup_viewport(data);
563 return 0;
566 static void
567 finish_insert(struct fileshow_priv *data)
569 if (hed_is_a_cursor(&data->insert)) {
570 hed_file_insert_end(data->file, &data->insert);
571 if (data->column == FSC_HEX && data->digitpos)
572 jump_relative(data, 1);
576 static void
577 erase_at_cursor(struct fileshow_priv *data, hed_uoff_t len)
579 hed_file_erase_block(data->file, &data->cursor, len);
580 if (!hed_is_a_cursor(&data->offset)) {
581 hed_dup2_cursor(&data->cursor, &data->offset);
582 hed_move_relative(&data->offset,
583 -pos_column(data, data->offset.pos));
584 update_idmark(data);
588 static off_t
589 do_search(struct fileshow_priv *data)
591 hed_cursor_t off;
592 int res;
594 hed_dup_cursor(&data->cursor, &off);
595 hed_move_relative(&off, data->last_search_dir);
596 res = hed_file_find_expr(data->file, &off, data->last_search_dir,
597 data->last_search, eval_reg_cb, data);
598 if (!res)
599 set_cursor(data, off.pos);
600 else if (!subst_all)
601 errmsg("No match found");
603 hed_put_cursor(&off);
604 return off.pos;
607 /* Search for the given string. */
608 static void
609 expr_search(struct inputline_data *inpline, void *hookdata)
611 struct fileshow_priv *data = hookdata;
613 if (data->last_search)
614 hed_expr_free(data->last_search);
615 data->last_search = hed_expr_compile(inpline->buf);
616 if (!data->last_search) {
617 errmsg("Invalid expression");
618 return;
621 do_search(data);
624 /* Search and substitute the given string. */
625 static void
626 expr_subst(struct inputline_data *inpline, void *hookdata)
628 struct fileshow_priv *data = hookdata;
629 struct hed_expr *subst;
630 unsigned char *buf;
632 subst = hed_expr_compile(inpline->buf);
633 if (!subst) {
634 errmsg("Invalid expression");
635 return;
638 data->last_search_dir = SDIR_FORWARD;
639 while (do_search(data) != HED_FINDOFF_NO_MATCH) {
640 buf = hed_expr_eval(subst, eval_reg_cb, eval_mark_cb, data);
641 if (!buf) {
642 errmsg("Cannot evaluate expression");
643 break;
645 /* Cursor at the match */
646 erase_at_cursor(data, hed_expr_len(data->last_search));
647 hed_file_insert_once(data->file, &data->cursor,
648 buf, hed_expr_len(subst));
649 if (!subst_all)
650 break;
652 hed_expr_free(subst);
655 static void
656 expr_subst1(struct inputline_data *inpline, void *hookdata)
658 struct fileshow_priv *data = hookdata;
660 if (data->last_search)
661 hed_expr_free(data->last_search);
662 data->last_search = hed_expr_compile(inpline->buf);
663 if (!data->last_search) {
664 errmsg("Invalid expression");
665 return;
667 inputline_init("s/.../", expr_subst, data);
670 static void
671 clip_yank(struct fileshow_priv *data)
673 hed_cursor_t base = HED_NULL_CURSOR;
674 size_t len = visual_extents(data, &base);
675 void *buf = realloc(clipboard[data->reg].data, len);
677 if (buf) {
678 clipboard[data->reg].data = buf;
679 len -= hed_file_cpin(data->file, buf, len, &base);
680 clipboard[data->reg].len = len;
682 hed_put_cursor(&base);
685 /* Those functions are totally horrible kludges. There should be specialized
686 * file methods for those operations. */
688 static void
689 clip_delete(struct fileshow_priv *data)
691 size_t len;
693 clip_yank(data);
694 len = visual_extents(data, &data->cursor);
695 erase_at_cursor(data, len);
698 static void
699 clip_put(struct fileshow_priv *data)
701 if (!clipboard[data->reg].data) {
702 errmsg("Register empty");
703 return;
705 hed_file_insert_once(data->file, &data->cursor,
706 clipboard[data->reg].data,
707 clipboard[data->reg].len);
708 if (data->offset.pos == data->cursor.pos)
709 hed_move_relative(&data->offset,
710 -clipboard[data->reg].len);
713 static void
714 clip_overwrite(struct fileshow_priv *data)
716 hed_cursor_t start = HED_NULL_CURSOR;
717 off_t i;
718 ssize_t len;
720 if (!clipboard[data->reg].data) {
721 errmsg("Register empty");
722 return;
725 if (data->mode == MODE_VISUAL) {
726 /* spill */
727 len = visual_extents(data, &start);
728 } else {
729 /* splat */
730 hed_dup_cursor(&data->cursor, &start);
731 len = clipboard[data->reg].len;
734 i = min(len, clipboard[data->reg].len);
735 hed_file_set_block(data->file, &start, clipboard[data->reg].data, i);
736 hed_file_set_bytes(data->file, &start, 0, len - i);
737 hed_put_cursor(&start);
740 static void
741 clip_swap(struct fileshow_priv *data)
743 struct clipboard_register orig, new;
745 new = clipboard[data->reg];
746 clipboard[data->reg].data = NULL;
747 clip_delete(data);
748 orig = clipboard[data->reg];
749 clipboard[data->reg] = new;
750 clip_put(data);
751 clipboard[data->reg] = orig;
755 static void
756 set_width(struct inputline_data *inpline, void *hookdata)
758 struct fileshow_priv *data = hookdata;
759 char *l;
760 int w;
761 w = strtol(inpline->buf, &l, 0);
762 if (*l) {
763 errmsg("Invalid width");
764 return;
766 data->width = w;
770 static void
771 read_region(struct inputline_data *inpline, void *hookdata)
773 struct fileshow_priv *data = hookdata;
774 char *fname = inpline->buf;
775 int flen = inpline->buf_len;
776 FILE *f;
777 int ch;
779 /* Trim leading/trailing whitespaces */
780 while (isspace(*fname))
781 fname++, flen--;
782 while (isspace(fname[flen - 1]) && flen > 0)
783 flen--;
784 if (!*fname)
785 return;
786 f = fopen(fname, "r");
787 if (!f) {
788 errmsg(strerror(errno));
789 return;
792 if (data->mode == MODE_VISUAL) {
793 size_t len = visual_extents(data, &data->cursor);
794 erase_at_cursor(data, len);
797 /* Ugh. :-) */
798 hed_file_insert_begin(data->file, &data->cursor, &data->insert);
799 while ((ch = fgetc(f)) != EOF)
800 hed_file_insert_byte(data->file, &data->insert, ch);
801 finish_insert(data);
802 fclose(f);
805 static int
806 write_to_file(struct hed_file *in, hed_cursor_t *pos, hed_uoff_t len,
807 FILE *out)
809 while (len) {
810 size_t blen, written;
811 void *p;
813 if (! (blen = hed_prepare_read(in, pos, len)) )
814 return -1;
816 if ( (p = hed_cursor_data(pos)) )
817 written = fwrite(p, 1, blen, out);
818 else for (written = 0; written < blen; ++written)
819 if (fputc(0, out) < 0)
820 break;
822 hed_move_relative(pos, written);
823 if (written < blen)
824 return -1;
825 len -= blen;
827 return 0;
830 static void
831 write_region(struct inputline_data *inpline, void *hookdata)
833 struct fileshow_priv *data = hookdata;
834 hed_cursor_t base = HED_NULL_CURSOR;
835 size_t len;
836 char *fname = inpline->buf;
837 int flen = inpline->buf_len;
838 FILE *f;
840 /* Trim leading/trailing whitespaces */
841 while (isspace(*fname))
842 fname++, flen--;
843 while (isspace(fname[flen - 1]) && flen > 0)
844 flen--;
845 if (!*fname)
846 return;
848 f = fopen(fname, "w");
849 if (!f) {
850 errmsg(strerror(errno));
851 return;
853 len = visual_extents(data, &base);
854 if (write_to_file(data->file, &base, len, f))
855 errmsg(strerror(errno));
856 hed_put_cursor(&base);
857 fclose(f);
860 /* Arbitrarily chosen... */
861 #define CHUNK_SIZE 1024
863 static int
864 filter_select(struct fileshow_priv *data, hed_cursor_t *base,
865 ssize_t len, int fd_in, int fd_out)
867 fd_set fdr, fdw;
868 int ret;
870 hed_file_insert_begin(data->file, &data->cursor, &data->insert);
871 while (fd_out || fd_in) {
872 FD_ZERO(&fdr);
873 FD_ZERO(&fdw);
874 if (fd_in) FD_SET(fd_in, &fdr);
875 if (fd_out) FD_SET(fd_out, &fdw);
877 ret = select(fd_in+fd_out+1, &fdr, &fdw, NULL, NULL);
879 if (ret <= 0)
880 continue;
882 if (FD_ISSET(fd_in, &fdr)) {
883 unsigned char buf[CHUNK_SIZE];
884 ret = read(fd_in, buf, CHUNK_SIZE);
885 if (ret < 0)
886 goto err;
887 if (ret == 0) {
888 close(fd_in);
889 fd_in = 0;
891 hed_file_insert_block(data->file, &data->insert,
892 buf, ret);
893 data->cursor.pos += ret;
896 if (FD_ISSET(fd_out, &fdw)) {
897 size_t blen = min(len, CHUNK_SIZE);
898 unsigned char *buf = malloc(blen);
899 blen -= hed_file_cpin(data->file, buf, blen, base);
900 if (blen < CHUNK_SIZE) {
901 len = 0;
902 } else {
903 len -= blen;
905 ret = write(fd_out, buf, blen);
906 free(buf);
907 if (ret <= 0)
908 goto err;
909 if (ret < blen)
910 len += blen - ret;
911 hed_move_relative(base, ret - blen);
913 if (!len) {
914 close(fd_out);
915 fd_out = 0;
919 finish_insert(data);
920 return 0;
921 err:
922 finish_insert(data);
923 if (fd_out)
924 close(fd_out);
925 if (fd_in)
926 close(fd_in);
927 return -1;
930 static void
931 pipe_region(struct inputline_data *inpline, void *hookdata)
933 struct fileshow_priv *data = hookdata;
934 hed_cursor_t base = HED_NULL_CURSOR;
935 size_t len = visual_extents(data, &base);
936 char *argv[4] = { "/bin/sh", "-c", inpline->buf, NULL };
937 int p_rd[2], p_wr[2];
938 pid_t pid;
940 hed_put_cursor(&data->visual);
941 data->mode = MODE_NORMAL;
943 pipe(p_rd);
944 pipe(p_wr);
946 pid = fork();
947 if (pid == 0) {
948 close(p_rd[1]);
949 close(p_wr[0]);
951 dup2(p_rd[0], 0);
952 dup2(p_wr[1], 1);
954 exit(execvp(argv[0], argv));
956 } else if (pid > 0) {
957 int status, ret;
959 close(p_rd[0]);
960 close(p_wr[1]);
962 set_cursor(data, base.pos + len);
963 if (filter_select(data, &base, len, p_wr[0], p_rd[1]) < 0) {
964 errmsg("Filter failed");
965 hed_put_cursor(&base);
966 return;
968 ret = waitpid(pid, &status, 0);
969 if (ret != pid) {
970 errmsg("Funny zombie");
971 hed_put_cursor(&base);
972 return;
974 if (WIFEXITED(status)) {
975 if (WEXITSTATUS(status))
976 errmsg("Filter returned error");
977 set_cursor(data, base.pos);
978 erase_at_cursor(data, len);
979 hed_put_cursor(&base);
980 return;
982 errmsg("Curious error");
983 hed_put_cursor(&base);
984 return;
986 } else {
987 errmsg("Fork failed");
988 hed_put_cursor(&base);
989 return;
991 hed_put_cursor(&base);
994 static hed_cursor_t xre;
996 static long
997 eval_mark_cb(void *hookdata, char mark,
998 unsigned char *scramble, size_t len)
1000 struct fileshow_priv *data = hookdata;
1001 int m = ch2mark(mark);
1002 if (m >= 0) {
1003 if (hed_file_has_mark(data->file, m)) {
1004 hed_off2bytestr(scramble, len,
1005 hed_file_mark(data->file, m)->pos);
1006 return 0;
1008 } else {
1009 if (mark == '$') {
1010 hed_off2bytestr(scramble, len, data->cursor.pos);
1011 return 0;
1014 memset(scramble, 0, len);
1015 return 0;
1019 static long
1020 eval_reg_cb(void *hookdata, char reg, hed_off_t ofs,
1021 unsigned char *scramble, size_t len)
1023 struct fileshow_priv *data = hookdata;
1024 unsigned char *buf;
1025 size_t blen;
1026 hed_cursor_t blkpos = HED_NULL_CURSOR;
1027 off_t skip;
1028 int r;
1029 long ret;
1031 switch (reg) {
1032 case ',':
1033 hed_dup_cursor(&data->cursor, &blkpos);
1034 break;
1035 case '_':
1036 hed_get_cursor(data->file, 0, &blkpos);
1037 break;
1038 case '.':
1039 if (!hed_is_a_cursor(&xre))
1040 goto out_zero;
1041 hed_dup_cursor(&xre, &blkpos);
1042 break;
1043 default:
1044 r = ch2reg(reg);
1045 if (r < 0)
1046 goto out_zero;
1047 buf = clipboard[r].data; blen = clipboard[r].len;
1048 if (ofs < 0) {
1049 if (-ofs >= len)
1050 goto out_zero;
1051 memset(scramble, 0, -ofs);
1052 scramble -= ofs;
1053 len += ofs;
1054 } else {
1055 if (ofs >= blen)
1056 goto out_zero;
1057 buf += ofs;
1058 blen -= ofs;
1060 if (blen > len)
1061 blen = len;
1062 memcpy(scramble, buf, blen);
1063 if (blen < len)
1064 memset(scramble + blen, 0, len - blen);
1065 return 0;
1068 skip = hed_move_relative(&blkpos, ofs) - ofs;
1069 if (skip) {
1070 if (ofs >= 0 || skip >= len)
1071 goto out_zero;
1072 memset(scramble, 0, skip);
1073 scramble += skip;
1074 len -= skip;
1076 ret = hed_file_cpin(data->file, scramble, len, &blkpos)
1077 ? HED_AEF_ERROR : 0;
1078 hed_put_cursor(&blkpos);
1079 return ret;
1081 out_zero:
1082 memset(scramble, 0, len);
1083 return 0;
1087 static void
1088 ieval_expr(struct inputline_data *inpline, void *hookdata)
1090 struct fileshow_priv *data = hookdata;
1091 struct hed_expr *expr = hed_expr_compile(inpline->buf);
1092 unsigned char *buf;
1094 if (!expr) {
1095 errmsg("Invalid expression");
1096 return;
1098 buf = hed_expr_eval(expr, eval_reg_cb, eval_mark_cb, data);
1099 if (!buf) {
1100 errmsg("Cannot evaluate expression");
1101 hed_expr_free(expr);
1102 return;
1104 hed_file_insert_once(data->file, &data->cursor, buf, hed_expr_len(expr));
1105 hed_expr_free(expr);
1108 static void
1109 peval_expr(struct inputline_data *inpline, void *hookdata)
1111 struct fileshow_priv *data = hookdata;
1112 struct hed_expr *expr = hed_expr_compile(inpline->buf);
1113 static char *resbuf;
1114 char *p;
1115 size_t i, len;
1116 unsigned char *buf;
1118 if (!expr) {
1119 errmsg("Invalid expression");
1120 return;
1122 buf = hed_expr_eval(expr, eval_reg_cb, eval_mark_cb, data);
1123 if (!buf) {
1124 errmsg("Cannot evaluate expression");
1125 hed_expr_free(expr);
1126 return;
1128 len = hed_expr_len(expr);
1129 p = realloc(resbuf, sizeof("Result: -") + 3 * len);
1130 if (!p) {
1131 errmsg("Cannot allocate buffer");
1132 return;
1135 resbuf = p;
1136 p = stpcpy(resbuf, "Result:");
1137 if (hed_expr_flags(expr) & HED_AEF_NEGATIVE)
1138 p = stpcpy(p, " -");
1139 for (i = 0; i < len; ++i)
1140 p += sprintf(p, " %02x", buf[i]);
1141 hed_expr_free(expr);
1142 errmsg(resbuf);
1146 static void
1147 reval_expr(struct inputline_data *inpline, void *hookdata)
1149 struct fileshow_priv *data = hookdata;
1150 struct hed_expr *expr = hed_expr_compile(inpline->buf);
1151 int elen;
1152 unsigned char *ebuf;
1153 size_t i, len;
1155 if (!expr) {
1156 errmsg("Invalid expression");
1157 goto out;
1159 elen = hed_expr_len(expr);
1160 len = visual_extents(data, &xre);
1161 for (i = 0; i < len; i += elen) {
1162 size_t rlen;
1163 ebuf = hed_expr_eval(expr, eval_reg_cb, eval_mark_cb, data);
1164 if (!ebuf) {
1165 errmsg("Cannot evaluate expression");
1166 break;
1168 rlen = elen;
1169 if (rlen > len - i)
1170 rlen = len - i;
1171 hed_file_set_block(data->file, &xre, ebuf, rlen);
1173 hed_expr_free(expr);
1174 hed_put_cursor(&xre);
1175 xre.block = NULL;
1176 out:
1177 if (data->mode != MODE_VISUAL)
1178 hed_put_cursor(&data->visual);
1181 static void exmode(struct inputline_data *inpline, void *hookdata);
1183 /* Evaluate an exmode command. */
1184 static void
1185 exmode_cmd_write(struct fileshow_priv *data)
1187 if (data->mode == MODE_VISUAL) {
1188 /* Write just the visual */
1189 inputline_init("File: ", write_region, data);
1190 } else
1191 hed_file_commit(data->file);
1193 static void
1194 exmode_cmd_read(struct fileshow_priv *data)
1196 inputline_init("File: ", read_region, data);
1198 static void
1199 exmode_cmd_pipe(struct fileshow_priv *data)
1201 inputline_init("Command: ", pipe_region, data);
1203 static void
1204 exmode_cmd_subst(struct fileshow_priv *data)
1206 subst_all = false;
1207 inputline_init("s/", expr_subst1, data);
1209 static void
1210 exmode_cmd_Subst(struct fileshow_priv *data)
1212 subst_all = true;
1213 inputline_init("S/", expr_subst1, data);
1215 static void
1216 exmode_cmd_swp(struct fileshow_priv *data)
1218 hed_file_write_swap(data->file);
1220 static void
1221 exmode_cmd_ieval(struct fileshow_priv *data)
1223 inputline_init("Expression: ", ieval_expr, data);
1225 static void
1226 exmode_cmd_reval(struct fileshow_priv *data)
1228 if (data->mode != MODE_VISUAL)
1229 hed_dup_cursor(&data->cursor, &data->visual);
1230 inputline_init("Expression: ", reval_expr, data);
1232 static void
1233 exmode_cmd_peval(struct fileshow_priv *data)
1235 inputline_init("Expression: ", peval_expr, data);
1237 static void
1238 exmode_cmd_width(struct fileshow_priv *data)
1240 inputline_init("Bytes per line: ", set_width, data);
1242 static void
1243 exmode_cmd_quit(struct fileshow_priv *data)
1245 terminus = true;
1247 static void
1248 exmode_cmd_exit(struct fileshow_priv *data)
1250 if (hed_file_is_modified(data->file))
1251 hed_file_commit(data->file);
1252 exmode_cmd_quit(data);
1255 enum command {
1256 cmd_backspace,
1257 cmd_clip_delete,
1258 cmd_clip_overwrite,
1259 cmd_clip_paste,
1260 cmd_clip_paste_after,
1261 cmd_clip_yank,
1262 cmd_delchar,
1263 cmd_delchar_back,
1264 cmd_endian,
1265 cmd_exit,
1266 cmd_exmode,
1267 cmd_file_begin,
1268 cmd_file_end,
1269 cmd_go_down,
1270 cmd_go_left,
1271 cmd_go_right,
1272 cmd_goto,
1273 cmd_go_up,
1274 cmd_ieval,
1275 cmd_insert,
1276 cmd_line_begin,
1277 cmd_line_end,
1278 cmd_normal_mode,
1279 cmd_page_down,
1280 cmd_page_up,
1281 cmd_peval,
1282 cmd_pipe,
1283 cmd_prefix,
1284 cmd_quit,
1285 cmd_read,
1286 cmd_replace,
1287 cmd_reval,
1288 cmd_scroll_down,
1289 cmd_scroll_up,
1290 cmd_search,
1291 cmd_search_back,
1292 cmd_search_next,
1293 cmd_search_prev,
1294 cmd_subst,
1295 cmd_substall,
1296 cmd_switch_column,
1297 cmd_swp,
1298 cmd_visual,
1299 cmd_width,
1300 cmd_write
1303 static enum handler_result
1304 do_command(struct fileshow_priv *data, enum command cmd)
1306 switch (cmd) {
1308 /* CURSOR MOVEMENT */
1310 case cmd_go_down:
1311 jump_relative(data, data->width);
1312 break;
1314 case cmd_go_up:
1315 jump_relative(data, -data->width);
1316 break;
1318 case cmd_go_left:
1319 if (data->column == FSC_HEX && data->digitpos > 0) {
1320 data->digitpos--;
1321 } else if (!jump_relative(data, -1) &&
1322 data->column == FSC_HEX)
1323 data->digitpos = 1;
1324 break;
1326 case cmd_go_right:
1327 if (data->column == FSC_HEX && data->digitpos < 1) {
1328 data->digitpos++;
1329 } else
1330 jump_relative(data, 1);
1331 break;
1333 case cmd_line_begin:
1334 if (!jump_relative(data, -pos_column(data, data->cursor.pos)))
1335 data->digitpos = 0;
1336 break;
1338 case cmd_line_end:
1339 if (!jump_relative(data, data->width -
1340 pos_column(data, data->cursor.pos) - 1))
1341 data->digitpos = 0;
1342 break;
1344 case cmd_file_end:
1345 if (hed_file_size(data->file)) {
1346 set_cursor(data, hed_file_size(data->file) - 1);
1347 data->digitpos = 0;
1348 break;
1350 /* else fall-through */
1352 case cmd_file_begin:
1353 set_cursor(data, 0);
1354 data->digitpos = 0;
1355 break;
1357 case cmd_page_down:
1358 slide_viewport(data, data->ssize - data->width);
1359 break;
1361 case cmd_page_up:
1362 slide_viewport(data, -data->ssize + data->width);
1363 break;
1365 case cmd_scroll_down:
1366 slide_viewport(data, data->width);
1367 break;
1369 case cmd_scroll_up:
1370 slide_viewport(data, -data->width);
1371 break;
1373 /* SIMPLE COMMANDS */
1375 case cmd_switch_column:
1376 data->column ^= 1;
1377 break;
1379 case cmd_visual:
1380 if (data->mode == MODE_VISUAL) {
1381 hed_put_cursor(&data->visual);
1382 data->mode = MODE_NORMAL;
1383 } else {
1384 data->mode = MODE_VISUAL;
1385 hed_dup_cursor(&data->cursor, &data->visual);
1387 break;
1389 case cmd_normal_mode:
1390 if (data->mode == MODE_VISUAL)
1391 hed_put_cursor(&data->visual);
1392 data->mode = MODE_NORMAL;
1393 break;
1395 case cmd_insert:
1396 if (data->mode == MODE_INSERT)
1397 data->mode = MODE_REPLACE;
1398 else
1399 data->mode = MODE_INSERT;
1400 break;
1402 case cmd_replace:
1403 data->mode = MODE_REPLACE;
1404 break;
1406 case cmd_delchar:
1407 erase_at_cursor(data, 1);
1408 break;
1410 case cmd_delchar_back:
1411 if (data->cursor.pos > 0) {
1412 jump_relative(data, -1);
1413 erase_at_cursor(data, 1);
1415 break;
1417 case cmd_backspace:
1418 return do_command(data, (cmd = data->mode == MODE_INSERT
1419 ? cmd_delchar_back : cmd_go_left));
1421 /* CLIPBOARD */
1423 case cmd_clip_yank:
1424 if (data->mode != MODE_VISUAL) {
1425 errmsg("No region selected");
1426 return EVH_SILENTHOLD;
1428 clip_yank(data);
1429 hed_put_cursor(&data->visual);
1430 data->mode = MODE_NORMAL;
1431 break;
1433 case cmd_clip_delete:
1434 if (data->mode != MODE_VISUAL) {
1435 errmsg("No region selected");
1436 return EVH_SILENTHOLD;
1438 clip_delete(data);
1439 hed_put_cursor(&data->visual);
1440 data->mode = MODE_NORMAL;
1441 break;
1443 case cmd_clip_paste_after:
1444 if (data->mode != MODE_VISUAL)
1445 hed_move_relative(&data->cursor, 1);
1446 /* fall through */
1447 case cmd_clip_paste:
1448 if (data->mode == MODE_VISUAL) {
1449 clip_swap(data);
1450 hed_put_cursor(&data->visual);
1451 data->mode = MODE_NORMAL;
1452 } else {
1453 clip_put(data);
1454 hed_move_relative(&data->cursor, -1);
1456 break;
1458 case cmd_clip_overwrite:
1459 clip_overwrite(data);
1460 if (data->mode == MODE_VISUAL) {
1461 hed_put_cursor(&data->visual);
1462 data->mode = MODE_NORMAL;
1464 break;
1466 /* INPUTLINE */
1468 case cmd_exmode:
1469 inputline_init(":", exmode, data);
1470 return EVH_SILENTHOLD;
1472 case cmd_goto:
1473 inputline_init("#", jump_offset, data);
1474 return EVH_SILENTHOLD;
1476 case cmd_search:
1477 data->last_search_dir = SDIR_FORWARD;
1478 inputline_init("/", expr_search, data);
1479 return EVH_SILENTHOLD;
1481 case cmd_search_back:
1482 data->last_search_dir = SDIR_BACK;
1483 inputline_init("?", expr_search, data);
1484 return EVH_SILENTHOLD;
1486 case cmd_search_prev:
1487 data->last_search_dir = -data->last_search_dir;
1488 /* fall through */
1489 case cmd_search_next:
1490 if (data->last_search)
1491 do_search(data);
1492 else
1493 errmsg("No previous search");
1495 if (cmd == cmd_search_prev)
1496 data->last_search_dir = -data->last_search_dir;
1497 break;
1499 /* EXMODE */
1501 case cmd_quit:
1502 exmode_cmd_quit(data);
1503 break;
1505 case cmd_exit:
1506 exmode_cmd_exit(data);
1507 break;
1509 case cmd_write:
1510 exmode_cmd_write(data);
1511 break;
1513 case cmd_read:
1514 exmode_cmd_read(data);
1515 break;
1517 case cmd_pipe:
1518 if (data-> mode == MODE_VISUAL)
1519 exmode_cmd_pipe(data);
1520 break;
1522 case cmd_subst:
1523 exmode_cmd_subst(data);
1524 break;
1526 case cmd_substall:
1527 exmode_cmd_Subst(data);
1528 break;
1530 case cmd_swp:
1531 exmode_cmd_swp(data);
1532 break;
1534 case cmd_ieval:
1535 exmode_cmd_ieval(data);
1536 break;
1538 case cmd_reval:
1539 exmode_cmd_reval(data);
1540 break;
1542 case cmd_peval:
1543 exmode_cmd_peval(data);
1544 break;
1546 case cmd_width:
1547 exmode_cmd_width(data);
1548 break;
1550 case cmd_endian:
1551 data->le ^= 1;
1552 break;
1554 /* PREFIX */
1556 case cmd_prefix:
1557 return EVH_XPREFIX;
1561 return EVH_HOLD;
1564 struct longcmd {
1565 const char *name;
1566 enum command cmd;
1569 static void
1570 exmode(struct inputline_data *inpline, void *hookdata)
1572 static const struct longcmd cmds[] = {
1573 { "!", cmd_pipe },
1574 { "endian", cmd_endian },
1575 { "exit", cmd_exit },
1576 { "ie", cmd_ieval },
1577 { "ieval", cmd_ieval },
1578 { "pe", cmd_peval },
1579 { "peval", cmd_peval },
1580 { "pipe", cmd_pipe },
1581 { "q", cmd_quit },
1582 { "quit", cmd_quit },
1583 { "r", cmd_read },
1584 { "read", cmd_read },
1585 { "re", cmd_reval },
1586 { "reval", cmd_reval },
1587 { "s", cmd_subst },
1588 { "S", cmd_substall },
1589 { "substall", cmd_substall },
1590 { "substitute", cmd_subst },
1591 { "swp", cmd_swp },
1592 { "w", cmd_write },
1593 { "width", cmd_width },
1594 { "write", cmd_write },
1595 { "x", cmd_exit },
1596 { NULL }
1599 struct fileshow_priv *data = hookdata;
1600 char *cmd = inpline->buf;
1601 int len = inpline->buf_len;
1602 const struct longcmd *p;
1604 /* Trim leading/trailing whitespaces */
1605 while (isspace(*cmd))
1606 cmd++, len--;
1607 while (isspace(cmd[len - 1]) && len > 0)
1608 len--;
1610 for (p = cmds; p->name; ++p)
1611 if (!strcmp(cmd, p->name)) {
1612 do_command(data, p->cmd);
1613 return;
1616 errmsg("Invalid command");
1619 struct shortcmd {
1620 int ch;
1621 enum command cmd;
1624 #define CTRL(ch) ((ch) - 'A' + 1)
1626 static const struct shortcmd commoncmds[] = {
1627 { '\e', cmd_normal_mode },
1628 { KEY_NPAGE, cmd_page_down },
1629 { CTRL('F'), cmd_page_down },
1630 { KEY_PPAGE, cmd_page_up },
1631 { CTRL('B'), cmd_page_up },
1632 { CTRL('E'), cmd_scroll_down },
1633 { CTRL('Y'), cmd_scroll_up },
1634 { KEY_HOME, cmd_file_begin },
1635 { KEY_END, cmd_file_end },
1636 { '\t', cmd_switch_column },
1637 { KEY_IC, cmd_insert },
1638 { KEY_DC, cmd_delchar },
1639 { KEY_BACKSPACE, cmd_backspace },
1640 { KEY_LEFT, cmd_go_left },
1641 { KEY_DOWN, cmd_go_down },
1642 { KEY_UP, cmd_go_up },
1643 { KEY_RIGHT, cmd_go_right },
1644 { KEY_F(2), cmd_write },
1645 { KEY_F(10), cmd_exit },
1646 { 0 }
1649 static const struct shortcmd cmds[] = {
1650 { 'G', cmd_file_end },
1651 { '0', cmd_line_begin },
1652 { '^', cmd_line_begin },
1653 { '$', cmd_line_end },
1654 { 'h', cmd_go_left },
1655 { 'j', cmd_go_down },
1656 { 'k', cmd_go_up },
1657 { 'l', cmd_go_right },
1658 { 'W', cmd_write },
1659 { 'Z', cmd_exit },
1660 { 'Q', cmd_quit },
1661 { ':', cmd_exmode },
1662 { '#', cmd_goto },
1663 { '/', cmd_search },
1664 { '?', cmd_search_back },
1665 { 'n', cmd_search_next },
1666 { 'N', cmd_search_prev },
1667 { 'x', cmd_delchar },
1668 { 'X', cmd_delchar_back },
1669 { 'v', cmd_visual },
1670 { 'y', cmd_clip_yank },
1671 { 'd', cmd_clip_delete },
1672 { 'p', cmd_clip_paste_after },
1673 { 'P', cmd_clip_paste },
1674 { 'o', cmd_clip_overwrite },
1675 { 'i', cmd_insert },
1676 { 'e', cmd_replace },
1677 { 'R', cmd_replace },
1678 { 'g', cmd_prefix },
1679 { 'r', cmd_prefix },
1680 { 'm', cmd_prefix },
1681 { '\'', cmd_prefix },
1682 { '\"', cmd_prefix },
1683 { 0 }
1686 static const struct shortcmd cmds_g[] = {
1687 { 'g', cmd_file_begin },
1688 { KEY_ENTER, cmd_goto },
1689 { 0 }
1692 static enum handler_result
1693 handle_shortcmd(struct fileshow_priv *data, int ch,
1694 const struct shortcmd *table)
1696 const struct shortcmd *p;
1698 for (p = table; p->ch; ++p)
1699 if (ch == p->ch)
1700 return do_command(data, p->cmd);
1702 return EVH_PASS;
1705 static enum handler_result
1706 fileshow_normal_in(struct fileshow_priv *data, int ch)
1708 enum handler_result ret;
1710 finish_insert(data);
1711 ret = handle_shortcmd(data, ch, commoncmds);
1712 if (ret == EVH_PASS)
1713 ret = handle_shortcmd(data, ch, cmds);
1714 return ret;
1717 static enum handler_result
1718 fileshow_normal_g_in(struct fileshow_priv *data, int ch)
1720 return handle_shortcmd(data, ch, cmds_g);
1723 static void
1724 hexdigit_in(struct fileshow_priv *data, int ch, int *byte)
1726 static const char hx[] = "0123456789abcdef";
1727 int nibble;
1729 nibble = strchr(hx, ch) - hx;
1730 assert(nibble < 16);
1731 if (data->digitpos == 0) {
1732 *byte = (data->curbyte & 0xf) | (nibble << 4);
1733 } else {
1734 *byte = (data->curbyte & 0xf0) | nibble;
1736 data->digitpos++;
1739 static enum handler_result
1740 fileshow_normal_r_in(struct fileshow_priv *data, int ch)
1742 enum handler_result res = EVH_PASS;
1743 int byte = -1;
1745 if (data->column == FSC_HEX) {
1746 if (isxdigit(ch)) {
1747 hexdigit_in(data, tolower(ch), &byte);
1748 if (data->digitpos > 1) {
1749 data->digitpos = 0;
1750 res = EVH_HOLD;
1751 } else {
1752 /* Keep @xprefix for the second digit. */
1753 res = EVH_CUSTOM_XPREFIX;
1756 } else if (ch == KEY_BACKSPACE && data->digitpos > 0) {
1757 data->digitpos--;
1758 res = EVH_CUSTOM_XPREFIX;
1759 } else if (ch == '\r' || ch == '\033') {
1760 /* Cancel the replace. */
1761 data->digitpos = 0;
1762 res = EVH_HOLD;
1763 } else {
1764 /* Persist until the user types something sensible. */
1765 res = EVH_CUSTOM_XPREFIX;
1768 } else {
1769 byte = ch;
1770 res = EVH_HOLD;
1773 if (byte >= 0) {
1774 if (data->mode == MODE_VISUAL) {
1775 hed_cursor_t base = HED_NULL_CURSOR;
1776 size_t len = visual_extents(data, &base);
1777 hed_file_set_bytes(data->file, &base, byte, len);
1778 hed_put_cursor(&base);
1779 } else {
1780 hed_file_set_byte(data->file, &data->cursor, byte);
1782 data->curbyte = byte;
1785 return res;
1788 static enum handler_result
1789 fileshow_normal_m_in(struct fileshow_priv *data, int ch)
1791 int mark = ch2mark(ch);
1792 if (mark >= 0)
1793 hed_dup2_cursor(&data->cursor,
1794 hed_file_mark(data->file, mark));
1795 else
1796 errmsg("Invalid mark");
1797 return EVH_SILENTHOLD;
1800 static enum handler_result
1801 fileshow_normal_Qq_in(struct fileshow_priv *data, int ch)
1803 int mark = ch2mark(ch);
1804 if (mark >= 0) {
1805 if (hed_file_has_mark(data->file, mark))
1806 set_cursor(data,
1807 hed_file_mark(data->file, mark)->pos);
1808 else
1809 errmsg("No such mark");
1810 } else {
1811 errmsg("Invalid mark");
1813 return EVH_HOLD;
1816 static enum handler_result
1817 fileshow_normal_QQ_in(struct fileshow_priv *data, int ch)
1819 char reg = ch2reg(ch);
1820 if (reg >= 0) {
1821 data->reg = reg;
1822 } else {
1823 errmsg("Invalid register");
1825 return EVH_SILENTHOLD;
1828 static enum handler_result
1829 fileshow_byte_in(struct fileshow_priv *data, int ch)
1831 enum handler_result ret = EVH_PASS;
1832 bool insert;
1833 int cursmove;
1834 int byte = -1;
1836 ret = handle_shortcmd(data, ch, commoncmds);
1837 if (ret != EVH_PASS)
1838 return ret;
1840 insert = (data->mode == MODE_INSERT && data->cursor.pos < OFF_MAX);
1842 if (insert && !hed_is_a_cursor(&data->insert)) {
1843 hed_file_insert_begin(data->file, &data->cursor, &data->insert);
1844 data->digitpos = 0;
1845 if (data->offset.pos == data->cursor.pos) {
1846 hed_dup2_cursor(&data->insert, &data->offset);
1847 update_idmark(data);
1851 cursmove = 0;
1852 if (data->column == FSC_HEX) {
1853 if (isxdigit(ch)) {
1854 if (insert && data->digitpos == 0)
1855 data->curbyte = 0;
1856 hexdigit_in(data, tolower(ch), &byte);
1857 if (data->digitpos > 1) {
1858 data->digitpos = 0;
1859 cursmove = 1;
1861 ret = EVH_HOLD;
1863 } else {
1864 cursmove = 1;
1865 byte = ch;
1866 ret = EVH_HOLD;
1869 if (byte < 0)
1870 return ret;
1872 if (insert) {
1873 if (data->column != FSC_HEX) {
1874 hed_file_insert_byte(data->file, &data->insert, byte);
1875 } else if (data->digitpos > 0) {
1876 hed_dup2_cursor(&data->insert, &data->cursor);
1877 hed_file_insert_byte(data->file, &data->insert, byte);
1878 } else {
1879 hed_file_set_byte(data->file, &data->cursor, byte);
1880 hed_move_relative(&data->cursor, 1);
1882 data->curbyte = byte;
1884 if (data->cursor.pos - data->ssize >= data->offset.pos)
1885 slide_viewport(data, data->width);
1886 else
1887 assert(data->cursor.pos >= data->offset.pos);
1888 } else {
1889 hed_file_set_byte(data->file, &data->cursor, byte);
1890 data->curbyte = byte;
1892 jump_relative(data, cursmove);
1895 return ret;
1898 static enum handler_result
1899 fileshow_handler(struct ui_component *comp, const struct ui_event *event)
1901 struct fileshow_priv *data = comp->data;
1902 enum handler_result res = EVH_PASS;
1904 switch (event->type) {
1905 case EV_REDRAW:
1906 fileshow_redraw(comp);
1907 break;
1908 case EV_KEYBOARD:
1910 int ch = event->v.keyboard.ch;
1912 if (data->mode == MODE_NORMAL || data->mode == MODE_VISUAL) {
1913 if (xprefix == 'g') {
1914 res = fileshow_normal_g_in(data, ch);
1915 } else if (xprefix == 'r') {
1916 res = fileshow_normal_r_in(data, ch);
1917 } else if (xprefix == 'm') {
1918 res = fileshow_normal_m_in(data, ch);
1919 } else if (xprefix == '\'') {
1920 res = fileshow_normal_Qq_in(data, ch);
1921 } else if (xprefix == '"') {
1922 res = fileshow_normal_QQ_in(data, ch);
1923 } else {
1924 res = fileshow_normal_in(data, ch);
1926 } else
1927 res = fileshow_byte_in(data, ch);
1929 if (res != EVH_PASS && res != EVH_SILENTHOLD)
1930 fileshow_redraw(comp);
1931 break;
1933 case EV_DONE:
1934 fileshow_done(comp);
1935 break;
1936 default:
1937 fprintf(stderr, "Bug: fileshow_handler E.T. %d\n", event->type);
1938 break;
1941 return res;