Rewrite the hed_expr API
[hed.git] / src / ui / fileshow.c
blob7b9ce222bf12771a7ef922bfa4a5aa9f3ba2293b
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 hed_cursor_t search; /* Current search position */
114 struct hed_expr *last_search;
115 enum { SDIR_FORWARD = 1, SDIR_BACK = -1 } last_search_dir;
117 enum { FSC_HEX, FSC_ASC } column;
118 enum ui_mode mode;
120 int reg;
122 int le; /* non-zero if little endian view */
125 static const char idmarks[] = {
126 '>', ' ', '<', /* insert start, normal, insert end */
127 ')', '|', '(', /* dtto after erase */
130 #define IDMARK_ERASE_IDX 3
132 static enum handler_result fileshow_handler(struct ui_component *, const struct ui_event *);
133 static long eval_reg_cb(void *hookdata, char reg, hed_off_t ofs,
134 unsigned char *scramble, size_t len);
135 static long eval_mark_cb(void *hookdata, char mark,
136 unsigned char *scramble, size_t len);
138 enum ui_mode opt_defmode = -1;
140 static void
141 update_idmark(struct fileshow_priv *data)
143 int idx = 1;
145 /* marks can only happen at zero offset */
146 if (!data->offset.off) {
147 idx = !hed_block_is_inserted(data->offset.block);
148 if (hed_block_is_after_erase(data->offset.block))
149 idx += IDMARK_ERASE_IDX;
150 if (hed_block_is_after_insert(data->offset.block))
151 idx = 1;
153 data->idmark = idmarks[idx];
156 static unsigned
157 pos_column(struct fileshow_priv *data, hed_uoff_t pos)
159 return pos % data->width;
162 static void
163 set_viewport(struct fileshow_priv *data, off_t pos)
165 hed_update_cursor(data->file, pos, &data->offset);
166 update_idmark(data);
169 /* Update screen offset to make cursor fit into the current viewport */
170 static void
171 fixup_viewport(struct fileshow_priv *data)
173 off_t curline = data->cursor.pos - pos_column(data, data->cursor.pos);
175 if (curline < data->offset.pos)
176 set_viewport(data, curline);
177 else if (curline - data->ssize >= data->offset.pos)
178 set_viewport(data, curline - data->ssize + data->width);
181 static void
182 set_cursor(struct fileshow_priv *data, off_t pos)
184 hed_update_cursor(data->file, pos, &data->cursor);
185 fixup_viewport(data);
188 /* Update cursor position to fit into the current viewport */
189 static void
190 fixup_cursor(struct fileshow_priv *data)
192 off_t curline = data->cursor.pos - pos_column(data, data->cursor.pos);
194 if (curline < data->offset.pos)
195 hed_move_relative(&data->cursor,
196 data->offset.pos - curline);
197 else if (curline >= data->offset.pos + data->ssize)
198 hed_move_relative(&data->cursor,
199 data->offset.pos + data->ssize -
200 data->width - curline);
203 static void
204 slide_viewport(struct fileshow_priv *data, off_t num)
206 hed_move_relative(&data->offset, num);
207 update_idmark(data);
208 fixup_cursor(data);
211 /* Initialize the component. */
212 void
213 fileshow_init(struct hed_file *file)
215 struct fileshow_priv *data;
216 struct ui_component *fileshow;
217 int width;
219 data = calloc(1, sizeof(struct fileshow_priv));
220 if (!data) goto nomem;
221 data->file = file;
222 data->reg = ch2reg('"');
223 data->le = arch_little_endian();
225 if ((int) opt_defmode >= 0) {
226 data->mode = opt_defmode;
227 } else {
228 char *defmode = get_opt_str("default_mode", "normal");
229 if (!strcmp(defmode, "normal")) {
230 data->mode = MODE_NORMAL;
231 } else if (!strcmp(defmode, "insert")) {
232 data->mode = MODE_INSERT;
233 } else if (!strcmp(defmode, "replace")) {
234 data->mode = MODE_REPLACE;
235 } else {
236 data->mode = MODE_NORMAL;
240 fileshow = ui_register(fileshow_handler, data);
241 if (!fileshow) {
242 free(data);
243 nomem:
244 sched_exit(EX_OSERR);
245 return;
248 /* TODO: Better height calculation. */
249 data->height = term_get_max_y() - 2;
250 width = (term_get_max_x() - /* ofs */ 8 - /* spaces */ 4);
251 width /= 4;
252 if (!opt_spreadview) {
253 width = 1 << bitsize(width);
254 width = get_opt_int("bytes_per_line", width);
256 data->width = width;
257 data->ssize = data->height * data->width;
259 hed_get_cursor(data->file, 0, &data->cursor);
260 hed_dup_cursor(&data->cursor, &data->offset);
261 data->idmark = ' ';
264 static void
265 fileshow_done(struct ui_component *comp)
267 free(comp->data);
268 ui_unregister(comp);
271 /* Check whether @pos is inside the visual block */
272 static int
273 is_inside_visual(struct fileshow_priv *data, off_t pos)
275 return (data->cursor.pos <= pos && pos <= data->visual.pos) ||
276 (data->visual.pos <= pos && pos <= data->cursor.pos);
279 /* Returns ZERO ON ERROR. */
280 static size_t
281 visual_extents(struct fileshow_priv *data, hed_cursor_t *base)
283 hed_cursor_t *basesrc;
284 size_t ret;
285 if (data->visual.pos < data->cursor.pos) {
286 ret = data->cursor.pos - data->visual.pos + 1;
287 basesrc = &data->visual;
288 } else {
289 ret = data->visual.pos - data->cursor.pos + 1;
290 basesrc = &data->cursor;
292 hed_dup2_cursor(basesrc, base);
293 return ret;
296 #define BYTES_PER_LONG (sizeof(unsigned long))
298 #define DIGITS_MAX(num) ((num + 9)/10)
300 /* The decadic logarithm of 2^8 is approx. 2.408239965311849584,
301 * so 3 digits must be always sufficient to represent the largest
302 * value that can be held in one byte. */
303 #define DIGITS_PER_BYTE 3
305 static int
306 print_status_num(unsigned char *cursdata, int nbytes,
307 int little_endian, int x, int y, int width)
309 off_t num;
310 long flags;
312 flags = little_endian ? HED_AEF_FORCELE : HED_AEF_FORCEBE;
314 if (nbytes < 0) {
315 nbytes = -nbytes;
316 flags |= HED_AEF_SIGNED;
319 assert(x && cursdata);
320 assert(nbytes * 8 <= _FILE_OFFSET_BITS);
322 num = hed_bytestr2off(cursdata, nbytes, flags);
324 return term_printf(x, y, COLOR_STATUS, flags & HED_AEF_SIGNED
325 ? "s%d:%*lld "
326 : "u%d:%*llu ",
327 nbytes * 8, width, (long long) num);
330 static void
331 print_fileview_status(struct fileshow_priv *data,
332 unsigned char *cursdata, int ofslen)
334 static char modechar[] = { 'N', 'R', 'I', 'V' };
335 int pos = 0;
336 const int y = data->height;
338 /* First, fill the status line with the status color */
339 term_set_bg_color(COLOR_STATUS);
340 term_clear_line(data->height);
342 /* We print the filename first so that it gets overwritten by
343 * the "more important" stuff. The most important part of the
344 * filename tends to be at the end anyway. */
345 term_print_string(term_get_max_x() - 1 - strlen(data->file->name),
346 y, COLOR_STATUS, data->file->name);
348 pos += term_printf(0, y, COLOR_STATUS, "%0*llx: %03d ", ofslen,
349 (unsigned long long) data->cursor.pos, *cursdata);
351 term_print_string(pos, y, COLOR_STATUS, data->le ? "LE: " : "BE: ");
352 pos += 4;
354 pos += print_status_num(cursdata, 2, data->le, pos, y, 6);
355 pos += print_status_num(cursdata, -2, data->le, pos, y, 6);
356 pos += print_status_num(cursdata, 4, data->le, pos, y, 11);
357 pos += print_status_num(cursdata, -4, data->le, pos, y, 11);
359 term_print_char(pos, y, COLOR_MODE,
360 hed_file_is_modified(data->file) ? '*' : ' ');
361 term_print_char(pos + 1, y, COLOR_MODE, modechar[data->mode]);
365 static int
366 num_hex_width(hed_uoff_t num)
368 /* Offset of the leftmost non-zero bit. */
369 int width = 0;
370 int i;
372 for (i = sizeof(hed_uoff_t) * 8 / 2; i > 0; i /= 2) {
373 if (num & (~(hed_uoff_t)0 << i)) {
374 num >>= i; width += i;
377 /* Round the width up to the hexadecimal places. */
378 return width / 4 + 1;
381 static void
382 fileshow_redraw(struct ui_component *comp)
384 struct fileshow_priv *data = comp->data;
385 hed_cursor_t pos;
386 char idmark; /* insertion/deletion marks */
388 hed_uoff_t maxofs;
389 int ofslen;
390 int xindent, xascpos;
391 int row;
393 unsigned char *p;
394 unsigned char cursdata[CURSOR_LENGTH];
396 if (!hed_prepare_read(data->file, &data->offset, 1)) {
397 /* This is a fatal error (e.g. no memory) */
398 errmsg("Cannot display file content");
399 return;
402 /* Offset width is chosen to accomodate the whole file in its current
403 * size as well as the current screen. */
404 maxofs = data->offset.pos + data->ssize - 1;
405 if (maxofs < hed_file_size(data->file))
406 maxofs = hed_file_size(data->file);
407 ofslen = num_hex_width(maxofs);
408 /* Always print at least 4 digits. */
409 if (ofslen < 4)
410 ofslen = 4;
411 xindent = ofslen + 2;
412 xascpos = xindent + data->width * 3 + 1;
414 hed_dup_cursor(&data->offset, &pos);
415 idmark = data->idmark;
416 assert(pos.pos >= 0);
418 term_set_bg_color(COLOR_NEUTRAL);
419 erase();
421 p = hed_cursor_data(&pos);
423 for (row = 0; row < data->height; ++row) {
424 int col;
425 term_color oldv = 0;
427 term_printf(0, row, COLOR_OFFSET, "%0*llx", ofslen, pos.pos);
429 for (col = 0; col < data->width; ++col) {
430 char hex[3];
431 unsigned char asc;
432 term_color color, v;
434 v = (data->mode == MODE_VISUAL &&
435 is_inside_visual(data, pos.pos))
436 ? COLOR_VISUAL
437 : 0;
439 color = COLOR_DATA_BASE;
440 if (pos.pos >= hed_file_size(data->file))
441 color |= COLOR_DATA_OUTSIDE;
443 term_color markcolor = (idmark == ' ')
444 ? (hed_block_is_dirty(pos.block)
445 ? COLOR_DIRTY
446 : color)
447 : COLOR_MARK;
448 term_print_char(xindent + col * 3 - 1, row,
449 markcolor | v | oldv, idmark);
451 if (pos.pos == data->cursor.pos) {
452 cursor_y = row;
453 cursor_x = data->column == FSC_HEX
454 ? xindent + col * 3 + data->digitpos
455 : xascpos + col;
456 color |= COLOR_DATA_CURSOR;
457 } else if (hed_block_is_dirty(pos.block))
458 color = COLOR_DIRTY;
459 if (hed_block_is_bad(pos.block))
460 color = COLOR_ERROR;
461 color |= v;
463 asc = p ? *p++ : 0x00;
464 sprintf(hex,
465 hed_block_is_bad(pos.block) ? "XX" : "%02x",
466 asc);
467 asc = isprint(asc) ? asc : asc == '\0' ? '.' : ':';
469 term_print_string(xindent + col * 3, row,
470 color, hex);
471 if (color >= COLOR_DATA_BASE)
472 color |= COLOR_DATA_ASCII;
473 term_print_char(xascpos + col, row,
474 color, (hed_block_is_bad(pos.block)
475 ? '?' : asc));
477 if (pos.pos >= OFF_MAX) {
478 term_print_char(xindent + col * 3 + 2, row,
479 COLOR_MARK | v, '!');
480 goto done;
483 int idx = !!hed_block_is_inserted(pos.block);
484 hed_move_relative(&pos, 1);
485 if (!pos.off) {
486 idx -= !!hed_block_is_inserted(pos.block);
487 if (hed_block_is_after_erase(pos.block))
488 idx += IDMARK_ERASE_IDX;
489 idmark = idmarks[idx + 1];
491 if (!hed_prepare_read(data->file, &pos, 1)) {
492 /* This is a fatal error */
493 errmsg("Cannot display file content");
494 return;
497 p = hed_cursor_data(&pos);
498 } else
499 idmark = ' ';
501 oldv = v;
504 if (idmark == '<' || idmark == '(') {
505 term_print_char(xascpos - 2, row,
506 COLOR_MARK | oldv, idmark);
507 idmark = ' ';
508 } else if (oldv)
509 term_print_char(xascpos - 2, row,
510 COLOR_NEUTRAL | COLOR_VISUAL, ' ');
512 done:
513 hed_put_cursor(&pos);
515 assert(data->cursor.pos >= 0);
517 memset(cursdata, 0, sizeof(cursdata));
518 hed_file_cpin(data->file, cursdata, sizeof(cursdata), &data->cursor);
519 data->curbyte = cursdata[0];
521 print_fileview_status(data, cursdata, ofslen);
522 term_unset_bg_color();
525 /* Jump to a position given by offset expression. */
526 static void
527 jump_offset(struct inputline_data *inpline, void *hookdata)
529 struct fileshow_priv *data = hookdata;
530 struct hed_expr *expr;
531 off_t pos;
533 expr = hed_expr_new(inpline->buf, eval_reg_cb, eval_mark_cb, data);
534 if (!expr) {
535 errmsg("Invalid expression");
536 return;
538 if (hed_expr_eval(expr) & HED_AEF_ERROR) {
539 errmsg("Cannot evaluate expression");
540 hed_expr_free(expr);
541 return;
543 pos = hed_expr2off(expr);
544 pos &= OFF_MAX;
545 hed_expr_free(expr);
547 set_cursor(data, pos);
550 static int
551 jump_relative(struct fileshow_priv *data, int num)
553 /* Zero-length jump usually means an invalid jump out of bounds */
554 if (hed_move_relative(&data->cursor, num) == 0)
555 return -1;
557 data->digitpos = 0;
558 fixup_viewport(data);
559 return 0;
562 static void
563 finish_insert(struct fileshow_priv *data)
565 if (hed_is_a_cursor(&data->insert)) {
566 hed_file_insert_end(data->file, &data->insert);
567 if (data->column == FSC_HEX && data->digitpos)
568 jump_relative(data, 1);
572 static void
573 erase_at_cursor(struct fileshow_priv *data, hed_uoff_t len)
575 hed_file_erase_block(data->file, &data->cursor, len);
576 if (!hed_is_a_cursor(&data->offset)) {
577 hed_dup2_cursor(&data->cursor, &data->offset);
578 hed_move_relative(&data->offset,
579 -pos_column(data, data->offset.pos));
580 update_idmark(data);
584 static off_t
585 do_search(struct fileshow_priv *data)
587 int res;
589 hed_dup_cursor(&data->cursor, &data->search);
590 hed_move_relative(&data->search, data->last_search_dir);
591 res = hed_file_find_expr(data->file, &data->search,
592 data->last_search_dir,
593 data->last_search, eval_reg_cb, data);
594 if (!res)
595 set_cursor(data, data->search.pos);
596 else if (!subst_all)
597 errmsg("No match found");
599 hed_put_cursor(&data->search);
600 return data->search.pos;
603 /* Search for the given string. */
604 static void
605 expr_search(struct inputline_data *inpline, void *hookdata)
607 struct fileshow_priv *data = hookdata;
609 if (data->last_search)
610 hed_expr_free(data->last_search);
611 data->last_search = hed_expr_new(inpline->buf,
612 eval_reg_cb, eval_mark_cb, data);
613 if (!data->last_search) {
614 errmsg("Invalid expression");
615 return;
618 do_search(data);
621 /* Search and substitute the given string. */
622 static void
623 expr_subst(struct inputline_data *inpline, void *hookdata)
625 struct fileshow_priv *data = hookdata;
626 struct hed_expr *subst;
628 subst = hed_expr_new(inpline->buf, eval_reg_cb, eval_mark_cb, data);
629 if (!subst) {
630 errmsg("Invalid expression");
631 return;
634 data->last_search_dir = SDIR_FORWARD;
635 while (do_search(data) != HED_FINDOFF_NO_MATCH) {
636 if (hed_expr_eval(subst) & HED_AEF_ERROR) {
637 errmsg("Cannot evaluate expression");
638 break;
640 /* Cursor at the match */
641 erase_at_cursor(data, hed_expr_len(data->last_search));
642 hed_file_insert_once(data->file, &data->cursor,
643 hed_expr_buf(subst), hed_expr_len(subst));
644 if (!subst_all)
645 break;
647 hed_expr_free(subst);
650 static void
651 expr_subst1(struct inputline_data *inpline, void *hookdata)
653 struct fileshow_priv *data = hookdata;
655 if (data->last_search)
656 hed_expr_free(data->last_search);
657 data->last_search = hed_expr_new(inpline->buf,
658 eval_reg_cb, eval_mark_cb, data);
659 if (!data->last_search) {
660 errmsg("Invalid expression");
661 return;
663 inputline_init("s/.../", expr_subst, data);
666 static void
667 clip_yank(struct fileshow_priv *data)
669 hed_cursor_t base = HED_NULL_CURSOR;
670 size_t len = visual_extents(data, &base);
671 void *buf = realloc(clipboard[data->reg].data, len);
673 if (buf) {
674 clipboard[data->reg].data = buf;
675 len -= hed_file_cpin(data->file, buf, len, &base);
676 clipboard[data->reg].len = len;
678 hed_put_cursor(&base);
681 /* Those functions are totally horrible kludges. There should be specialized
682 * file methods for those operations. */
684 static void
685 clip_delete(struct fileshow_priv *data)
687 size_t len;
689 clip_yank(data);
690 len = visual_extents(data, &data->cursor);
691 erase_at_cursor(data, len);
694 static void
695 clip_put(struct fileshow_priv *data)
697 if (!clipboard[data->reg].data) {
698 errmsg("Register empty");
699 return;
701 hed_file_insert_once(data->file, &data->cursor,
702 clipboard[data->reg].data,
703 clipboard[data->reg].len);
704 if (data->offset.pos == data->cursor.pos)
705 hed_move_relative(&data->offset,
706 -clipboard[data->reg].len);
709 static void
710 clip_overwrite(struct fileshow_priv *data)
712 hed_cursor_t start = HED_NULL_CURSOR;
713 off_t i;
714 ssize_t len;
716 if (!clipboard[data->reg].data) {
717 errmsg("Register empty");
718 return;
721 if (data->mode == MODE_VISUAL) {
722 /* spill */
723 len = visual_extents(data, &start);
724 } else {
725 /* splat */
726 hed_dup_cursor(&data->cursor, &start);
727 len = clipboard[data->reg].len;
730 i = min(len, clipboard[data->reg].len);
731 hed_file_set_block(data->file, &start, clipboard[data->reg].data, i);
732 hed_file_set_bytes(data->file, &start, 0, len - i);
733 hed_put_cursor(&start);
736 static void
737 clip_swap(struct fileshow_priv *data)
739 struct clipboard_register orig, new;
741 new = clipboard[data->reg];
742 clipboard[data->reg].data = NULL;
743 clip_delete(data);
744 orig = clipboard[data->reg];
745 clipboard[data->reg] = new;
746 clip_put(data);
747 clipboard[data->reg] = orig;
751 static void
752 set_width(struct inputline_data *inpline, void *hookdata)
754 struct fileshow_priv *data = hookdata;
755 char *l;
756 int w;
757 w = strtol(inpline->buf, &l, 0);
758 if (*l) {
759 errmsg("Invalid width");
760 return;
762 data->width = w;
766 static void
767 read_region(struct inputline_data *inpline, void *hookdata)
769 struct fileshow_priv *data = hookdata;
770 char *fname = inpline->buf;
771 int flen = inpline->buf_len;
772 FILE *f;
773 int ch;
775 /* Trim leading/trailing whitespaces */
776 while (isspace(*fname))
777 fname++, flen--;
778 while (isspace(fname[flen - 1]) && flen > 0)
779 flen--;
780 if (!*fname)
781 return;
782 f = fopen(fname, "r");
783 if (!f) {
784 errmsg(strerror(errno));
785 return;
788 if (data->mode == MODE_VISUAL) {
789 size_t len = visual_extents(data, &data->cursor);
790 erase_at_cursor(data, len);
793 /* Ugh. :-) */
794 hed_file_insert_begin(data->file, &data->cursor, &data->insert);
795 while ((ch = fgetc(f)) != EOF)
796 hed_file_insert_byte(data->file, &data->insert, ch);
797 finish_insert(data);
798 fclose(f);
801 static int
802 write_to_file(struct hed_file *in, hed_cursor_t *pos, hed_uoff_t len,
803 FILE *out)
805 while (len) {
806 size_t blen, written;
807 void *p;
809 if (! (blen = hed_prepare_read(in, pos, len)) )
810 return -1;
812 if ( (p = hed_cursor_data(pos)) )
813 written = fwrite(p, 1, blen, out);
814 else for (written = 0; written < blen; ++written)
815 if (fputc(0, out) < 0)
816 break;
818 hed_move_relative(pos, written);
819 if (written < blen)
820 return -1;
821 len -= blen;
823 return 0;
826 static void
827 write_region(struct inputline_data *inpline, void *hookdata)
829 struct fileshow_priv *data = hookdata;
830 hed_cursor_t base = HED_NULL_CURSOR;
831 size_t len;
832 char *fname = inpline->buf;
833 int flen = inpline->buf_len;
834 FILE *f;
836 /* Trim leading/trailing whitespaces */
837 while (isspace(*fname))
838 fname++, flen--;
839 while (isspace(fname[flen - 1]) && flen > 0)
840 flen--;
841 if (!*fname)
842 return;
844 f = fopen(fname, "w");
845 if (!f) {
846 errmsg(strerror(errno));
847 return;
849 len = visual_extents(data, &base);
850 if (write_to_file(data->file, &base, len, f))
851 errmsg(strerror(errno));
852 hed_put_cursor(&base);
853 fclose(f);
856 /* Arbitrarily chosen... */
857 #define CHUNK_SIZE 1024
859 static int
860 filter_select(struct fileshow_priv *data, hed_cursor_t *base,
861 ssize_t len, int fd_in, int fd_out)
863 fd_set fdr, fdw;
864 int ret;
866 hed_file_insert_begin(data->file, &data->cursor, &data->insert);
867 while (fd_out || fd_in) {
868 FD_ZERO(&fdr);
869 FD_ZERO(&fdw);
870 if (fd_in) FD_SET(fd_in, &fdr);
871 if (fd_out) FD_SET(fd_out, &fdw);
873 ret = select(fd_in+fd_out+1, &fdr, &fdw, NULL, NULL);
875 if (ret <= 0)
876 continue;
878 if (FD_ISSET(fd_in, &fdr)) {
879 unsigned char buf[CHUNK_SIZE];
880 ret = read(fd_in, buf, CHUNK_SIZE);
881 if (ret < 0)
882 goto err;
883 if (ret == 0) {
884 close(fd_in);
885 fd_in = 0;
887 hed_file_insert_block(data->file, &data->insert,
888 buf, ret);
889 data->cursor.pos += ret;
892 if (FD_ISSET(fd_out, &fdw)) {
893 size_t blen = min(len, CHUNK_SIZE);
894 unsigned char *buf = malloc(blen);
895 blen -= hed_file_cpin(data->file, buf, blen, base);
896 if (blen < CHUNK_SIZE) {
897 len = 0;
898 } else {
899 len -= blen;
901 ret = write(fd_out, buf, blen);
902 free(buf);
903 if (ret <= 0)
904 goto err;
905 if (ret < blen)
906 len += blen - ret;
907 hed_move_relative(base, ret - blen);
909 if (!len) {
910 close(fd_out);
911 fd_out = 0;
915 finish_insert(data);
916 return 0;
917 err:
918 finish_insert(data);
919 if (fd_out)
920 close(fd_out);
921 if (fd_in)
922 close(fd_in);
923 return -1;
926 static void
927 pipe_region(struct inputline_data *inpline, void *hookdata)
929 struct fileshow_priv *data = hookdata;
930 hed_cursor_t base = HED_NULL_CURSOR;
931 size_t len = visual_extents(data, &base);
932 char *argv[4] = { "/bin/sh", "-c", inpline->buf, NULL };
933 int p_rd[2], p_wr[2];
934 pid_t pid;
936 hed_put_cursor(&data->visual);
937 data->mode = MODE_NORMAL;
939 pipe(p_rd);
940 pipe(p_wr);
942 pid = fork();
943 if (pid == 0) {
944 close(p_rd[1]);
945 close(p_wr[0]);
947 dup2(p_rd[0], 0);
948 dup2(p_wr[1], 1);
950 exit(execvp(argv[0], argv));
952 } else if (pid > 0) {
953 int status, ret;
955 close(p_rd[0]);
956 close(p_wr[1]);
958 set_cursor(data, base.pos + len);
959 if (filter_select(data, &base, len, p_wr[0], p_rd[1]) < 0) {
960 errmsg("Filter failed");
961 hed_put_cursor(&base);
962 return;
964 ret = waitpid(pid, &status, 0);
965 if (ret != pid) {
966 errmsg("Funny zombie");
967 hed_put_cursor(&base);
968 return;
970 if (WIFEXITED(status)) {
971 if (WEXITSTATUS(status))
972 errmsg("Filter returned error");
973 set_cursor(data, base.pos);
974 erase_at_cursor(data, len);
975 hed_put_cursor(&base);
976 return;
978 errmsg("Curious error");
979 hed_put_cursor(&base);
980 return;
982 } else {
983 errmsg("Fork failed");
984 hed_put_cursor(&base);
985 return;
987 hed_put_cursor(&base);
990 static hed_cursor_t xre;
992 static long
993 eval_mark_cb(void *hookdata, char mark,
994 unsigned char *scramble, size_t len)
996 struct fileshow_priv *data = hookdata;
997 int m = ch2mark(mark);
998 if (m >= 0) {
999 if (hed_file_has_mark(data->file, m)) {
1000 hed_off2bytestr(scramble, len,
1001 hed_file_mark(data->file, m)->pos);
1002 return 0;
1004 } else {
1005 if (mark == '$') {
1006 hed_off2bytestr(scramble, len, data->cursor.pos);
1007 return 0;
1010 memset(scramble, 0, len);
1011 return 0;
1015 static long
1016 eval_reg_cb(void *hookdata, char reg, hed_off_t ofs,
1017 unsigned char *scramble, size_t len)
1019 struct fileshow_priv *data = hookdata;
1020 unsigned char *buf;
1021 size_t blen;
1022 hed_cursor_t blkpos;
1023 off_t skip;
1024 int r;
1025 long ret = 0;
1027 switch (reg) {
1028 case ',':
1029 hed_dup_cursor(&data->cursor, &blkpos);
1030 break;
1031 case '_':
1032 hed_get_cursor(data->file, 0, &blkpos);
1033 break;
1034 case '.':
1035 if (hed_is_a_cursor(&data->search)) {
1036 hed_dup_cursor(&data->search, &blkpos);
1037 ret |= HED_AEF_DYNAMIC;
1038 } else if (hed_is_a_cursor(&xre))
1039 hed_dup_cursor(&xre, &blkpos);
1040 else
1041 goto out_zero;
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 if (hed_file_cpin(data->file, scramble, len, &blkpos))
1077 ret |= HED_AEF_ERROR;
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;
1093 expr = hed_expr_new(inpline->buf, eval_reg_cb, eval_mark_cb, data);
1094 if (!expr) {
1095 errmsg("Invalid expression");
1096 return;
1098 if (hed_expr_eval(expr) & HED_AEF_ERROR) {
1099 errmsg("Cannot evaluate expression");
1100 hed_expr_free(expr);
1101 return;
1103 hed_file_insert_once(data->file, &data->cursor,
1104 hed_expr_buf(expr), 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;
1113 static char *resbuf;
1114 char *p;
1115 size_t i, len;
1116 unsigned char *buf;
1118 expr = hed_expr_new(inpline->buf, eval_reg_cb, eval_mark_cb, data);
1119 if (!expr) {
1120 errmsg("Invalid expression");
1121 return;
1123 if (hed_expr_eval(expr) & HED_AEF_ERROR) {
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, buf = hed_expr_buf(expr); 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;
1151 int elen;
1152 size_t i, len;
1154 expr = hed_expr_new(inpline->buf, eval_reg_cb, eval_mark_cb, data);
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 if (hed_expr_eval(expr) & HED_AEF_ERROR) {
1164 errmsg("Cannot evaluate expression");
1165 break;
1167 rlen = elen;
1168 if (rlen > len - i)
1169 rlen = len - i;
1170 hed_file_set_block(data->file, &xre, hed_expr_buf(expr), rlen);
1172 hed_expr_free(expr);
1173 hed_put_cursor(&xre);
1174 xre.block = NULL;
1175 out:
1176 if (data->mode != MODE_VISUAL)
1177 hed_put_cursor(&data->visual);
1180 static void exmode(struct inputline_data *inpline, void *hookdata);
1182 /* Evaluate an exmode command. */
1183 static void
1184 exmode_cmd_write(struct fileshow_priv *data)
1186 if (data->mode == MODE_VISUAL) {
1187 /* Write just the visual */
1188 inputline_init("File: ", write_region, data);
1189 } else
1190 hed_file_commit(data->file);
1192 static void
1193 exmode_cmd_read(struct fileshow_priv *data)
1195 inputline_init("File: ", read_region, data);
1197 static void
1198 exmode_cmd_pipe(struct fileshow_priv *data)
1200 inputline_init("Command: ", pipe_region, data);
1202 static void
1203 exmode_cmd_subst(struct fileshow_priv *data)
1205 subst_all = false;
1206 inputline_init("s/", expr_subst1, data);
1208 static void
1209 exmode_cmd_Subst(struct fileshow_priv *data)
1211 subst_all = true;
1212 inputline_init("S/", expr_subst1, data);
1214 static void
1215 exmode_cmd_swp(struct fileshow_priv *data)
1217 hed_file_write_swap(data->file);
1219 static void
1220 exmode_cmd_ieval(struct fileshow_priv *data)
1222 inputline_init("Expression: ", ieval_expr, data);
1224 static void
1225 exmode_cmd_reval(struct fileshow_priv *data)
1227 if (data->mode != MODE_VISUAL)
1228 hed_dup_cursor(&data->cursor, &data->visual);
1229 inputline_init("Expression: ", reval_expr, data);
1231 static void
1232 exmode_cmd_peval(struct fileshow_priv *data)
1234 inputline_init("Expression: ", peval_expr, data);
1236 static void
1237 exmode_cmd_width(struct fileshow_priv *data)
1239 inputline_init("Bytes per line: ", set_width, data);
1241 static void
1242 exmode_cmd_quit(struct fileshow_priv *data)
1244 terminus = true;
1246 static void
1247 exmode_cmd_exit(struct fileshow_priv *data)
1249 if (hed_file_is_modified(data->file))
1250 hed_file_commit(data->file);
1251 exmode_cmd_quit(data);
1254 enum command {
1255 cmd_backspace,
1256 cmd_clip_delete,
1257 cmd_clip_overwrite,
1258 cmd_clip_paste,
1259 cmd_clip_paste_after,
1260 cmd_clip_yank,
1261 cmd_delchar,
1262 cmd_delchar_back,
1263 cmd_endian,
1264 cmd_exit,
1265 cmd_exmode,
1266 cmd_file_begin,
1267 cmd_file_end,
1268 cmd_go_down,
1269 cmd_go_left,
1270 cmd_go_right,
1271 cmd_goto,
1272 cmd_go_up,
1273 cmd_ieval,
1274 cmd_insert,
1275 cmd_line_begin,
1276 cmd_line_end,
1277 cmd_normal_mode,
1278 cmd_page_down,
1279 cmd_page_up,
1280 cmd_peval,
1281 cmd_pipe,
1282 cmd_prefix,
1283 cmd_quit,
1284 cmd_read,
1285 cmd_replace,
1286 cmd_reval,
1287 cmd_scroll_down,
1288 cmd_scroll_up,
1289 cmd_search,
1290 cmd_search_back,
1291 cmd_search_next,
1292 cmd_search_prev,
1293 cmd_subst,
1294 cmd_substall,
1295 cmd_switch_column,
1296 cmd_swp,
1297 cmd_visual,
1298 cmd_width,
1299 cmd_write
1302 static enum handler_result
1303 do_command(struct fileshow_priv *data, enum command cmd)
1305 switch (cmd) {
1307 /* CURSOR MOVEMENT */
1309 case cmd_go_down:
1310 jump_relative(data, data->width);
1311 break;
1313 case cmd_go_up:
1314 jump_relative(data, -data->width);
1315 break;
1317 case cmd_go_left:
1318 if (data->column == FSC_HEX && data->digitpos > 0) {
1319 data->digitpos--;
1320 } else if (!jump_relative(data, -1) &&
1321 data->column == FSC_HEX)
1322 data->digitpos = 1;
1323 break;
1325 case cmd_go_right:
1326 if (data->column == FSC_HEX && data->digitpos < 1) {
1327 data->digitpos++;
1328 } else
1329 jump_relative(data, 1);
1330 break;
1332 case cmd_line_begin:
1333 if (!jump_relative(data, -pos_column(data, data->cursor.pos)))
1334 data->digitpos = 0;
1335 break;
1337 case cmd_line_end:
1338 if (!jump_relative(data, data->width -
1339 pos_column(data, data->cursor.pos) - 1))
1340 data->digitpos = 0;
1341 break;
1343 case cmd_file_end:
1344 if (hed_file_size(data->file)) {
1345 set_cursor(data, hed_file_size(data->file) - 1);
1346 data->digitpos = 0;
1347 break;
1349 /* else fall-through */
1351 case cmd_file_begin:
1352 set_cursor(data, 0);
1353 data->digitpos = 0;
1354 break;
1356 case cmd_page_down:
1357 slide_viewport(data, data->ssize - data->width);
1358 break;
1360 case cmd_page_up:
1361 slide_viewport(data, -data->ssize + data->width);
1362 break;
1364 case cmd_scroll_down:
1365 slide_viewport(data, data->width);
1366 break;
1368 case cmd_scroll_up:
1369 slide_viewport(data, -data->width);
1370 break;
1372 /* SIMPLE COMMANDS */
1374 case cmd_switch_column:
1375 data->column ^= 1;
1376 break;
1378 case cmd_visual:
1379 if (data->mode == MODE_VISUAL) {
1380 hed_put_cursor(&data->visual);
1381 data->mode = MODE_NORMAL;
1382 } else {
1383 data->mode = MODE_VISUAL;
1384 hed_dup_cursor(&data->cursor, &data->visual);
1386 break;
1388 case cmd_normal_mode:
1389 if (data->mode == MODE_VISUAL)
1390 hed_put_cursor(&data->visual);
1391 data->mode = MODE_NORMAL;
1392 break;
1394 case cmd_insert:
1395 if (data->mode == MODE_INSERT)
1396 data->mode = MODE_REPLACE;
1397 else
1398 data->mode = MODE_INSERT;
1399 break;
1401 case cmd_replace:
1402 data->mode = MODE_REPLACE;
1403 break;
1405 case cmd_delchar:
1406 erase_at_cursor(data, 1);
1407 break;
1409 case cmd_delchar_back:
1410 if (data->cursor.pos > 0) {
1411 jump_relative(data, -1);
1412 erase_at_cursor(data, 1);
1414 break;
1416 case cmd_backspace:
1417 return do_command(data, (cmd = data->mode == MODE_INSERT
1418 ? cmd_delchar_back : cmd_go_left));
1420 /* CLIPBOARD */
1422 case cmd_clip_yank:
1423 if (data->mode != MODE_VISUAL) {
1424 errmsg("No region selected");
1425 return EVH_SILENTHOLD;
1427 clip_yank(data);
1428 hed_put_cursor(&data->visual);
1429 data->mode = MODE_NORMAL;
1430 break;
1432 case cmd_clip_delete:
1433 if (data->mode != MODE_VISUAL) {
1434 errmsg("No region selected");
1435 return EVH_SILENTHOLD;
1437 clip_delete(data);
1438 hed_put_cursor(&data->visual);
1439 data->mode = MODE_NORMAL;
1440 break;
1442 case cmd_clip_paste_after:
1443 if (data->mode != MODE_VISUAL)
1444 hed_move_relative(&data->cursor, 1);
1445 /* fall through */
1446 case cmd_clip_paste:
1447 if (data->mode == MODE_VISUAL) {
1448 clip_swap(data);
1449 hed_put_cursor(&data->visual);
1450 data->mode = MODE_NORMAL;
1451 } else {
1452 clip_put(data);
1453 hed_move_relative(&data->cursor, -1);
1455 break;
1457 case cmd_clip_overwrite:
1458 clip_overwrite(data);
1459 if (data->mode == MODE_VISUAL) {
1460 hed_put_cursor(&data->visual);
1461 data->mode = MODE_NORMAL;
1463 break;
1465 /* INPUTLINE */
1467 case cmd_exmode:
1468 inputline_init(":", exmode, data);
1469 return EVH_SILENTHOLD;
1471 case cmd_goto:
1472 inputline_init("#", jump_offset, data);
1473 return EVH_SILENTHOLD;
1475 case cmd_search:
1476 data->last_search_dir = SDIR_FORWARD;
1477 inputline_init("/", expr_search, data);
1478 return EVH_SILENTHOLD;
1480 case cmd_search_back:
1481 data->last_search_dir = SDIR_BACK;
1482 inputline_init("?", expr_search, data);
1483 return EVH_SILENTHOLD;
1485 case cmd_search_prev:
1486 data->last_search_dir = -data->last_search_dir;
1487 /* fall through */
1488 case cmd_search_next:
1489 if (data->last_search)
1490 do_search(data);
1491 else
1492 errmsg("No previous search");
1494 if (cmd == cmd_search_prev)
1495 data->last_search_dir = -data->last_search_dir;
1496 break;
1498 /* EXMODE */
1500 case cmd_quit:
1501 exmode_cmd_quit(data);
1502 break;
1504 case cmd_exit:
1505 exmode_cmd_exit(data);
1506 break;
1508 case cmd_write:
1509 exmode_cmd_write(data);
1510 break;
1512 case cmd_read:
1513 exmode_cmd_read(data);
1514 break;
1516 case cmd_pipe:
1517 if (data-> mode == MODE_VISUAL)
1518 exmode_cmd_pipe(data);
1519 break;
1521 case cmd_subst:
1522 exmode_cmd_subst(data);
1523 break;
1525 case cmd_substall:
1526 exmode_cmd_Subst(data);
1527 break;
1529 case cmd_swp:
1530 exmode_cmd_swp(data);
1531 break;
1533 case cmd_ieval:
1534 exmode_cmd_ieval(data);
1535 break;
1537 case cmd_reval:
1538 exmode_cmd_reval(data);
1539 break;
1541 case cmd_peval:
1542 exmode_cmd_peval(data);
1543 break;
1545 case cmd_width:
1546 exmode_cmd_width(data);
1547 break;
1549 case cmd_endian:
1550 data->le ^= 1;
1551 break;
1553 /* PREFIX */
1555 case cmd_prefix:
1556 return EVH_XPREFIX;
1560 return EVH_HOLD;
1563 struct longcmd {
1564 const char *name;
1565 enum command cmd;
1568 static void
1569 exmode(struct inputline_data *inpline, void *hookdata)
1571 static const struct longcmd cmds[] = {
1572 { "!", cmd_pipe },
1573 { "endian", cmd_endian },
1574 { "exit", cmd_exit },
1575 { "ie", cmd_ieval },
1576 { "ieval", cmd_ieval },
1577 { "pe", cmd_peval },
1578 { "peval", cmd_peval },
1579 { "pipe", cmd_pipe },
1580 { "q", cmd_quit },
1581 { "quit", cmd_quit },
1582 { "r", cmd_read },
1583 { "read", cmd_read },
1584 { "re", cmd_reval },
1585 { "reval", cmd_reval },
1586 { "s", cmd_subst },
1587 { "S", cmd_substall },
1588 { "substall", cmd_substall },
1589 { "substitute", cmd_subst },
1590 { "swp", cmd_swp },
1591 { "w", cmd_write },
1592 { "width", cmd_width },
1593 { "write", cmd_write },
1594 { "x", cmd_exit },
1595 { NULL }
1598 struct fileshow_priv *data = hookdata;
1599 char *cmd = inpline->buf;
1600 int len = inpline->buf_len;
1601 const struct longcmd *p;
1603 /* Trim leading/trailing whitespaces */
1604 while (isspace(*cmd))
1605 cmd++, len--;
1606 while (isspace(cmd[len - 1]) && len > 0)
1607 len--;
1609 for (p = cmds; p->name; ++p)
1610 if (!strcmp(cmd, p->name)) {
1611 do_command(data, p->cmd);
1612 return;
1615 errmsg("Invalid command");
1618 struct shortcmd {
1619 int ch;
1620 enum command cmd;
1623 #define CTRL(ch) ((ch) - 'A' + 1)
1625 static const struct shortcmd commoncmds[] = {
1626 { '\e', cmd_normal_mode },
1627 { KEY_NPAGE, cmd_page_down },
1628 { CTRL('F'), cmd_page_down },
1629 { KEY_PPAGE, cmd_page_up },
1630 { CTRL('B'), cmd_page_up },
1631 { CTRL('E'), cmd_scroll_down },
1632 { CTRL('Y'), cmd_scroll_up },
1633 { KEY_HOME, cmd_file_begin },
1634 { KEY_END, cmd_file_end },
1635 { '\t', cmd_switch_column },
1636 { KEY_IC, cmd_insert },
1637 { KEY_DC, cmd_delchar },
1638 { KEY_BACKSPACE, cmd_backspace },
1639 { KEY_LEFT, cmd_go_left },
1640 { KEY_DOWN, cmd_go_down },
1641 { KEY_UP, cmd_go_up },
1642 { KEY_RIGHT, cmd_go_right },
1643 { KEY_F(2), cmd_write },
1644 { KEY_F(10), cmd_exit },
1645 { 0 }
1648 static const struct shortcmd cmds[] = {
1649 { 'G', cmd_file_end },
1650 { '0', cmd_line_begin },
1651 { '^', cmd_line_begin },
1652 { '$', cmd_line_end },
1653 { 'h', cmd_go_left },
1654 { 'j', cmd_go_down },
1655 { 'k', cmd_go_up },
1656 { 'l', cmd_go_right },
1657 { 'W', cmd_write },
1658 { 'Z', cmd_exit },
1659 { 'Q', cmd_quit },
1660 { ':', cmd_exmode },
1661 { '#', cmd_goto },
1662 { '/', cmd_search },
1663 { '?', cmd_search_back },
1664 { 'n', cmd_search_next },
1665 { 'N', cmd_search_prev },
1666 { 'x', cmd_delchar },
1667 { 'X', cmd_delchar_back },
1668 { 'v', cmd_visual },
1669 { 'y', cmd_clip_yank },
1670 { 'd', cmd_clip_delete },
1671 { 'p', cmd_clip_paste_after },
1672 { 'P', cmd_clip_paste },
1673 { 'o', cmd_clip_overwrite },
1674 { 'i', cmd_insert },
1675 { 'e', cmd_replace },
1676 { 'R', cmd_replace },
1677 { 'g', cmd_prefix },
1678 { 'r', cmd_prefix },
1679 { 'm', cmd_prefix },
1680 { '\'', cmd_prefix },
1681 { '\"', cmd_prefix },
1682 { 0 }
1685 static const struct shortcmd cmds_g[] = {
1686 { 'g', cmd_file_begin },
1687 { KEY_ENTER, cmd_goto },
1688 { 0 }
1691 static enum handler_result
1692 handle_shortcmd(struct fileshow_priv *data, int ch,
1693 const struct shortcmd *table)
1695 const struct shortcmd *p;
1697 for (p = table; p->ch; ++p)
1698 if (ch == p->ch)
1699 return do_command(data, p->cmd);
1701 return EVH_PASS;
1704 static enum handler_result
1705 fileshow_normal_in(struct fileshow_priv *data, int ch)
1707 enum handler_result ret;
1709 finish_insert(data);
1710 ret = handle_shortcmd(data, ch, commoncmds);
1711 if (ret == EVH_PASS)
1712 ret = handle_shortcmd(data, ch, cmds);
1713 return ret;
1716 static enum handler_result
1717 fileshow_normal_g_in(struct fileshow_priv *data, int ch)
1719 return handle_shortcmd(data, ch, cmds_g);
1722 static void
1723 hexdigit_in(struct fileshow_priv *data, int ch, int *byte)
1725 static const char hx[] = "0123456789abcdef";
1726 int nibble;
1728 nibble = strchr(hx, ch) - hx;
1729 assert(nibble < 16);
1730 if (data->digitpos == 0) {
1731 *byte = (data->curbyte & 0xf) | (nibble << 4);
1732 } else {
1733 *byte = (data->curbyte & 0xf0) | nibble;
1735 data->digitpos++;
1738 static enum handler_result
1739 fileshow_normal_r_in(struct fileshow_priv *data, int ch)
1741 enum handler_result res = EVH_PASS;
1742 int byte = -1;
1744 if (data->column == FSC_HEX) {
1745 if (isxdigit(ch)) {
1746 hexdigit_in(data, tolower(ch), &byte);
1747 if (data->digitpos > 1) {
1748 data->digitpos = 0;
1749 res = EVH_HOLD;
1750 } else {
1751 /* Keep @xprefix for the second digit. */
1752 res = EVH_CUSTOM_XPREFIX;
1755 } else if (ch == KEY_BACKSPACE && data->digitpos > 0) {
1756 data->digitpos--;
1757 res = EVH_CUSTOM_XPREFIX;
1758 } else if (ch == '\r' || ch == '\033') {
1759 /* Cancel the replace. */
1760 data->digitpos = 0;
1761 res = EVH_HOLD;
1762 } else {
1763 /* Persist until the user types something sensible. */
1764 res = EVH_CUSTOM_XPREFIX;
1767 } else {
1768 byte = ch;
1769 res = EVH_HOLD;
1772 if (byte >= 0) {
1773 if (data->mode == MODE_VISUAL) {
1774 hed_cursor_t base = HED_NULL_CURSOR;
1775 size_t len = visual_extents(data, &base);
1776 hed_file_set_bytes(data->file, &base, byte, len);
1777 hed_put_cursor(&base);
1778 } else {
1779 hed_file_set_byte(data->file, &data->cursor, byte);
1781 data->curbyte = byte;
1784 return res;
1787 static enum handler_result
1788 fileshow_normal_m_in(struct fileshow_priv *data, int ch)
1790 int mark = ch2mark(ch);
1791 if (mark >= 0)
1792 hed_dup2_cursor(&data->cursor,
1793 hed_file_mark(data->file, mark));
1794 else
1795 errmsg("Invalid mark");
1796 return EVH_SILENTHOLD;
1799 static enum handler_result
1800 fileshow_normal_Qq_in(struct fileshow_priv *data, int ch)
1802 int mark = ch2mark(ch);
1803 if (mark >= 0) {
1804 if (hed_file_has_mark(data->file, mark))
1805 set_cursor(data,
1806 hed_file_mark(data->file, mark)->pos);
1807 else
1808 errmsg("No such mark");
1809 } else {
1810 errmsg("Invalid mark");
1812 return EVH_HOLD;
1815 static enum handler_result
1816 fileshow_normal_QQ_in(struct fileshow_priv *data, int ch)
1818 char reg = ch2reg(ch);
1819 if (reg >= 0) {
1820 data->reg = reg;
1821 } else {
1822 errmsg("Invalid register");
1824 return EVH_SILENTHOLD;
1827 static enum handler_result
1828 fileshow_byte_in(struct fileshow_priv *data, int ch)
1830 enum handler_result ret = EVH_PASS;
1831 bool insert;
1832 int cursmove;
1833 int byte = -1;
1835 ret = handle_shortcmd(data, ch, commoncmds);
1836 if (ret != EVH_PASS)
1837 return ret;
1839 insert = (data->mode == MODE_INSERT && data->cursor.pos < OFF_MAX);
1841 if (insert && !hed_is_a_cursor(&data->insert)) {
1842 hed_file_insert_begin(data->file, &data->cursor, &data->insert);
1843 data->digitpos = 0;
1844 if (data->offset.pos == data->cursor.pos) {
1845 hed_dup2_cursor(&data->insert, &data->offset);
1846 update_idmark(data);
1850 cursmove = 0;
1851 if (data->column == FSC_HEX) {
1852 if (isxdigit(ch)) {
1853 if (insert && data->digitpos == 0)
1854 data->curbyte = 0;
1855 hexdigit_in(data, tolower(ch), &byte);
1856 if (data->digitpos > 1) {
1857 data->digitpos = 0;
1858 cursmove = 1;
1860 ret = EVH_HOLD;
1862 } else {
1863 cursmove = 1;
1864 byte = ch;
1865 ret = EVH_HOLD;
1868 if (byte < 0)
1869 return ret;
1871 if (insert) {
1872 if (data->column != FSC_HEX) {
1873 hed_file_insert_byte(data->file, &data->insert, byte);
1874 } else if (data->digitpos > 0) {
1875 hed_dup2_cursor(&data->insert, &data->cursor);
1876 hed_file_insert_byte(data->file, &data->insert, byte);
1877 } else {
1878 hed_file_set_byte(data->file, &data->cursor, byte);
1879 hed_move_relative(&data->cursor, 1);
1881 data->curbyte = byte;
1883 if (data->cursor.pos - data->ssize >= data->offset.pos)
1884 slide_viewport(data, data->width);
1885 else
1886 assert(data->cursor.pos >= data->offset.pos);
1887 } else {
1888 hed_file_set_byte(data->file, &data->cursor, byte);
1889 data->curbyte = byte;
1891 jump_relative(data, cursmove);
1894 return ret;
1897 static enum handler_result
1898 fileshow_handler(struct ui_component *comp, const struct ui_event *event)
1900 struct fileshow_priv *data = comp->data;
1901 enum handler_result res = EVH_PASS;
1903 switch (event->type) {
1904 case EV_REDRAW:
1905 fileshow_redraw(comp);
1906 break;
1907 case EV_KEYBOARD:
1909 int ch = event->v.keyboard.ch;
1911 if (data->mode == MODE_NORMAL || data->mode == MODE_VISUAL) {
1912 if (xprefix == 'g') {
1913 res = fileshow_normal_g_in(data, ch);
1914 } else if (xprefix == 'r') {
1915 res = fileshow_normal_r_in(data, ch);
1916 } else if (xprefix == 'm') {
1917 res = fileshow_normal_m_in(data, ch);
1918 } else if (xprefix == '\'') {
1919 res = fileshow_normal_Qq_in(data, ch);
1920 } else if (xprefix == '"') {
1921 res = fileshow_normal_QQ_in(data, ch);
1922 } else {
1923 res = fileshow_normal_in(data, ch);
1925 } else
1926 res = fileshow_byte_in(data, ch);
1928 if (res != EVH_PASS && res != EVH_SILENTHOLD)
1929 fileshow_redraw(comp);
1930 break;
1932 case EV_DONE:
1933 fileshow_done(comp);
1934 break;
1935 default:
1936 fprintf(stderr, "Bug: fileshow_handler E.T. %d\n", event->type);
1937 break;
1940 return res;