hexedit.c revision efb309d4cdb2f4c3926b0550d9dc1661c1e4a091
1/* hexedit.c - Hexadecimal file editor
2 *
3 * Copyright 2015 Rob Landley <rob@landley.net>
4 *
5 * No standard
6
7USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
8
9config HEXEDIT
10  bool "hexedit"
11  default y
12  help
13    usage: hexedit FILENAME
14
15    Hexadecimal file editor.
16
17    -r	Read only (display but don't edit)
18*/
19
20#define FOR_hexedit
21#include "toys.h"
22
23GLOBALS(
24  char *data;
25  long long len, base;
26  int numlen, undo, undolen;
27  unsigned height;
28)
29
30#define UNDO_LEN (sizeof(toybuf)/(sizeof(long long)+1))
31
32// Render all characters printable, using color to distinguish.
33static int draw_char(FILE *fp, wchar_t broiled)
34{
35  if (fp) {
36    if (broiled<32 || broiled>=127) {
37      if (broiled>127) {
38        tty_esc("2m");
39        broiled &= 127;
40      }
41      if (broiled<32 || broiled==127) {
42        tty_esc("7m");
43        if (broiled==127) broiled = 32;
44        else broiled += 64;
45      }
46      printf("%c", broiled);
47      tty_esc("0m");
48    } else printf("%c", broiled);
49  }
50
51  return 1;
52}
53
54static void draw_tail(void)
55{
56  tty_jump(0, TT.height);
57  tty_esc("K");
58
59  draw_rstr(*toys.optargs, 71, draw_char);
60}
61
62static void draw_line(long long yy)
63{
64  int x, xx = 16;
65
66  yy = (TT.base+yy)*16;
67  if (yy+xx>=TT.len) xx = TT.len-yy;
68
69  if (yy<TT.len) {
70    printf("\r%0*llX ", TT.numlen, yy);
71    for (x=0; x<xx; x++) printf(" %02X", TT.data[yy+x]);
72    printf("%*s", 2+3*(16-xx), "");
73    for (x=0; x<xx; x++) draw_char(stdout, TT.data[yy+x]);
74    printf("%*s", 16-xx, "");
75  }
76  tty_esc("K");
77}
78
79static void draw_page(void)
80{
81  int y;
82
83  tty_jump(0, 0);
84  for (y = 0; y<TT.height; y++) {
85    if (y) printf("\r\n");
86    draw_line(y);
87  }
88  draw_tail();
89}
90
91// side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only
92static void highlight(int xx, int yy, int side)
93{
94  char cc = TT.data[16*(TT.base+yy)+xx];
95  int i;
96
97  // Display cursor
98  tty_jump(2+TT.numlen+3*xx, yy);
99  tty_esc("0m");
100  if (side!=2) tty_esc("7m");
101  if (side>1) printf("%02X", cc);
102  else for (i=0; i<2;) {
103    if (side==i) tty_esc("32m");
104    printf("%X", (cc>>(4*(1&++i)))&15);
105  }
106  tty_esc("0m");
107  tty_jump(TT.numlen+17*3+xx, yy);
108  draw_char(stdout, cc);
109}
110
111void hexedit_main(void)
112{
113  long long pos = 0, y;
114  int x, i, side = 0, key, ro = toys.optflags&FLAG_r,
115      fd = xopen(*toys.optargs, ro ? O_RDONLY : O_RDWR);
116  char keybuf[16];
117
118  *keybuf = 0;
119
120  // Terminal setup
121  TT.height = 25;
122  terminal_size(0, &TT.height);
123  if (TT.height) TT.height--;
124  sigatexit(tty_sigreset);
125  tty_esc("0m");
126  tty_esc("?25l");
127  fflush(0);
128  set_terminal(1, 1, 0);
129
130  if ((TT.len = fdlength(fd))<0) error_exit("bad length");
131  if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX;
132  // count file length hex in digits, rounded up to multiple of 4
133  for (pos = TT.len, TT.numlen = 0; pos; pos >>= 4, TT.numlen++);
134  TT.numlen += (4-TT.numlen)&3;
135
136  TT.data = mmap(0, TT.len, PROT_READ|(PROT_WRITE*!ro), MAP_SHARED, fd, 0);
137  draw_page();
138
139  for (;;) {
140    // Scroll display if necessary
141    if (pos<0) pos = 0;
142    if (pos>TT.len) pos = TT.len-1;
143    x = pos&15;
144    y = pos/16;
145
146    i = 0;
147    while (y<TT.base) {
148      if (TT.base-y>(TT.height/2)) {
149        TT.base = y;
150        draw_page();
151      } else {
152        TT.base--;
153        i++;
154        tty_esc("1T");
155        tty_jump(0, 0);
156        draw_line(0);
157      }
158    }
159    while (y>=TT.base+TT.height) {
160      if (y-(TT.base+TT.height)>(TT.height/2)) {
161        TT.base = y-TT.height-1;
162        draw_page();
163      } else {
164        TT.base++;
165        i++;
166        tty_esc("1S");
167        tty_jump(0, TT.height-1);
168        draw_line(TT.height-1);
169      }
170    }
171    if (i) draw_tail();
172    y -= TT.base;
173
174    // Display cursor and flush output
175    highlight(x, y, ro ? 3 : side);
176    xflush();
177
178    // Wait for next key
179    key = scan_key(keybuf, -1);
180    // Exit for q, ctrl-c, ctrl-d, escape, or EOF
181    if (key==-1 || key==3 || key==4 || key==27 || key=='q') break;
182    highlight(x, y, 2);
183
184    // Hex digit?
185    if (key>='a' && key<='f') key-=32;
186    if (!ro && ((key>='0' && key<='9') || (key>='A' && key<='F'))) {
187      if (!side) {
188        long long *ll = (long long *)toybuf;
189
190        ll[TT.undo] = pos;
191        toybuf[(sizeof(long long)*UNDO_LEN)+TT.undo++] = TT.data[pos];
192        if (TT.undolen < UNDO_LEN) TT.undolen++;
193        TT.undo %= UNDO_LEN;
194      }
195
196      i = key - '0';
197      if (i>9) i -= 7;
198      TT.data[pos] &= 15<<(4*side);
199      TT.data[pos] |= i<<(4*!side);
200
201      if (++side==2) {
202        highlight(x, y, side);
203        side = 0;
204        ++pos;
205      }
206    } else side = 0;
207    if (key=='u') {
208      if (TT.undolen) {
209        long long *ll = (long long *)toybuf;
210
211        TT.undolen--;
212        if (!TT.undo) TT.undo = UNDO_LEN;
213        pos = ll[--TT.undo];
214        TT.data[pos] = toybuf[sizeof(long long)*UNDO_LEN+TT.undo];
215      }
216    }
217    if (key>256) {
218      key -= 256;
219
220      if (key==KEY_UP) pos -= 16;
221      else if (key==KEY_DOWN) pos += 16;
222      else if (key==KEY_RIGHT) {
223        if (x<15) pos++;
224      } else if (key==KEY_LEFT) {
225        if (x) pos--;
226      } else if (key==KEY_PGUP) pos -= 16*TT.height;
227      else if (key==KEY_PGDN) pos += 16*TT.height;
228      else if (key==KEY_HOME) pos = 0;
229      else if (key==KEY_END) pos = TT.len-1;
230    }
231  }
232  munmap(TT.data, TT.len);
233  close(fd);
234  tty_reset();
235}
236