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.
44 #include <config/config.h>
45 #include <libhed/eval.h>
46 #include <libhed/file.h>
47 #include <term/term.h>
50 #include <ui/fileshow.h>
51 #include <ui/inputline.h>
52 #include <util/lists.h>
55 /* For now, we re-request the file fragment each time we redraw the thing.
56 * This might not be feasible in the future. */
58 /* Number of bytes to fetch at cursor position.
59 * Currently, at maximum 4 bytes are needed (for the s32/u32 display).
60 * If you need more in print_fileview_status, feel free to increase
61 * this constant and use the extra bytes.
63 #define CURSOR_LENGTH 4
65 /* TODO: Make the multiclipboard smarter - copy-on-write. */
66 /* TODO: Registers should work as in vim, esp. the unnamed (") register */
67 struct clipboard_register
{
71 static struct clipboard_register clipboard
[1 + 'z' - 'a' + 1 + 'Z' - 'A' + 1 + '9' - '0' + 1];
76 return ('A' <= ch
&& ch
<= 'Z') ? ch
- 'A' :
77 ('a' <= ch
&& ch
<= 'z') ? 26 + ch
- 'a' :
84 return ('0' <= ch
&& ch
<= '9') ? 52 + ch
- '0' :
89 static bool subst_all
;
92 struct fileshow_priv
{
93 struct hed_file
*file
;
94 hed_cursor_t offset
; /* Beginning of screen (file offset) */
96 int width
, height
; /* Width and height of the viewport */
97 ssize_t ssize
; /* Size (in bytes) of the viewport */
99 hed_cursor_t cursor
; /* Cursor position (file offset) */
100 hed_cursor_t visual
; /* Visual mark */
101 int digitpos
; /* Selected digit in the hexadecimal column. */
102 unsigned char curbyte
; /* The value of the current byte under the cursor. */
106 char idmark
; /* insertion/deletion mark at screen start */
108 struct expression
*last_search
;
109 enum { SDIR_FORWARD
= 1, SDIR_BACK
= -1 } last_search_dir
;
111 enum { FSC_HEX
, FSC_ASC
} column
;
116 int le
; /* non-zero if little endian view */
119 static const char idmarks
[] = {
120 '>', ' ', '<', /* insert start, normal, insert end */
121 ')', '|', '(', /* dtto after erase */
124 #define IDMARK_ERASE_IDX 3
126 static enum handler_result
fileshow_handler(struct ui_component
*, const struct ui_event
*);
127 static long eval_reg_cb(void *hookdata
, char reg
, off_t ofs
,
128 unsigned char *scramble
, size_t len
);
129 static long eval_mark_cb(void *hookdata
, char mark
,
130 unsigned char *scramble
, size_t len
);
132 enum ui_mode opt_defmode
= -1;
135 update_idmark(struct fileshow_priv
*data
)
139 /* marks can only happen at zero offset */
140 if (!data
->offset
.off
) {
141 struct hed_tree
*tree
= file_blocks(data
->file
);
143 idx
= !hed_block_is_inserted(data
->offset
.block
);
144 if (hed_block_is_after_erase(tree
, data
->offset
.block
))
145 idx
+= IDMARK_ERASE_IDX
;
146 if (hed_block_is_after_insert(tree
, data
->offset
.block
))
149 data
->idmark
= idmarks
[idx
];
153 pos_column(struct fileshow_priv
*data
, uoff_t pos
)
155 return pos
% data
->width
;
159 set_viewport(struct fileshow_priv
*data
, off_t pos
)
161 file_get_cursor(data
->file
, pos
, &data
->offset
);
165 /* Update screen offset to make cursor fit into the current viewport */
167 fixup_viewport(struct fileshow_priv
*data
)
169 off_t curline
= data
->cursor
.pos
- pos_column(data
, data
->cursor
.pos
);
171 if (curline
< data
->offset
.pos
)
172 set_viewport(data
, curline
);
173 else if (curline
- data
->ssize
>= data
->offset
.pos
)
174 set_viewport(data
, curline
- data
->ssize
+ data
->width
);
178 set_cursor(struct fileshow_priv
*data
, off_t pos
)
180 file_get_cursor(data
->file
, pos
, &data
->cursor
);
181 fixup_viewport(data
);
184 /* Update cursor position to fit into the current viewport */
186 fixup_cursor(struct fileshow_priv
*data
)
188 off_t curline
= data
->cursor
.pos
- pos_column(data
, data
->cursor
.pos
);
190 if (curline
< data
->offset
.pos
)
191 file_move_relative(&data
->cursor
,
192 data
->offset
.pos
- curline
);
193 else if (curline
>= data
->offset
.pos
+ data
->ssize
)
194 file_move_relative(&data
->cursor
,
195 data
->offset
.pos
+ data
->ssize
- data
->width
- curline
);
199 slide_viewport(struct fileshow_priv
*data
, off_t num
)
201 file_move_relative(&data
->offset
, num
);
206 /* Initialize the component. */
208 fileshow_init(struct hed_file
*file
)
210 struct fileshow_priv
*data
;
211 struct ui_component
*fileshow
;
214 data
= calloc(1, sizeof(struct fileshow_priv
));
215 if (!data
) goto nomem
;
217 data
->reg
= ch2reg('"');
218 data
->le
= arch_little_endian();
220 if ((int) opt_defmode
>= 0) {
221 data
->mode
= opt_defmode
;
223 char *defmode
= get_opt_str("default_mode", "normal");
224 if (!strcmp(defmode
, "normal")) {
225 data
->mode
= MODE_NORMAL
;
226 } else if (!strcmp(defmode
, "insert")) {
227 data
->mode
= MODE_INSERT
;
228 } else if (!strcmp(defmode
, "replace")) {
229 data
->mode
= MODE_REPLACE
;
231 data
->mode
= MODE_NORMAL
;
235 fileshow
= ui_register(fileshow_handler
, data
);
239 sched_exit(EX_OSERR
);
243 /* TODO: Better height calculation. */
244 data
->height
= term_get_max_y() - 2;
245 width
= (term_get_max_x() - /* ofs */ 8 - /* spaces */ 4);
247 if (!opt_spreadview
) {
248 width
= 1 << bitsize(width
);
249 width
= get_opt_int("bytes_per_line", width
);
252 data
->ssize
= data
->height
* data
->width
;
254 file_get_cursor(data
->file
, 0, &data
->cursor
);
255 file_dup_cursor(&data
->cursor
, &data
->offset
);
260 fileshow_done(struct ui_component
*comp
)
266 /* Check whether @pos is inside the visual block */
268 is_inside_visual(struct fileshow_priv
*data
, off_t pos
)
270 return (data
->cursor
.pos
<= pos
&& pos
<= data
->visual
.pos
) ||
271 (data
->visual
.pos
<= pos
&& pos
<= data
->cursor
.pos
);
274 /* Returns ZERO ON ERROR. */
276 visual_extents(struct fileshow_priv
*data
, hed_cursor_t
*base
)
278 hed_cursor_t
*basesrc
;
280 if (data
->visual
.pos
< data
->cursor
.pos
) {
281 ret
= data
->cursor
.pos
- data
->visual
.pos
+ 1;
282 basesrc
= &data
->visual
;
284 ret
= data
->visual
.pos
- data
->cursor
.pos
+ 1;
285 basesrc
= &data
->cursor
;
287 file_dup2_cursor(basesrc
, base
);
291 #define BYTES_PER_LONG (sizeof(unsigned long))
293 #define DIGITS_MAX(num) ((num + 9)/10)
295 /* The decadic logarithm of 2^8 is approx. 2.408239965311849584,
296 * so 3 digits must be always sufficient to represent the largest
297 * value that can be held in one byte. */
298 #define DIGITS_PER_BYTE 3
301 print_status_num(unsigned char *cursdata
, int nbytes
,
302 int little_endian
, int x
, int y
, int width
)
307 flags
= little_endian
? AEF_FORCELE
: AEF_FORCEBE
;
314 assert(x
&& cursdata
);
315 assert(nbytes
* 8 <= _FILE_OFFSET_BITS
);
317 num
= bytestr2off(cursdata
, nbytes
, flags
);
319 return term_printf(x
, y
, COLOR_STATUS
, flags
& AEF_SIGNED
322 nbytes
* 8, width
, (long long) num
);
326 print_fileview_status(struct fileshow_priv
*data
,
327 unsigned char *cursdata
, int ofslen
)
329 static char modechar
[] = { 'N', 'R', 'I', 'V' };
331 const int y
= data
->height
;
333 /* First, fill the status line with the status color */
334 term_set_bg_color(COLOR_STATUS
);
335 term_clear_line(data
->height
);
337 /* We print the filename first so that it gets overwritten by
338 * the "more important" stuff. The most important part of the
339 * filename tends to be at the end anyway. */
340 term_print_string(term_get_max_x() - 1 - strlen(data
->file
->name
),
341 y
, COLOR_STATUS
, data
->file
->name
);
343 pos
+= term_printf(0, y
, COLOR_STATUS
, "%0*llx: %03d ", ofslen
,
344 (unsigned long long) data
->cursor
.pos
, *cursdata
);
346 term_print_string(pos
, y
, COLOR_STATUS
, data
->le
? "LE: " : "BE: ");
349 pos
+= print_status_num(cursdata
, 2, data
->le
, pos
, y
, 6);
350 pos
+= print_status_num(cursdata
, -2, data
->le
, pos
, y
, 6);
351 pos
+= print_status_num(cursdata
, 4, data
->le
, pos
, y
, 11);
352 pos
+= print_status_num(cursdata
, -4, data
->le
, pos
, y
, 11);
354 term_print_char(pos
, y
, COLOR_MODE
,
355 file_is_modified(data
->file
) ? '*' : ' ');
356 term_print_char(pos
+ 1, y
, COLOR_MODE
, modechar
[data
->mode
]);
361 num_hex_width(uoff_t num
)
363 /* Offset of the leftmost non-zero bit. */
367 for (i
= sizeof(uoff_t
) * 8 / 2; i
> 0; i
/= 2) {
368 if (num
& (~(uoff_t
)0 << i
)) {
369 num
>>= i
; width
+= i
;
372 /* Round the width up to the hexadecimal places. */
373 return width
/ 4 + 1;
377 fileshow_redraw(struct ui_component
*comp
)
379 struct fileshow_priv
*data
= comp
->data
;
381 char idmark
; /* insertion/deletion marks */
385 int xindent
, xascpos
;
389 unsigned char cursdata
[CURSOR_LENGTH
];
391 if (file_read_begin(data
->file
, &data
->offset
)) {
392 /* TODO: Better error reporting. */
393 perror("file_read_begin");
397 /* Offset width is chosen to accomodate the whole file in its current
398 * size as well as the current screen. */
399 maxofs
= data
->offset
.pos
+ data
->ssize
- 1;
400 if (maxofs
< file_size(data
->file
))
401 maxofs
= file_size(data
->file
);
402 ofslen
= num_hex_width(maxofs
);
403 /* Always print at least 4 digits. */
406 xindent
= ofslen
+ 2;
407 xascpos
= xindent
+ data
->width
* 3 + 1;
409 file_dup_cursor(&data
->offset
, &pos
);
410 p
= hed_block_data(pos
.block
) + pos
.off
;
411 idmark
= data
->idmark
;
412 assert(pos
.pos
>= 0);
414 term_set_bg_color(COLOR_NEUTRAL
);
417 for (row
= 0; row
< data
->height
; ++row
) {
421 term_printf(0, row
, COLOR_OFFSET
, "%0*llx", ofslen
, pos
.pos
);
423 for (col
= 0; col
< data
->width
; ++col
) {
428 v
= (data
->mode
== MODE_VISUAL
&&
429 is_inside_visual(data
, pos
.pos
))
433 color
= COLOR_DATA_BASE
;
434 if (pos
.pos
>= file_size(data
->file
))
435 color
|= COLOR_DATA_OUTSIDE
;
437 term_color markcolor
= (idmark
== ' ')
438 ? (hed_block_is_dirty(pos
.block
)
442 term_print_char(xindent
+ col
* 3 - 1, row
,
443 markcolor
| v
| oldv
, idmark
);
445 asc
= hed_block_is_virtual(pos
.block
)
447 sprintf(hex
, "%02x", asc
);
448 asc
= isprint(asc
) ? asc
: asc
== '\0' ? '.' : ':';
450 if (pos
.pos
== data
->cursor
.pos
) {
452 cursor_x
= data
->column
== FSC_HEX
453 ? xindent
+ col
* 3 + data
->digitpos
455 color
|= COLOR_DATA_CURSOR
;
456 } else if (hed_block_is_dirty(pos
.block
))
460 term_print_string(xindent
+ col
* 3, row
,
462 if (color
>= COLOR_DATA_BASE
)
463 color
|= COLOR_DATA_ASCII
;
464 term_print_char(xascpos
+ col
, row
,
467 if (pos
.pos
>= OFF_MAX
) {
468 term_print_char(xindent
+ col
* 3 + 2, row
,
469 COLOR_MARK
| v
, '!');
473 ++pos
.pos
; ++pos
.off
;
474 if (pos
.off
>= hed_block_size(pos
.block
)) {
475 int idx
= !!hed_block_is_inserted(pos
.block
);
476 file_next_block(data
->file
, &pos
);
477 idx
-= !!hed_block_is_inserted(pos
.block
);
478 if (hed_block_is_after_erase(
479 file_blocks(data
->file
),
481 idx
+= IDMARK_ERASE_IDX
;
482 idmark
= idmarks
[idx
+ 1];
484 p
= hed_block_data(pos
.block
);
491 if (idmark
== '<' || idmark
== '(') {
492 term_print_char(xascpos
- 2, row
,
493 COLOR_MARK
| oldv
, idmark
);
496 term_print_char(xascpos
- 2, row
,
497 COLOR_NEUTRAL
| COLOR_VISUAL
, ' ');
500 file_put_cursor(&pos
);
502 assert(data
->cursor
.pos
>= 0);
504 memset(cursdata
, 0, sizeof(cursdata
));
505 file_cpin(data
->file
, cursdata
, sizeof(cursdata
), &data
->cursor
);
506 data
->curbyte
= cursdata
[0];
508 file_read_end(data
->file
);
510 print_fileview_status(data
, cursdata
, ofslen
);
511 term_unset_bg_color();
514 /* Jump to a position given by offset expression. */
516 jump_offset(struct inputline_data
*inpline
, void *hookdata
)
518 struct fileshow_priv
*data
= hookdata
;
519 struct expression
*expr
= expr_compile(inpline
->buf
);
524 errmsg("Invalid expression");
527 buf
= expr_eval(expr
, eval_reg_cb
, eval_mark_cb
, data
);
529 errmsg("Cannot evaluate expression");
533 pos
= bytestr2off(buf
, expr_len(expr
), expr_flags(expr
));
537 set_cursor(data
, pos
);
541 jump_relative(struct fileshow_priv
*data
, int num
)
543 /* Zero-length jump usually means an invalid jump out of bounds */
544 if (file_move_relative(&data
->cursor
, num
) == 0)
548 fixup_viewport(data
);
553 finish_insert(struct fileshow_priv
*data
)
555 if (is_a_cursor(&data
->insert
)) {
556 file_insert_end(data
->file
, &data
->insert
);
557 if (data
->column
== FSC_HEX
&& data
->digitpos
)
558 jump_relative(data
, 1);
563 erase_at_cursor(struct fileshow_priv
*data
, uoff_t len
)
565 file_erase_block(data
->file
, &data
->cursor
, len
);
566 if (!is_a_cursor(&data
->offset
)) {
567 file_dup2_cursor(&data
->cursor
, &data
->offset
);
568 file_move_relative(&data
->offset
,
569 -pos_column(data
, data
->offset
.pos
));
575 do_search(struct fileshow_priv
*data
)
580 file_dup_cursor(&data
->cursor
, &off
);
581 file_move_relative(&off
, data
->last_search_dir
);
582 res
= file_find_expr(data
->file
, &off
, data
->last_search_dir
,
583 data
->last_search
, eval_reg_cb
, data
);
585 set_cursor(data
, off
.pos
);
587 errmsg("No match found");
589 file_put_cursor(&off
);
593 /* Search for the given string. */
595 expr_search(struct inputline_data
*inpline
, void *hookdata
)
597 struct fileshow_priv
*data
= hookdata
;
599 if (data
->last_search
)
600 expr_free(data
->last_search
);
601 data
->last_search
= expr_compile(inpline
->buf
);
602 if (!data
->last_search
) {
603 errmsg("Invalid expression");
610 /* Search and substitute the given string. */
612 expr_subst(struct inputline_data
*inpline
, void *hookdata
)
614 struct fileshow_priv
*data
= hookdata
;
615 struct expression
*subst
;
618 subst
= expr_compile(inpline
->buf
);
620 errmsg("Invalid expression");
624 data
->last_search_dir
= SDIR_FORWARD
;
625 while (do_search(data
) != FINDOFF_NO_MATCH
) {
626 buf
= expr_eval(subst
, eval_reg_cb
, eval_mark_cb
, data
);
628 errmsg("Cannot evaluate expression");
631 /* Cursor at the match */
632 erase_at_cursor(data
, expr_len(data
->last_search
));
633 file_insert_once(data
->file
, &data
->cursor
,
634 buf
, expr_len(subst
));
642 expr_subst1(struct inputline_data
*inpline
, void *hookdata
)
644 struct fileshow_priv
*data
= hookdata
;
646 if (data
->last_search
)
647 expr_free(data
->last_search
);
648 data
->last_search
= expr_compile(inpline
->buf
);
649 if (!data
->last_search
) {
650 errmsg("Invalid expression");
653 inputline_init("s/.../", expr_subst
, data
);
657 clip_yank(struct fileshow_priv
*data
)
659 hed_cursor_t base
= HED_NULL_CURSOR
;
660 size_t len
= visual_extents(data
, &base
);
662 file_put_cursor(&base
);
663 if (clipboard
[data
->reg
].data
)
664 free(clipboard
[data
->reg
].data
);
665 clipboard
[data
->reg
].data
=
666 file_fetch_block(data
->file
, base
.pos
, &len
);
667 clipboard
[data
->reg
].len
= len
;
670 /* Those functions are totally horrible kludges. There should be specialized
671 * file methods for those operations. */
674 clip_delete(struct fileshow_priv
*data
)
679 len
= visual_extents(data
, &data
->cursor
);
680 erase_at_cursor(data
, len
);
684 clip_put(struct fileshow_priv
*data
)
686 if (!clipboard
[data
->reg
].data
) {
687 errmsg("Register empty");
690 file_insert_once(data
->file
, &data
->cursor
,
691 clipboard
[data
->reg
].data
, clipboard
[data
->reg
].len
);
692 if (data
->offset
.pos
== data
->cursor
.pos
)
693 file_move_relative(&data
->offset
, -clipboard
[data
->reg
].len
);
697 clip_overwrite(struct fileshow_priv
*data
)
699 hed_cursor_t start
= HED_NULL_CURSOR
;
703 if (!clipboard
[data
->reg
].data
) {
704 errmsg("Register empty");
708 if (data
->mode
== MODE_VISUAL
) {
710 len
= visual_extents(data
, &start
);
713 file_dup_cursor(&data
->cursor
, &start
);
714 len
= clipboard
[data
->reg
].len
;
717 i
= min(len
, clipboard
[data
->reg
].len
);
718 file_set_block(data
->file
, &start
, clipboard
[data
->reg
].data
, i
);
719 file_set_bytes(data
->file
, &start
, 0, len
- i
);
720 file_put_cursor(&start
);
724 clip_swap(struct fileshow_priv
*data
)
726 struct clipboard_register orig
, new;
728 new = clipboard
[data
->reg
];
729 clipboard
[data
->reg
].data
= NULL
;
731 orig
= clipboard
[data
->reg
];
732 clipboard
[data
->reg
] = new;
734 clipboard
[data
->reg
] = orig
;
739 set_width(struct inputline_data
*inpline
, void *hookdata
)
741 struct fileshow_priv
*data
= hookdata
;
744 w
= strtol(inpline
->buf
, &l
, 0);
746 errmsg("Invalid width");
754 read_region(struct inputline_data
*inpline
, void *hookdata
)
756 struct fileshow_priv
*data
= hookdata
;
757 char *fname
= inpline
->buf
;
758 int flen
= inpline
->buf_len
;
762 /* Trim leading/trailing whitespaces */
763 while (isspace(*fname
))
765 while (isspace(fname
[flen
- 1]) && flen
> 0)
769 f
= fopen(fname
, "r");
771 errmsg(strerror(errno
));
775 if (data
->mode
== MODE_VISUAL
) {
776 size_t len
= visual_extents(data
, &data
->cursor
);
777 erase_at_cursor(data
, len
);
781 file_insert_begin(data
->file
, &data
->cursor
, &data
->insert
);
782 while ((ch
= fgetc(f
)) != EOF
)
783 file_insert_byte(data
->file
, &data
->insert
, ch
);
789 write_region(struct inputline_data
*inpline
, void *hookdata
)
791 struct fileshow_priv
*data
= hookdata
;
792 hed_cursor_t base
= HED_NULL_CURSOR
;
793 size_t len
= visual_extents(data
, &base
);
795 char *fname
= inpline
->buf
;
796 int flen
= inpline
->buf_len
;
799 file_put_cursor(&base
);
801 /* Trim leading/trailing whitespaces */
802 while (isspace(*fname
))
804 while (isspace(fname
[flen
- 1]) && flen
> 0)
809 /* FIXME: Don't do it at once */
810 buf
= file_fetch_block(data
->file
, base
.pos
, &len
);
811 f
= fopen(fname
, "w");
813 errmsg(strerror(errno
));
817 fwrite(buf
, len
, 1, f
);
823 /* Arbitrarily chosen... */
824 #define CHUNK_SIZE 1024
827 filter_select(struct fileshow_priv
*data
, off_t base
, ssize_t len
, int fd_in
, int fd_out
)
832 file_insert_begin(data
->file
, &data
->cursor
, &data
->insert
);
833 while (fd_out
|| fd_in
) {
836 if (fd_in
) FD_SET(fd_in
, &fdr
);
837 if (fd_out
) FD_SET(fd_out
, &fdw
);
839 ret
= select(fd_in
+fd_out
+1, &fdr
, &fdw
, NULL
, NULL
);
844 if (FD_ISSET(fd_in
, &fdr
)) {
845 unsigned char buf
[CHUNK_SIZE
];
846 ret
= read(fd_in
, buf
, CHUNK_SIZE
);
853 file_insert_block(data
->file
, &data
->insert
, buf
, ret
);
854 data
->cursor
.pos
+= ret
;
857 if (FD_ISSET(fd_out
, &fdw
)) {
858 size_t blen
= min(len
, CHUNK_SIZE
);
859 unsigned char *buf
= file_fetch_block(data
->file
, base
, &blen
);
860 if (blen
< CHUNK_SIZE
) {
865 ret
= write(fd_out
, buf
, blen
);
891 pipe_region(struct inputline_data
*inpline
, void *hookdata
)
893 struct fileshow_priv
*data
= hookdata
;
894 hed_cursor_t base
= HED_NULL_CURSOR
;
895 size_t len
= visual_extents(data
, &base
);
896 char *argv
[4] = { "/bin/sh", "-c", inpline
->buf
, NULL
};
897 int p_rd
[2], p_wr
[2];
900 file_put_cursor(&base
);
902 file_put_cursor(&data
->visual
);
903 data
->mode
= MODE_NORMAL
;
916 exit(execvp(argv
[0], argv
));
918 } else if (pid
> 0) {
924 set_cursor(data
, base
.pos
+ len
);
925 if (filter_select(data
, base
.pos
, len
, p_wr
[0], p_rd
[1]) < 0) {
926 errmsg("Filter failed");
929 ret
= waitpid(pid
, &status
, 0);
931 errmsg("Funny zombie");
934 if (WIFEXITED(status
)) {
935 if (WEXITSTATUS(status
))
936 errmsg("Filter returned error");
937 set_cursor(data
, base
.pos
);
938 erase_at_cursor(data
, len
);
941 errmsg("Curious error");
945 errmsg("Fork failed");
950 static hed_cursor_t xre
;
953 eval_mark_cb(void *hookdata
, char mark
,
954 unsigned char *scramble
, size_t len
)
956 struct fileshow_priv
*data
= hookdata
;
957 int m
= ch2mark(mark
);
959 if (file_mark_is_valid(data
->file
, m
)) {
960 off2bytestr(scramble
, len
,
961 file_mark(data
->file
, m
)->pos
);
966 off2bytestr(scramble
, len
, data
->cursor
.pos
);
970 memset(scramble
, 0, len
);
976 eval_reg_cb(void *hookdata
, char reg
, off_t ofs
,
977 unsigned char *scramble
, size_t len
)
979 struct fileshow_priv
*data
= hookdata
;
982 hed_cursor_t blkpos
= HED_NULL_CURSOR
;
989 file_dup_cursor(&data
->cursor
, &blkpos
);
992 file_get_cursor(data
->file
, 0, &blkpos
);
995 if (!is_a_cursor(&xre
))
997 file_dup_cursor(&xre
, &blkpos
);
1003 buf
= clipboard
[r
].data
; blen
= clipboard
[r
].len
;
1007 memset(scramble
, 0, -ofs
);
1018 memcpy(scramble
, buf
, blen
);
1020 memset(scramble
+ blen
, 0, len
- blen
);
1024 skip
= file_move_relative(&blkpos
, ofs
) - ofs
;
1026 if (ofs
>= 0 || skip
>= len
)
1028 memset(scramble
, 0, skip
);
1032 ret
= file_cpin(data
->file
, scramble
, len
, &blkpos
)
1034 file_put_cursor(&blkpos
);
1038 memset(scramble
, 0, len
);
1044 ieval_expr(struct inputline_data
*inpline
, void *hookdata
)
1046 struct fileshow_priv
*data
= hookdata
;
1047 struct expression
*expr
= expr_compile(inpline
->buf
);
1051 errmsg("Invalid expression");
1054 buf
= expr_eval(expr
, eval_reg_cb
, eval_mark_cb
, data
);
1056 errmsg("Cannot evaluate expression");
1060 file_insert_once(data
->file
, &data
->cursor
, buf
, expr_len(expr
));
1065 peval_expr(struct inputline_data
*inpline
, void *hookdata
)
1067 struct fileshow_priv
*data
= hookdata
;
1068 struct expression
*expr
= expr_compile(inpline
->buf
);
1069 static char *resbuf
;
1075 errmsg("Invalid expression");
1078 buf
= expr_eval(expr
, eval_reg_cb
, eval_mark_cb
, data
);
1080 errmsg("Cannot evaluate expression");
1084 len
= expr_len(expr
);
1085 p
= realloc(resbuf
, sizeof("Result: -") + 3 * len
);
1087 errmsg("Cannot allocate buffer");
1092 p
= stpcpy(resbuf
, "Result:");
1093 if (expr_flags(expr
) & AEF_NEGATIVE
)
1094 p
= stpcpy(p
, " -");
1095 for (i
= 0; i
< len
; ++i
)
1096 p
+= sprintf(p
, " %02x", buf
[i
]);
1103 reval_expr(struct inputline_data
*inpline
, void *hookdata
)
1105 struct fileshow_priv
*data
= hookdata
;
1106 struct expression
*expr
= expr_compile(inpline
->buf
);
1108 unsigned char *ebuf
;
1112 errmsg("Invalid expression");
1115 elen
= expr_len(expr
);
1116 len
= visual_extents(data
, &xre
);
1117 for (i
= 0; i
< len
; i
+= elen
) {
1119 ebuf
= expr_eval(expr
, eval_reg_cb
, eval_mark_cb
, data
);
1121 errmsg("Cannot evaluate expression");
1127 file_set_block(data
->file
, &xre
, ebuf
, rlen
);
1130 file_put_cursor(&xre
);
1133 if (data
->mode
!= MODE_VISUAL
)
1134 file_put_cursor(&data
->visual
);
1137 static void exmode(struct inputline_data
*inpline
, void *hookdata
);
1139 /* Evaluate an exmode command. */
1141 exmode_cmd_write(struct fileshow_priv
*data
)
1143 if (data
->mode
== MODE_VISUAL
) {
1144 /* Write just the visual */
1145 inputline_init("File: ", write_region
, data
);
1147 file_commit(data
->file
);
1150 exmode_cmd_read(struct fileshow_priv
*data
)
1152 inputline_init("File: ", read_region
, data
);
1155 exmode_cmd_pipe(struct fileshow_priv
*data
)
1157 inputline_init("Command: ", pipe_region
, data
);
1160 exmode_cmd_subst(struct fileshow_priv
*data
)
1163 inputline_init("s/", expr_subst1
, data
);
1166 exmode_cmd_Subst(struct fileshow_priv
*data
)
1169 inputline_init("S/", expr_subst1
, data
);
1172 exmode_cmd_swp(struct fileshow_priv
*data
)
1174 file_write_swap(data
->file
);
1177 exmode_cmd_ieval(struct fileshow_priv
*data
)
1179 inputline_init("Expression: ", ieval_expr
, data
);
1182 exmode_cmd_reval(struct fileshow_priv
*data
)
1184 if (data
->mode
!= MODE_VISUAL
)
1185 file_dup_cursor(&data
->cursor
, &data
->visual
);
1186 inputline_init("Expression: ", reval_expr
, data
);
1189 exmode_cmd_peval(struct fileshow_priv
*data
)
1191 inputline_init("Expression: ", peval_expr
, data
);
1194 exmode_cmd_width(struct fileshow_priv
*data
)
1196 inputline_init("Bytes per line: ", set_width
, data
);
1199 exmode_cmd_quit(struct fileshow_priv
*data
)
1204 exmode_cmd_exit(struct fileshow_priv
*data
)
1206 if (file_is_modified(data
->file
))
1207 file_commit(data
->file
);
1208 exmode_cmd_quit(data
);
1216 cmd_clip_paste_after
,
1259 static enum handler_result
1260 do_command(struct fileshow_priv
*data
, enum command cmd
)
1264 /* CURSOR MOVEMENT */
1267 jump_relative(data
, data
->width
);
1271 jump_relative(data
, -data
->width
);
1275 if (data
->column
== FSC_HEX
&& data
->digitpos
> 0) {
1277 } else if (!jump_relative(data
, -1) &&
1278 data
->column
== FSC_HEX
)
1283 if (data
->column
== FSC_HEX
&& data
->digitpos
< 1) {
1286 jump_relative(data
, 1);
1289 case cmd_line_begin
:
1290 if (!jump_relative(data
, -pos_column(data
, data
->cursor
.pos
)))
1295 if (!jump_relative(data
, data
->width
-
1296 pos_column(data
, data
->cursor
.pos
) - 1))
1301 if (file_size(data
->file
)) {
1302 set_cursor(data
, file_size(data
->file
) - 1);
1306 /* else fall-through */
1308 case cmd_file_begin
:
1309 set_cursor(data
, 0);
1314 slide_viewport(data
, data
->ssize
- data
->width
);
1318 slide_viewport(data
, -data
->ssize
+ data
->width
);
1321 case cmd_scroll_down
:
1322 slide_viewport(data
, data
->width
);
1326 slide_viewport(data
, -data
->width
);
1329 /* SIMPLE COMMANDS */
1331 case cmd_switch_column
:
1336 if (data
->mode
== MODE_VISUAL
) {
1337 file_put_cursor(&data
->visual
);
1338 data
->mode
= MODE_NORMAL
;
1340 data
->mode
= MODE_VISUAL
;
1341 file_dup_cursor(&data
->cursor
, &data
->visual
);
1345 case cmd_normal_mode
:
1346 if (data
->mode
== MODE_VISUAL
)
1347 file_put_cursor(&data
->visual
);
1348 data
->mode
= MODE_NORMAL
;
1352 if (data
->mode
== MODE_INSERT
)
1353 data
->mode
= MODE_REPLACE
;
1355 data
->mode
= MODE_INSERT
;
1359 data
->mode
= MODE_REPLACE
;
1363 erase_at_cursor(data
, 1);
1366 case cmd_delchar_back
:
1367 if (data
->cursor
.pos
> 0) {
1368 jump_relative(data
, -1);
1369 erase_at_cursor(data
, 1);
1374 return do_command(data
, (cmd
= data
->mode
== MODE_INSERT
1375 ? cmd_delchar_back
: cmd_go_left
));
1380 if (data
->mode
!= MODE_VISUAL
) {
1381 errmsg("No region selected");
1382 return EVH_SILENTHOLD
;
1385 file_put_cursor(&data
->visual
);
1386 data
->mode
= MODE_NORMAL
;
1389 case cmd_clip_delete
:
1390 if (data
->mode
!= MODE_VISUAL
) {
1391 errmsg("No region selected");
1392 return EVH_SILENTHOLD
;
1395 file_put_cursor(&data
->visual
);
1396 data
->mode
= MODE_NORMAL
;
1399 case cmd_clip_paste_after
:
1400 if (data
->mode
!= MODE_VISUAL
)
1401 file_move_relative(&data
->cursor
, 1);
1403 case cmd_clip_paste
:
1404 if (data
->mode
== MODE_VISUAL
) {
1406 file_put_cursor(&data
->visual
);
1407 data
->mode
= MODE_NORMAL
;
1410 file_move_relative(&data
->cursor
, -1);
1414 case cmd_clip_overwrite
:
1415 clip_overwrite(data
);
1416 if (data
->mode
== MODE_VISUAL
) {
1417 file_put_cursor(&data
->visual
);
1418 data
->mode
= MODE_NORMAL
;
1425 inputline_init(":", exmode
, data
);
1426 return EVH_SILENTHOLD
;
1429 inputline_init("#", jump_offset
, data
);
1430 return EVH_SILENTHOLD
;
1433 data
->last_search_dir
= SDIR_FORWARD
;
1434 inputline_init("/", expr_search
, data
);
1435 return EVH_SILENTHOLD
;
1437 case cmd_search_back
:
1438 data
->last_search_dir
= SDIR_BACK
;
1439 inputline_init("?", expr_search
, data
);
1440 return EVH_SILENTHOLD
;
1442 case cmd_search_prev
:
1443 data
->last_search_dir
= -data
->last_search_dir
;
1445 case cmd_search_next
:
1446 if (data
->last_search
)
1449 errmsg("No previous search");
1451 if (cmd
== cmd_search_prev
)
1452 data
->last_search_dir
= -data
->last_search_dir
;
1458 exmode_cmd_quit(data
);
1462 exmode_cmd_exit(data
);
1466 exmode_cmd_write(data
);
1470 exmode_cmd_read(data
);
1474 if (data
-> mode
== MODE_VISUAL
)
1475 exmode_cmd_pipe(data
);
1479 exmode_cmd_subst(data
);
1483 exmode_cmd_Subst(data
);
1487 exmode_cmd_swp(data
);
1491 exmode_cmd_ieval(data
);
1495 exmode_cmd_reval(data
);
1499 exmode_cmd_peval(data
);
1503 exmode_cmd_width(data
);
1526 exmode(struct inputline_data
*inpline
, void *hookdata
)
1528 static const struct longcmd cmds
[] = {
1530 { "endian", cmd_endian
},
1531 { "exit", cmd_exit
},
1532 { "ie", cmd_ieval
},
1533 { "ieval", cmd_ieval
},
1534 { "pe", cmd_peval
},
1535 { "peval", cmd_peval
},
1536 { "pipe", cmd_pipe
},
1538 { "quit", cmd_quit
},
1540 { "read", cmd_read
},
1541 { "re", cmd_reval
},
1542 { "reval", cmd_reval
},
1544 { "S", cmd_substall
},
1545 { "substall", cmd_substall
},
1546 { "substitute", cmd_subst
},
1549 { "width", cmd_width
},
1550 { "write", cmd_write
},
1555 struct fileshow_priv
*data
= hookdata
;
1556 char *cmd
= inpline
->buf
;
1557 int len
= inpline
->buf_len
;
1558 const struct longcmd
*p
;
1560 /* Trim leading/trailing whitespaces */
1561 while (isspace(*cmd
))
1563 while (isspace(cmd
[len
- 1]) && len
> 0)
1566 for (p
= cmds
; p
->name
; ++p
)
1567 if (!strcmp(cmd
, p
->name
)) {
1568 do_command(data
, p
->cmd
);
1572 errmsg("Invalid command");
1580 #define CTRL(ch) ((ch) - 'A' + 1)
1582 static const struct shortcmd commoncmds
[] = {
1583 { '\e', cmd_normal_mode
},
1584 { KEY_NPAGE
, cmd_page_down
},
1585 { CTRL('F'), cmd_page_down
},
1586 { KEY_PPAGE
, cmd_page_up
},
1587 { CTRL('B'), cmd_page_up
},
1588 { CTRL('E'), cmd_scroll_down
},
1589 { CTRL('Y'), cmd_scroll_up
},
1590 { KEY_HOME
, cmd_file_begin
},
1591 { KEY_END
, cmd_file_end
},
1592 { '\t', cmd_switch_column
},
1593 { KEY_IC
, cmd_insert
},
1594 { KEY_DC
, cmd_delchar
},
1595 { KEY_BACKSPACE
, cmd_backspace
},
1596 { KEY_LEFT
, cmd_go_left
},
1597 { KEY_DOWN
, cmd_go_down
},
1598 { KEY_UP
, cmd_go_up
},
1599 { KEY_RIGHT
, cmd_go_right
},
1600 { KEY_F(2), cmd_write
},
1601 { KEY_F(10), cmd_exit
},
1605 static const struct shortcmd cmds
[] = {
1606 { 'G', cmd_file_end
},
1607 { '0', cmd_line_begin
},
1608 { '^', cmd_line_begin
},
1609 { '$', cmd_line_end
},
1610 { 'h', cmd_go_left
},
1611 { 'j', cmd_go_down
},
1613 { 'l', cmd_go_right
},
1617 { ':', cmd_exmode
},
1619 { '/', cmd_search
},
1620 { '?', cmd_search_back
},
1621 { 'n', cmd_search_next
},
1622 { 'N', cmd_search_prev
},
1623 { 'x', cmd_delchar
},
1624 { 'X', cmd_delchar_back
},
1625 { 'v', cmd_visual
},
1626 { 'y', cmd_clip_yank
},
1627 { 'd', cmd_clip_delete
},
1628 { 'p', cmd_clip_paste_after
},
1629 { 'P', cmd_clip_paste
},
1630 { 'o', cmd_clip_overwrite
},
1631 { 'i', cmd_insert
},
1632 { 'e', cmd_replace
},
1633 { 'R', cmd_replace
},
1634 { 'g', cmd_prefix
},
1635 { 'r', cmd_prefix
},
1636 { 'm', cmd_prefix
},
1637 { '\'', cmd_prefix
},
1638 { '\"', cmd_prefix
},
1642 static const struct shortcmd cmds_g
[] = {
1643 { 'g', cmd_file_begin
},
1644 { KEY_ENTER
, cmd_goto
},
1648 static enum handler_result
1649 handle_shortcmd(struct fileshow_priv
*data
, int ch
,
1650 const struct shortcmd
*table
)
1652 const struct shortcmd
*p
;
1654 for (p
= table
; p
->ch
; ++p
)
1656 return do_command(data
, p
->cmd
);
1661 static enum handler_result
1662 fileshow_normal_in(struct fileshow_priv
*data
, int ch
)
1664 enum handler_result ret
;
1666 finish_insert(data
);
1667 ret
= handle_shortcmd(data
, ch
, commoncmds
);
1668 if (ret
== EVH_PASS
)
1669 ret
= handle_shortcmd(data
, ch
, cmds
);
1673 static enum handler_result
1674 fileshow_normal_g_in(struct fileshow_priv
*data
, int ch
)
1676 return handle_shortcmd(data
, ch
, cmds_g
);
1680 hexdigit_in(struct fileshow_priv
*data
, int ch
, int *byte
)
1682 static const char hx
[] = "0123456789abcdef";
1685 nibble
= strchr(hx
, ch
) - hx
;
1686 assert(nibble
< 16);
1687 if (data
->digitpos
== 0) {
1688 *byte
= (data
->curbyte
& 0xf) | (nibble
<< 4);
1690 *byte
= (data
->curbyte
& 0xf0) | nibble
;
1695 static enum handler_result
1696 fileshow_normal_r_in(struct fileshow_priv
*data
, int ch
)
1698 enum handler_result res
= EVH_PASS
;
1701 if (data
->column
== FSC_HEX
) {
1703 hexdigit_in(data
, tolower(ch
), &byte
);
1704 if (data
->digitpos
> 1) {
1708 /* Keep @xprefix for the second digit. */
1709 res
= EVH_CUSTOM_XPREFIX
;
1712 } else if (ch
== KEY_BACKSPACE
&& data
->digitpos
> 0) {
1714 res
= EVH_CUSTOM_XPREFIX
;
1715 } else if (ch
== '\r' || ch
== '\033') {
1716 /* Cancel the replace. */
1720 /* Persist until the user types something sensible. */
1721 res
= EVH_CUSTOM_XPREFIX
;
1730 if (data
->mode
== MODE_VISUAL
) {
1731 hed_cursor_t base
= HED_NULL_CURSOR
;
1732 size_t len
= visual_extents(data
, &base
);
1733 file_set_bytes(data
->file
, &base
, byte
, len
);
1734 file_put_cursor(&base
);
1736 file_set_byte(data
->file
, &data
->cursor
, byte
);
1738 data
->curbyte
= byte
;
1744 static enum handler_result
1745 fileshow_normal_m_in(struct fileshow_priv
*data
, int ch
)
1747 int mark
= ch2mark(ch
);
1749 file_dup2_cursor(&data
->cursor
, file_mark(data
->file
, mark
));
1751 errmsg("Invalid mark");
1752 return EVH_SILENTHOLD
;
1755 static enum handler_result
1756 fileshow_normal_Qq_in(struct fileshow_priv
*data
, int ch
)
1758 int mark
= ch2mark(ch
);
1760 if (file_mark_is_valid(data
->file
, mark
))
1761 set_cursor(data
, file_mark(data
->file
, mark
)->pos
);
1763 errmsg("No such mark");
1765 errmsg("Invalid mark");
1770 static enum handler_result
1771 fileshow_normal_QQ_in(struct fileshow_priv
*data
, int ch
)
1773 char reg
= ch2reg(ch
);
1777 errmsg("Invalid register");
1779 return EVH_SILENTHOLD
;
1782 static enum handler_result
1783 fileshow_byte_in(struct fileshow_priv
*data
, int ch
)
1785 enum handler_result ret
= EVH_PASS
;
1790 ret
= handle_shortcmd(data
, ch
, commoncmds
);
1791 if (ret
!= EVH_PASS
)
1794 insert
= (data
->mode
== MODE_INSERT
&& data
->cursor
.pos
< OFF_MAX
);
1796 if (insert
&& !is_a_cursor(&data
->insert
)) {
1797 file_insert_begin(data
->file
, &data
->cursor
, &data
->insert
);
1799 if (data
->offset
.pos
== data
->cursor
.pos
) {
1800 file_dup2_cursor(&data
->insert
, &data
->offset
);
1801 update_idmark(data
);
1806 if (data
->column
== FSC_HEX
) {
1808 if (insert
&& data
->digitpos
== 0)
1810 hexdigit_in(data
, tolower(ch
), &byte
);
1811 if (data
->digitpos
> 1) {
1827 if (data
->column
!= FSC_HEX
) {
1828 file_insert_byte(data
->file
, &data
->insert
, byte
);
1829 } else if (data
->digitpos
> 0) {
1830 file_dup2_cursor(&data
->insert
, &data
->cursor
);
1831 file_insert_byte(data
->file
, &data
->insert
, byte
);
1833 file_set_byte(data
->file
, &data
->cursor
, byte
);
1834 file_move_relative(&data
->cursor
, 1);
1836 data
->curbyte
= byte
;
1838 if (data
->cursor
.pos
- data
->ssize
>= data
->offset
.pos
)
1839 slide_viewport(data
, data
->width
);
1841 assert(data
->cursor
.pos
>= data
->offset
.pos
);
1843 file_set_byte(data
->file
, &data
->cursor
, byte
);
1844 data
->curbyte
= byte
;
1846 jump_relative(data
, cursmove
);
1852 static enum handler_result
1853 fileshow_handler(struct ui_component
*comp
, const struct ui_event
*event
)
1855 struct fileshow_priv
*data
= comp
->data
;
1856 enum handler_result res
= EVH_PASS
;
1858 switch (event
->type
) {
1860 fileshow_redraw(comp
);
1864 int ch
= event
->v
.keyboard
.ch
;
1866 if (data
->mode
== MODE_NORMAL
|| data
->mode
== MODE_VISUAL
) {
1867 if (xprefix
== 'g') {
1868 res
= fileshow_normal_g_in(data
, ch
);
1869 } else if (xprefix
== 'r') {
1870 res
= fileshow_normal_r_in(data
, ch
);
1871 } else if (xprefix
== 'm') {
1872 res
= fileshow_normal_m_in(data
, ch
);
1873 } else if (xprefix
== '\'') {
1874 res
= fileshow_normal_Qq_in(data
, ch
);
1875 } else if (xprefix
== '"') {
1876 res
= fileshow_normal_QQ_in(data
, ch
);
1878 res
= fileshow_normal_in(data
, ch
);
1881 res
= fileshow_byte_in(data
, ch
);
1883 if (res
!= EVH_PASS
&& res
!= EVH_SILENTHOLD
)
1884 fileshow_redraw(comp
);
1888 fileshow_done(comp
);
1891 fprintf(stderr
, "Bug: fileshow_handler E.T. %d\n", event
->type
);