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:
49 #include <config/config.h>
50 #include <libhed/expr.h>
51 #include <libhed/file.h>
52 #include <term/term.h>
55 #include <ui/fileshow.h>
56 #include <ui/inputline.h>
57 #include <util/lists.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
{
76 static struct clipboard_register clipboard
[1 + 'z' - 'a' + 1 + 'Z' - 'A' + 1 + '9' - '0' + 1];
81 return ('A' <= ch
&& ch
<= 'Z') ? ch
- 'A' :
82 ('a' <= ch
&& ch
<= 'z') ? 26 + ch
- 'a' :
89 return ('0' <= ch
&& ch
<= '9') ? 52 + ch
- '0' :
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. */
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
;
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;
141 update_idmark(struct fileshow_priv
*data
)
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
))
153 data
->idmark
= idmarks
[idx
];
157 pos_column(struct fileshow_priv
*data
, hed_uoff_t pos
)
159 return pos
% data
->width
;
163 set_viewport(struct fileshow_priv
*data
, off_t pos
)
165 hed_update_cursor(data
->file
, pos
, &data
->offset
);
169 /* Update screen offset to make cursor fit into the current viewport */
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
);
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 */
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
);
204 slide_viewport(struct fileshow_priv
*data
, off_t num
)
206 hed_move_relative(&data
->offset
, num
);
211 /* Initialize the component. */
213 fileshow_init(struct hed_file
*file
)
215 struct fileshow_priv
*data
;
216 struct ui_component
*fileshow
;
219 data
= calloc(1, sizeof(struct fileshow_priv
));
220 if (!data
) goto nomem
;
222 data
->reg
= ch2reg('"');
223 data
->le
= arch_little_endian();
225 if ((int) opt_defmode
>= 0) {
226 data
->mode
= opt_defmode
;
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
;
236 data
->mode
= MODE_NORMAL
;
240 fileshow
= ui_register(fileshow_handler
, data
);
244 sched_exit(EX_OSERR
);
248 /* TODO: Better height calculation. */
249 data
->height
= term_get_max_y() - 2;
250 width
= (term_get_max_x() - /* ofs */ 8 - /* spaces */ 4);
252 if (!opt_spreadview
) {
253 width
= 1 << bitsize(width
);
254 width
= get_opt_int("bytes_per_line", 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
);
265 fileshow_done(struct ui_component
*comp
)
271 /* Check whether @pos is inside the visual block */
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. */
281 visual_extents(struct fileshow_priv
*data
, hed_cursor_t
*base
)
283 hed_cursor_t
*basesrc
;
285 if (data
->visual
.pos
< data
->cursor
.pos
) {
286 ret
= data
->cursor
.pos
- data
->visual
.pos
+ 1;
287 basesrc
= &data
->visual
;
289 ret
= data
->visual
.pos
- data
->cursor
.pos
+ 1;
290 basesrc
= &data
->cursor
;
292 hed_dup2_cursor(basesrc
, base
);
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
306 print_status_num(unsigned char *cursdata
, int nbytes
,
307 int little_endian
, int x
, int y
, int width
)
312 flags
= little_endian
? HED_AEF_FORCELE
: HED_AEF_FORCEBE
;
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
327 nbytes
* 8, width
, (long long) num
);
331 print_fileview_status(struct fileshow_priv
*data
,
332 unsigned char *cursdata
, int ofslen
)
334 static char modechar
[] = { 'N', 'R', 'I', 'V' };
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: ");
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
]);
366 num_hex_width(hed_uoff_t num
)
368 /* Offset of the leftmost non-zero bit. */
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;
382 fileshow_redraw(struct ui_component
*comp
)
384 struct fileshow_priv
*data
= comp
->data
;
386 char idmark
; /* insertion/deletion marks */
390 int xindent
, xascpos
;
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");
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. */
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
);
421 p
= hed_cursor_data(&pos
);
423 for (row
= 0; row
< data
->height
; ++row
) {
427 term_printf(0, row
, COLOR_OFFSET
, "%0*llx", ofslen
, pos
.pos
);
429 for (col
= 0; col
< data
->width
; ++col
) {
434 v
= (data
->mode
== MODE_VISUAL
&&
435 is_inside_visual(data
, pos
.pos
))
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
)
448 term_print_char(xindent
+ col
* 3 - 1, row
,
449 markcolor
| v
| oldv
, idmark
);
451 if (pos
.pos
== data
->cursor
.pos
) {
453 cursor_x
= data
->column
== FSC_HEX
454 ? xindent
+ col
* 3 + data
->digitpos
456 color
|= COLOR_DATA_CURSOR
;
457 } else if (hed_block_is_dirty(pos
.block
))
459 if (hed_block_is_bad(pos
.block
))
463 asc
= p
? *p
++ : 0x00;
465 hed_block_is_bad(pos
.block
) ? "XX" : "%02x",
467 asc
= isprint(asc
) ? asc
: asc
== '\0' ? '.' : ':';
469 term_print_string(xindent
+ col
* 3, row
,
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
)
477 if (pos
.pos
>= OFF_MAX
) {
478 term_print_char(xindent
+ col
* 3 + 2, row
,
479 COLOR_MARK
| v
, '!');
483 int idx
= !!hed_block_is_inserted(pos
.block
);
484 hed_move_relative(&pos
, 1);
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");
497 p
= hed_cursor_data(&pos
);
504 if (idmark
== '<' || idmark
== '(') {
505 term_print_char(xascpos
- 2, row
,
506 COLOR_MARK
| oldv
, idmark
);
509 term_print_char(xascpos
- 2, row
,
510 COLOR_NEUTRAL
| COLOR_VISUAL
, ' ');
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. */
527 jump_offset(struct inputline_data
*inpline
, void *hookdata
)
529 struct fileshow_priv
*data
= hookdata
;
530 struct hed_expr
*expr
;
533 expr
= hed_expr_new(inpline
->buf
, eval_reg_cb
, eval_mark_cb
, data
);
535 errmsg("Invalid expression");
538 if (hed_expr_eval(expr
) & HED_AEF_ERROR
) {
539 errmsg("Cannot evaluate expression");
543 pos
= hed_expr2off(expr
);
547 set_cursor(data
, pos
);
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)
558 fixup_viewport(data
);
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);
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
));
585 do_search(struct fileshow_priv
*data
)
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
);
595 set_cursor(data
, data
->search
.pos
);
597 errmsg("No match found");
599 hed_put_cursor(&data
->search
);
600 return data
->search
.pos
;
603 /* Search for the given string. */
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");
621 /* Search and substitute the given string. */
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
);
630 errmsg("Invalid expression");
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");
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
));
647 hed_expr_free(subst
);
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");
663 inputline_init("s/.../", expr_subst
, data
);
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
);
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. */
685 clip_delete(struct fileshow_priv
*data
)
690 len
= visual_extents(data
, &data
->cursor
);
691 erase_at_cursor(data
, len
);
695 clip_put(struct fileshow_priv
*data
)
697 if (!clipboard
[data
->reg
].data
) {
698 errmsg("Register empty");
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
);
710 clip_overwrite(struct fileshow_priv
*data
)
712 hed_cursor_t start
= HED_NULL_CURSOR
;
716 if (!clipboard
[data
->reg
].data
) {
717 errmsg("Register empty");
721 if (data
->mode
== MODE_VISUAL
) {
723 len
= visual_extents(data
, &start
);
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
);
737 clip_swap(struct fileshow_priv
*data
)
739 struct clipboard_register orig
, new;
741 new = clipboard
[data
->reg
];
742 clipboard
[data
->reg
].data
= NULL
;
744 orig
= clipboard
[data
->reg
];
745 clipboard
[data
->reg
] = new;
747 clipboard
[data
->reg
] = orig
;
752 set_width(struct inputline_data
*inpline
, void *hookdata
)
754 struct fileshow_priv
*data
= hookdata
;
757 w
= strtol(inpline
->buf
, &l
, 0);
759 errmsg("Invalid width");
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
;
775 /* Trim leading/trailing whitespaces */
776 while (isspace(*fname
))
778 while (isspace(fname
[flen
- 1]) && flen
> 0)
782 f
= fopen(fname
, "r");
784 errmsg(strerror(errno
));
788 if (data
->mode
== MODE_VISUAL
) {
789 size_t len
= visual_extents(data
, &data
->cursor
);
790 erase_at_cursor(data
, len
);
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
);
802 write_to_file(struct hed_file
*in
, hed_cursor_t
*pos
, hed_uoff_t len
,
806 size_t blen
, written
;
809 if (! (blen
= hed_prepare_read(in
, pos
, len
)) )
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)
818 hed_move_relative(pos
, written
);
827 write_region(struct inputline_data
*inpline
, void *hookdata
)
829 struct fileshow_priv
*data
= hookdata
;
830 hed_cursor_t base
= HED_NULL_CURSOR
;
832 char *fname
= inpline
->buf
;
833 int flen
= inpline
->buf_len
;
836 /* Trim leading/trailing whitespaces */
837 while (isspace(*fname
))
839 while (isspace(fname
[flen
- 1]) && flen
> 0)
844 f
= fopen(fname
, "w");
846 errmsg(strerror(errno
));
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
);
856 /* Arbitrarily chosen... */
857 #define CHUNK_SIZE 1024
860 filter_select(struct fileshow_priv
*data
, hed_cursor_t
*base
,
861 ssize_t len
, int fd_in
, int fd_out
)
866 hed_file_insert_begin(data
->file
, &data
->cursor
, &data
->insert
);
867 while (fd_out
|| fd_in
) {
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
);
878 if (FD_ISSET(fd_in
, &fdr
)) {
879 unsigned char buf
[CHUNK_SIZE
];
880 ret
= read(fd_in
, buf
, CHUNK_SIZE
);
887 hed_file_insert_block(data
->file
, &data
->insert
,
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
) {
901 ret
= write(fd_out
, buf
, blen
);
907 hed_move_relative(base
, ret
- blen
);
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];
936 hed_put_cursor(&data
->visual
);
937 data
->mode
= MODE_NORMAL
;
950 exit(execvp(argv
[0], argv
));
952 } else if (pid
> 0) {
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
);
964 ret
= waitpid(pid
, &status
, 0);
966 errmsg("Funny zombie");
967 hed_put_cursor(&base
);
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
);
978 errmsg("Curious error");
979 hed_put_cursor(&base
);
983 errmsg("Fork failed");
984 hed_put_cursor(&base
);
987 hed_put_cursor(&base
);
990 static hed_cursor_t xre
;
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
);
999 if (hed_file_has_mark(data
->file
, m
)) {
1000 hed_off2bytestr(scramble
, len
,
1001 hed_file_mark(data
->file
, m
)->pos
);
1006 hed_off2bytestr(scramble
, len
, data
->cursor
.pos
);
1010 memset(scramble
, 0, len
);
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
;
1022 hed_cursor_t blkpos
;
1029 hed_dup_cursor(&data
->cursor
, &blkpos
);
1032 hed_get_cursor(data
->file
, 0, &blkpos
);
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
);
1047 buf
= clipboard
[r
].data
; blen
= clipboard
[r
].len
;
1051 memset(scramble
, 0, -ofs
);
1062 memcpy(scramble
, buf
, blen
);
1064 memset(scramble
+ blen
, 0, len
- blen
);
1068 skip
= hed_move_relative(&blkpos
, ofs
) - ofs
;
1070 if (ofs
>= 0 || skip
>= len
)
1072 memset(scramble
, 0, skip
);
1076 if (hed_file_cpin(data
->file
, scramble
, len
, &blkpos
))
1077 ret
|= HED_AEF_ERROR
;
1078 hed_put_cursor(&blkpos
);
1082 memset(scramble
, 0, len
);
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
);
1095 errmsg("Invalid expression");
1098 if (hed_expr_eval(expr
) & HED_AEF_ERROR
) {
1099 errmsg("Cannot evaluate expression");
1100 hed_expr_free(expr
);
1103 hed_file_insert_once(data
->file
, &data
->cursor
,
1104 hed_expr_buf(expr
), hed_expr_len(expr
));
1105 hed_expr_free(expr
);
1109 peval_expr(struct inputline_data
*inpline
, void *hookdata
)
1111 struct fileshow_priv
*data
= hookdata
;
1112 struct hed_expr
*expr
;
1113 static char *resbuf
;
1118 expr
= hed_expr_new(inpline
->buf
, eval_reg_cb
, eval_mark_cb
, data
);
1120 errmsg("Invalid expression");
1123 if (hed_expr_eval(expr
) & HED_AEF_ERROR
) {
1124 errmsg("Cannot evaluate expression");
1125 hed_expr_free(expr
);
1128 len
= hed_expr_len(expr
);
1129 p
= realloc(resbuf
, sizeof("Result: -") + 3 * len
);
1131 errmsg("Cannot allocate buffer");
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
);
1147 reval_expr(struct inputline_data
*inpline
, void *hookdata
)
1149 struct fileshow_priv
*data
= hookdata
;
1150 struct hed_expr
*expr
;
1154 expr
= hed_expr_new(inpline
->buf
, eval_reg_cb
, eval_mark_cb
, data
);
1156 errmsg("Invalid expression");
1159 elen
= hed_expr_len(expr
);
1160 len
= visual_extents(data
, &xre
);
1161 for (i
= 0; i
< len
; i
+= elen
) {
1163 if (hed_expr_eval(expr
) & HED_AEF_ERROR
) {
1164 errmsg("Cannot evaluate expression");
1170 hed_file_set_block(data
->file
, &xre
, hed_expr_buf(expr
), rlen
);
1172 hed_expr_free(expr
);
1173 hed_put_cursor(&xre
);
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. */
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
);
1190 hed_file_commit(data
->file
);
1193 exmode_cmd_read(struct fileshow_priv
*data
)
1195 inputline_init("File: ", read_region
, data
);
1198 exmode_cmd_pipe(struct fileshow_priv
*data
)
1200 inputline_init("Command: ", pipe_region
, data
);
1203 exmode_cmd_subst(struct fileshow_priv
*data
)
1206 inputline_init("s/", expr_subst1
, data
);
1209 exmode_cmd_Subst(struct fileshow_priv
*data
)
1212 inputline_init("S/", expr_subst1
, data
);
1215 exmode_cmd_swp(struct fileshow_priv
*data
)
1217 hed_file_write_swap(data
->file
);
1220 exmode_cmd_ieval(struct fileshow_priv
*data
)
1222 inputline_init("Expression: ", ieval_expr
, data
);
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
);
1232 exmode_cmd_peval(struct fileshow_priv
*data
)
1234 inputline_init("Expression: ", peval_expr
, data
);
1237 exmode_cmd_width(struct fileshow_priv
*data
)
1239 inputline_init("Bytes per line: ", set_width
, data
);
1242 exmode_cmd_quit(struct fileshow_priv
*data
)
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
);
1259 cmd_clip_paste_after
,
1302 static enum handler_result
1303 do_command(struct fileshow_priv
*data
, enum command cmd
)
1307 /* CURSOR MOVEMENT */
1310 jump_relative(data
, data
->width
);
1314 jump_relative(data
, -data
->width
);
1318 if (data
->column
== FSC_HEX
&& data
->digitpos
> 0) {
1320 } else if (!jump_relative(data
, -1) &&
1321 data
->column
== FSC_HEX
)
1326 if (data
->column
== FSC_HEX
&& data
->digitpos
< 1) {
1329 jump_relative(data
, 1);
1332 case cmd_line_begin
:
1333 if (!jump_relative(data
, -pos_column(data
, data
->cursor
.pos
)))
1338 if (!jump_relative(data
, data
->width
-
1339 pos_column(data
, data
->cursor
.pos
) - 1))
1344 if (hed_file_size(data
->file
)) {
1345 set_cursor(data
, hed_file_size(data
->file
) - 1);
1349 /* else fall-through */
1351 case cmd_file_begin
:
1352 set_cursor(data
, 0);
1357 slide_viewport(data
, data
->ssize
- data
->width
);
1361 slide_viewport(data
, -data
->ssize
+ data
->width
);
1364 case cmd_scroll_down
:
1365 slide_viewport(data
, data
->width
);
1369 slide_viewport(data
, -data
->width
);
1372 /* SIMPLE COMMANDS */
1374 case cmd_switch_column
:
1379 if (data
->mode
== MODE_VISUAL
) {
1380 hed_put_cursor(&data
->visual
);
1381 data
->mode
= MODE_NORMAL
;
1383 data
->mode
= MODE_VISUAL
;
1384 hed_dup_cursor(&data
->cursor
, &data
->visual
);
1388 case cmd_normal_mode
:
1389 if (data
->mode
== MODE_VISUAL
)
1390 hed_put_cursor(&data
->visual
);
1391 data
->mode
= MODE_NORMAL
;
1395 if (data
->mode
== MODE_INSERT
)
1396 data
->mode
= MODE_REPLACE
;
1398 data
->mode
= MODE_INSERT
;
1402 data
->mode
= MODE_REPLACE
;
1406 erase_at_cursor(data
, 1);
1409 case cmd_delchar_back
:
1410 if (data
->cursor
.pos
> 0) {
1411 jump_relative(data
, -1);
1412 erase_at_cursor(data
, 1);
1417 return do_command(data
, (cmd
= data
->mode
== MODE_INSERT
1418 ? cmd_delchar_back
: cmd_go_left
));
1423 if (data
->mode
!= MODE_VISUAL
) {
1424 errmsg("No region selected");
1425 return EVH_SILENTHOLD
;
1428 hed_put_cursor(&data
->visual
);
1429 data
->mode
= MODE_NORMAL
;
1432 case cmd_clip_delete
:
1433 if (data
->mode
!= MODE_VISUAL
) {
1434 errmsg("No region selected");
1435 return EVH_SILENTHOLD
;
1438 hed_put_cursor(&data
->visual
);
1439 data
->mode
= MODE_NORMAL
;
1442 case cmd_clip_paste_after
:
1443 if (data
->mode
!= MODE_VISUAL
)
1444 hed_move_relative(&data
->cursor
, 1);
1446 case cmd_clip_paste
:
1447 if (data
->mode
== MODE_VISUAL
) {
1449 hed_put_cursor(&data
->visual
);
1450 data
->mode
= MODE_NORMAL
;
1453 hed_move_relative(&data
->cursor
, -1);
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
;
1468 inputline_init(":", exmode
, data
);
1469 return EVH_SILENTHOLD
;
1472 inputline_init("#", jump_offset
, data
);
1473 return EVH_SILENTHOLD
;
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
;
1488 case cmd_search_next
:
1489 if (data
->last_search
)
1492 errmsg("No previous search");
1494 if (cmd
== cmd_search_prev
)
1495 data
->last_search_dir
= -data
->last_search_dir
;
1501 exmode_cmd_quit(data
);
1505 exmode_cmd_exit(data
);
1509 exmode_cmd_write(data
);
1513 exmode_cmd_read(data
);
1517 if (data
-> mode
== MODE_VISUAL
)
1518 exmode_cmd_pipe(data
);
1522 exmode_cmd_subst(data
);
1526 exmode_cmd_Subst(data
);
1530 exmode_cmd_swp(data
);
1534 exmode_cmd_ieval(data
);
1538 exmode_cmd_reval(data
);
1542 exmode_cmd_peval(data
);
1546 exmode_cmd_width(data
);
1569 exmode(struct inputline_data
*inpline
, void *hookdata
)
1571 static const struct longcmd cmds
[] = {
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
},
1581 { "quit", cmd_quit
},
1583 { "read", cmd_read
},
1584 { "re", cmd_reval
},
1585 { "reval", cmd_reval
},
1587 { "S", cmd_substall
},
1588 { "substall", cmd_substall
},
1589 { "substitute", cmd_subst
},
1592 { "width", cmd_width
},
1593 { "write", cmd_write
},
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
))
1606 while (isspace(cmd
[len
- 1]) && len
> 0)
1609 for (p
= cmds
; p
->name
; ++p
)
1610 if (!strcmp(cmd
, p
->name
)) {
1611 do_command(data
, p
->cmd
);
1615 errmsg("Invalid command");
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
},
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
},
1656 { 'l', cmd_go_right
},
1660 { ':', cmd_exmode
},
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
},
1685 static const struct shortcmd cmds_g
[] = {
1686 { 'g', cmd_file_begin
},
1687 { KEY_ENTER
, cmd_goto
},
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
)
1699 return do_command(data
, p
->cmd
);
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
);
1716 static enum handler_result
1717 fileshow_normal_g_in(struct fileshow_priv
*data
, int ch
)
1719 return handle_shortcmd(data
, ch
, cmds_g
);
1723 hexdigit_in(struct fileshow_priv
*data
, int ch
, int *byte
)
1725 static const char hx
[] = "0123456789abcdef";
1728 nibble
= strchr(hx
, ch
) - hx
;
1729 assert(nibble
< 16);
1730 if (data
->digitpos
== 0) {
1731 *byte
= (data
->curbyte
& 0xf) | (nibble
<< 4);
1733 *byte
= (data
->curbyte
& 0xf0) | nibble
;
1738 static enum handler_result
1739 fileshow_normal_r_in(struct fileshow_priv
*data
, int ch
)
1741 enum handler_result res
= EVH_PASS
;
1744 if (data
->column
== FSC_HEX
) {
1746 hexdigit_in(data
, tolower(ch
), &byte
);
1747 if (data
->digitpos
> 1) {
1751 /* Keep @xprefix for the second digit. */
1752 res
= EVH_CUSTOM_XPREFIX
;
1755 } else if (ch
== KEY_BACKSPACE
&& data
->digitpos
> 0) {
1757 res
= EVH_CUSTOM_XPREFIX
;
1758 } else if (ch
== '\r' || ch
== '\033') {
1759 /* Cancel the replace. */
1763 /* Persist until the user types something sensible. */
1764 res
= EVH_CUSTOM_XPREFIX
;
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
);
1779 hed_file_set_byte(data
->file
, &data
->cursor
, byte
);
1781 data
->curbyte
= byte
;
1787 static enum handler_result
1788 fileshow_normal_m_in(struct fileshow_priv
*data
, int ch
)
1790 int mark
= ch2mark(ch
);
1792 hed_dup2_cursor(&data
->cursor
,
1793 hed_file_mark(data
->file
, mark
));
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
);
1804 if (hed_file_has_mark(data
->file
, mark
))
1806 hed_file_mark(data
->file
, mark
)->pos
);
1808 errmsg("No such mark");
1810 errmsg("Invalid mark");
1815 static enum handler_result
1816 fileshow_normal_QQ_in(struct fileshow_priv
*data
, int ch
)
1818 char reg
= ch2reg(ch
);
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
;
1835 ret
= handle_shortcmd(data
, ch
, commoncmds
);
1836 if (ret
!= EVH_PASS
)
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
);
1844 if (data
->offset
.pos
== data
->cursor
.pos
) {
1845 hed_dup2_cursor(&data
->insert
, &data
->offset
);
1846 update_idmark(data
);
1851 if (data
->column
== FSC_HEX
) {
1853 if (insert
&& data
->digitpos
== 0)
1855 hexdigit_in(data
, tolower(ch
), &byte
);
1856 if (data
->digitpos
> 1) {
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
);
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
);
1886 assert(data
->cursor
.pos
>= data
->offset
.pos
);
1888 hed_file_set_byte(data
->file
, &data
->cursor
, byte
);
1889 data
->curbyte
= byte
;
1891 jump_relative(data
, cursmove
);
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
) {
1905 fileshow_redraw(comp
);
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
);
1923 res
= fileshow_normal_in(data
, ch
);
1926 res
= fileshow_byte_in(data
, ch
);
1928 if (res
!= EVH_PASS
&& res
!= EVH_SILENTHOLD
)
1929 fileshow_redraw(comp
);
1933 fileshow_done(comp
);
1936 fprintf(stderr
, "Bug: fileshow_handler E.T. %d\n", event
->type
);