123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- /* hexedit.c - Hexadecimal file editor
- *
- * Copyright 2015 Rob Landley <rob@landley.net>
- *
- * No standard.
- USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
- config HEXEDIT
- bool "hexedit"
- default y
- help
- usage: hexedit FILE
- Hexadecimal file editor/viewer. All changes are written to disk immediately.
- -r Read only (display but don't edit)
- Keys:
- Arrows Move left/right/up/down by one line/column
- PgUp/PgDn Move up/down by one page
- Home/End Start/end of line (start/end of file with ctrl)
- 0-9, a-f Change current half-byte to hexadecimal value
- ^J or : Jump (+/- for relative offset, otherwise absolute address)
- ^F or / Find string (^G/n: next, ^D/p: previous match)
- u Undo
- x Toggle bw/color display
- q/^C/^Q/Esc Quit
- */
- #define FOR_hexedit
- #include "toys.h"
- GLOBALS(
- char *data, *search, keybuf[16], input[80];
- long long len, base, pos;
- int numlen, undo, undolen, mode;
- unsigned rows, cols;
- )
- #define UNDO_LEN (sizeof(toybuf)/(sizeof(long long)+1))
- static void show_error(char *what)
- {
- printf("\e[%dH\e[41m\e[37m\e[K\e[1m%s\e[0m", TT.rows+1, what);
- xflush(1);
- msleep(500);
- }
- // TODO: support arrow keys, insertion, and scrolling (and reuse in vi)
- static int prompt(char *prompt, char *initial_value)
- {
- int yes = 0, key, len = strlen(initial_value);
- strcpy(TT.input, initial_value);
- while (1) {
- printf("\e[%dH\e[K\e[1m%s: \e[0m%s\e[?25h", TT.rows+1, prompt, TT.input);
- xflush(1);
- key = scan_key(TT.keybuf, -1);
- if (key < 0 || key == 27) break;
- if (key == '\r') {
- yes = len; // Hitting enter with no input counts as cancellation.
- break;
- }
- if (key == 0x7f && (len > 0)) TT.input[--len] = 0;
- else if (key == 'U'-'@') while (len > 0) TT.input[--len] = 0;
- else if (key >= ' ' && key < 0x7f && len < sizeof(TT.input))
- TT.input[len++] = key;
- }
- printf("\e[?25l");
- return yes;
- }
- // Render all characters printable, using color to distinguish.
- static void draw_char(int ch)
- {
- if (ch >= ' ' && ch < 0x7f) {
- putchar(ch);
- return;
- }
- if (TT.mode) {
- if (ch>127) {
- printf("\e[2m");
- ch &= 127;
- }
- if (ch<32 || ch==127) {
- printf("\e[7m");
- if (ch==127) ch = 32;
- else ch += 64;
- }
- xputc(ch);
- } else {
- if (ch < ' ') printf("\e[31m%c", ch + '@');
- else printf("\e[35m?");
- }
- printf("\e[0m");
- }
- static void draw_status(void)
- {
- char line[80];
- printf("\e[%dH\e[K", TT.rows+1);
- snprintf(line, sizeof(line), "\"%s\"%s, %#llx/%#llx", *toys.optargs,
- FLAG(r) ? " [readonly]" : "", TT.pos, TT.len);
- draw_trim(line, -1, TT.cols);
- }
- static void draw_byte(int byte)
- {
- if (byte) printf("%02x", byte);
- else printf("\e[2m00\e[0m");
- }
- static void draw_line(long long yy)
- {
- int x, xx = 16;
- yy = (TT.base+yy)*16;
- if (yy+xx>=TT.len) xx = TT.len-yy;
- if (yy<TT.len) {
- printf("\r\e[%dm%0*llx\e[0m ", 33*!TT.mode, TT.numlen, yy);
- for (x=0; x<xx; x++) {
- putchar(' ');
- draw_byte(TT.data[yy+x]);
- }
- printf("%*s", 2+3*(16-xx), "");
- for (x=0; x<xx; x++) draw_char(TT.data[yy+x]);
- printf("%*s", 16-xx, "");
- }
- printf("\e[K");
- }
- static void draw_page(void)
- {
- int y;
- for (y = 0; y<TT.rows; y++) {
- printf(y ? "\r\n" : "\e[H");
- draw_line(y);
- }
- draw_status();
- }
- // side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only
- static void highlight(int xx, int yy, int side)
- {
- char cc = TT.data[16*(TT.base+yy)+xx];
- int i;
- // Display cursor in hex area.
- printf("\e[%u;%uH\e[%dm", yy+1, TT.numlen+3*(xx+1), 7*(side!=2));
- if (side>1) draw_byte(cc);
- else for (i=0; i<2;) {
- if (side==i) printf("\e[32m");
- printf("%x", (cc>>(4*(1&++i)))&15);
- }
- // Display cursor in text area.
- printf("\e[7m\e[%u;%uH"+4*(side==2), yy+1, 1+TT.numlen+17*3+xx);
- draw_char(cc);
- }
- static void find_next(int pos)
- {
- char *p;
- p = memmem(TT.data+pos, TT.len-pos, TT.search, strlen(TT.search));
- if (p) TT.pos = p - TT.data;
- else show_error("No match!");
- }
- static void find_prev(int pos)
- {
- size_t len = strlen(TT.search);
- for (; pos >= 0; pos--) {
- if (!memcmp(TT.data+pos, TT.search, len)) {
- TT.pos = pos;
- return;
- }
- }
- show_error("No match!");
- }
- void hexedit_main(void)
- {
- long long y;
- int x, i, side = 0, key, fd;
- // Terminal setup
- TT.cols = 80;
- TT.rows = 24;
- terminal_size(&TT.cols, &TT.rows);
- if (TT.rows) TT.rows--;
- xsignal(SIGWINCH, generic_signal);
- sigatexit(tty_sigreset);
- dprintf(1, "\e[0m\e[?25l");
- xset_terminal(1, 1, 0, 0);
- if (access(*toys.optargs, W_OK)) toys.optflags |= FLAG_r;
- fd = xopen(*toys.optargs, FLAG(r) ? O_RDONLY : O_RDWR);
- if ((TT.len = fdlength(fd))<1) error_exit("bad length");
- if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX;
- // count file length hex in digits, rounded up to multiple of 4
- for (TT.pos = TT.len, TT.numlen = 0; TT.pos; TT.pos >>= 4, TT.numlen++);
- TT.numlen += (4-TT.numlen)&3;
- TT.data=xmmap(0, TT.len, PROT_READ|(PROT_WRITE*!FLAG(r)), MAP_SHARED, fd, 0);
- close(fd);
- draw_page();
- for (;;) {
- // Scroll display if necessary
- if (TT.pos<0) TT.pos = 0;
- if (TT.pos>=TT.len) TT.pos = TT.len-1;
- x = TT.pos&15;
- y = TT.pos/16;
- // scroll up
- while (y<TT.base) {
- if (TT.base-y>(TT.rows/2)) {
- TT.base = y;
- draw_page();
- } else {
- TT.base--;
- printf("\e[H\e[1L");
- draw_line(0);
- }
- }
- // scroll down
- while (y>=TT.base+TT.rows) {
- if (y-(TT.base+TT.rows)>(TT.rows/2)) {
- TT.base = y-TT.rows-1;
- draw_page();
- } else {
- TT.base++;
- printf("\e[H\e[1M\e[%uH", TT.rows);
- draw_line(TT.rows-1);
- }
- }
- draw_status();
- y -= TT.base;
- // Display cursor and flush output
- highlight(x, y, FLAG(r) ? 3 : side);
- xflush(1);
- // Wait for next key
- key = scan_key(TT.keybuf, -1);
- // Window resized?
- if (key == -3) {
- toys.signal = 0;
- terminal_size(&TT.cols, &TT.rows);
- if (TT.rows) TT.rows--;
- draw_page();
- continue;
- }
- if (key == 'x') {
- TT.mode = !TT.mode;
- printf("\e[0m");
- draw_page();
- continue;
- }
- // Various popular ways to quit...
- if (key==-1||key==('C'-'@')||key==('Q'-'@')||key==27||key=='q') break;
- highlight(x, y, 2);
- if (key == ('J'-'@') || key == ':' || key == '-' || key == '+') {
- // Jump (relative or absolute)
- char initial[2] = {}, *s = 0;
- long long val;
- if (key == '-' || key == '+') *initial = key;
- if (!prompt("Jump to", initial)) continue;
- val = estrtol(TT.input, &s, 0);
- if (!errno && s && !*s) {
- if (*TT.input == '-' || *TT.input == '+') TT.pos += val;
- else TT.pos = val;
- }
- continue;
- } else if (key == ('F'-'@') || key == '/') { // Find
- if (!prompt("Find", TT.search ? TT.search : "")) continue;
- // TODO: parse hex escapes in input, and record length to support \0
- free(TT.search);
- TT.search = xstrdup(TT.input);
- find_next(TT.pos);
- } else if (TT.search && (key == ('G'-'@') || key == 'n')) { // Find next
- if (TT.pos < TT.len) find_next(TT.pos+1);
- } else if (TT.search && (key == ('D'-'@') || key == 'p')) { // Find previous
- if (TT.pos > 0) find_prev(TT.pos-1);
- }
- // Remove cursor
- highlight(x, y, 2);
- // Hex digit?
- if (key>='a' && key<='f') key-=32;
- if (!FLAG(r) && ((key>='0' && key<='9') || (key>='A' && key<='F'))) {
- if (!side) {
- long long *ll = (long long *)toybuf;
- ll[TT.undo] = TT.pos;
- toybuf[(sizeof(long long)*UNDO_LEN)+TT.undo++] = TT.data[TT.pos];
- if (TT.undolen < UNDO_LEN) TT.undolen++;
- TT.undo %= UNDO_LEN;
- }
- i = key - '0';
- if (i>9) i -= 7;
- TT.data[TT.pos] &= 15<<(4*side);
- TT.data[TT.pos] |= i<<(4*!side);
- if (++side==2) {
- highlight(x, y, side);
- side = 0;
- ++TT.pos;
- }
- } else side = 0;
- if (key=='u') {
- if (TT.undolen) {
- long long *ll = (long long *)toybuf;
- TT.undolen--;
- if (!TT.undo) TT.undo = UNDO_LEN;
- TT.pos = ll[--TT.undo];
- TT.data[TT.pos] = toybuf[sizeof(long long)*UNDO_LEN+TT.undo];
- }
- }
- if (key>=256) {
- key -= 256;
- if (key==KEY_UP) TT.pos -= 16;
- else if (key==KEY_DOWN) TT.pos += 16;
- else if (key==KEY_RIGHT) {
- if (TT.pos<TT.len) TT.pos++;
- } else if (key==KEY_LEFT) {
- if (TT.pos>0) TT.pos--;
- } else if (key==KEY_PGUP) {
- TT.pos -= 16*TT.rows;
- if (TT.pos < 0) TT.pos = 0;
- TT.base = TT.pos/16;
- draw_page();
- } else if (key==KEY_PGDN) {
- TT.pos += 16*TT.rows;
- if (TT.pos > TT.len-1) TT.pos = TT.len-1;
- TT.base = TT.pos/16;
- draw_page();
- } else if (key==KEY_HOME) TT.pos &= ~0xf;
- else if (key==KEY_END) TT.pos |= 0xf;
- else if (key==(KEY_CTRL|KEY_HOME)) TT.pos = 0;
- else if (key==(KEY_CTRL|KEY_END)) TT.pos = TT.len-1;
- }
- }
- munmap(TT.data, TT.len);
- tty_reset();
- }
|