1 /* hexedit.c - Hexadecimal file editor
3 * Copyright 2015 Rob Landley <rob@landley.net>
7 USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
13 usage: hexedit FILENAME
15 Hexadecimal file editor.
17 -r Read only (display but don't edit)
26 int numlen, undo, undolen;
30 #define UNDO_LEN (sizeof(toybuf)/(sizeof(long long)+1))
32 // Render all characters printable, using color to distinguish.
33 static void draw_char(char broiled)
35 if (broiled<32 || broiled>=127) {
40 if (broiled<32 || broiled==127) {
42 if (broiled==127) broiled = 32;
45 printf("%c", broiled);
47 } else printf("%c", broiled);
50 static void draw_tail(void)
52 int i = 0, width = 0, w, len;
53 char *start = *toys.optargs, *end;
55 tty_jump(0, TT.height);
58 // First time, make sure we fit in 71 chars (advancing start as necessary).
59 // Second time, print from start to end, escaping nonprintable chars.
61 for (end = start; *end;) {
64 len = mbrtowc(&wc, end, 99, 0);
65 if (len<0 || wc<32 || (w = wcwidth(wc))<0) {
67 if (i) draw_char(*end);
68 } else if (i) fwrite(end, len, 1, stdout);
74 len = mbrtowc(&wc, start, 99, 0);
75 if (len<0 || wc<32 || (w = wcwidth(wc))<0) len = w = 1;
84 static void draw_line(long long yy)
89 if (yy+xx>=TT.len) xx = TT.len-yy;
92 printf("\r%0*llX ", TT.numlen, yy);
93 for (x=0; x<xx; x++) printf(" %02X", TT.data[yy+x]);
94 printf("%*s", 2+3*(16-xx), "");
95 for (x=0; x<xx; x++) draw_char(TT.data[yy+x]);
96 printf("%*s", 16-xx, "");
101 static void draw_page(void)
106 for (y = 0; y<TT.height; y++) {
107 if (y) printf("\r\n");
113 // side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only
114 static void highlight(int xx, int yy, int side)
116 char cc = TT.data[16*(TT.base+yy)+xx];
120 tty_jump(2+TT.numlen+3*xx, yy);
122 if (side!=2) tty_esc("7m");
123 if (side>1) printf("%02X", cc);
124 else for (i=0; i<2;) {
125 if (side==i) tty_esc("32m");
126 printf("%X", (cc>>(4*(1&++i)))&15);
129 tty_jump(TT.numlen+17*3+xx, yy);
133 void hexedit_main(void)
135 long long pos = 0, y;
136 int x, i, side = 0, key, ro = toys.optflags&FLAG_r,
137 fd = xopen(*toys.optargs, ro ? O_RDONLY : O_RDWR);
144 terminal_size(0, &TT.height);
145 if (TT.height) TT.height--;
146 sigatexit(tty_sigreset);
150 set_terminal(1, 1, 0);
152 if ((TT.len = fdlength(fd))<0) error_exit("bad length");
153 if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX;
154 // count file length hex in digits, rounded up to multiple of 4
155 for (pos = TT.len, TT.numlen = 0; pos; pos >>= 4, TT.numlen++);
156 TT.numlen += (4-TT.numlen)&3;
158 TT.data = mmap(0, TT.len, PROT_READ|(PROT_WRITE*!ro), MAP_SHARED, fd, 0);
162 // Scroll display if necessary
164 if (pos>TT.len) pos = TT.len-1;
170 if (TT.base-y>(TT.height/2)) {
181 while (y>=TT.base+TT.height) {
182 if (y-(TT.base+TT.height)>(TT.height/2)) {
183 TT.base = y-TT.height-1;
189 tty_jump(0, TT.height-1);
190 draw_line(TT.height-1);
196 // Display cursor and flush output
197 highlight(x, y, ro ? 3 : side);
201 key = scan_key(keybuf, 1);
202 // Exit for q, ctrl-c, ctrl-d, escape, or EOF
203 if (key==-1 || key==3 || key==4 || key==27 || key=='q') break;
207 if (key>='a' && key<='f') key-=32;
208 if (!ro && ((key>='0' && key<='9') || (key>='A' && key<='F'))) {
210 long long *ll = (long long *)toybuf;
213 toybuf[(sizeof(long long)*UNDO_LEN)+TT.undo++] = TT.data[pos];
214 if (TT.undolen < UNDO_LEN) TT.undolen++;
220 TT.data[pos] &= 15<<(4*side);
221 TT.data[pos] |= i<<(4*!side);
224 highlight(x, y, side);
231 long long *ll = (long long *)toybuf;
234 if (!TT.undo) TT.undo = UNDO_LEN;
236 TT.data[pos] = toybuf[sizeof(long long)*UNDO_LEN+TT.undo];
238 } else if (key==KEY_UP) pos -= 16;
239 else if (key==KEY_DOWN) pos += 16;
240 else if (key==KEY_RIGHT) {
242 } else if (key==KEY_LEFT) {
244 } else if (key==KEY_PGUP) pos -= 16*TT.height;
245 else if (key==KEY_PGDN) pos += 16*TT.height;
246 else if (key==KEY_HOME) pos = 0;
247 else if (key==KEY_END) pos = TT.len-1;
249 munmap(TT.data, TT.len);