Patch from Richard Boulton for large-file support. Works by using
[sgt/tweak] / main.c
CommitLineData
6e182d98 1/*
6e182d98 2 * TODO possibly after that:
3 *
6e182d98 4 * - Multiple buffers, multiple on-screen windows.
5 * + ^X^F to open new file
6 * + ^X^R to open new file RO
7 * + ^X b to switch buffers in a window
8 * + ^X o to switch windows
9 * + ^X 2 to split a window
10 * + ^X 1 to destroy all windows but this
11 * + ^X 0 to destroy this window
12 * + ^X ^ to enlarge this window by one line
13 * + width settings vary per buffer (aha, _that's_ why I wanted
14 * a buffer structure surrounding the raw B-tree)
15 * + hex-editor-style minibuffer for entering search terms,
16 * rather than the current rather crap one; in particular
17 * this enables pasting into the search string.
18 * + er, how exactly do we deal with the problem of saving over
d28a4799 19 * a file which we're maintaining references to in another
20 * buffer? The _current_ buffer can at least be sorted out by
21 * replacing it with a fresh tree containing a single
22 * file-data block, but other buffers are in trouble.
23 * * if we can rely on Unix fd semantics, this isn't too
24 * bad; we can just keep the fd open on the original file,
25 * and then the data stays around even after we rename(2)
26 * our new version over the top. Disk space usage gets
27 * silly after a few iterations, but it's better than
28 * nothing.
6e182d98 29 *
30 * - Undo!
31 * + this actually doesn't seem _too_ horrid. For a start, one
32 * simple approach would be to clone the entire buffer B-tree
33 * every time we perform an operation! That's actually not
34 * _too_ expensive, if we maintain a limit on the number of
35 * operations we may undo.
36 * + I had also thought of cloning the tree we insert for each
37 * buf_insert_data and cloning the one removed for each
38 * buf_delete_data (both must be cloned for an overwrite),
39 * but I'm not convinced that simply cloning the entire thing
40 * isn't a superior option.
d28a4799 41 * + this really starts to show up the distinction between a
42 * `buffer' and a bare tree. A buffer is something which has
43 * an undo chain attached; so, in particular, the cut buffer
44 * shouldn't be one. Sort that out.
6e182d98 45 *
6e182d98 46 * - In-place editing.
47 * + this is an extra option when running in Fix mode. It
48 * causes a change of semantics when saving: instead of
49 * constructing a new backup file and writing it over the old
50 * one, we simply seek within the original file and write out
51 * all the pieces that have changed.
52 * + Primarily useful for editing disk devices directly
d28a4799 53 * (yikes!), or other situations where you actually cannot
54 * create a fresh copy of the file and rename(2) it into
55 * place.
6e182d98 56 * + I had intended to suggest that in Fix mode this would be
57 * nice and easy, since every element of the buffer tree is
58 * either a literal block (needs writing) or a from-file
59 * block denoting the same file _in the same position_.
60 * However, this is not in fact the case because you can cut
61 * and paste, so it's not that easy.
62 * + So I'm forced to the conclusion that when operating in
63 * this mode, it becomes illegal to cut and paste from-file
64 * blocks: they must be loaded in full at some point.
65 * * Thinking ahead about multiple-buffer operation: it
66 * would be a bad idea to keep a from-file block
67 * referencing /dev/hda and paste it into another ordinary
68 * buffer. But _also_ it would be a bad idea to paste a
69 * from-file block referencing a file stored _on_ /dev/hda
70 * into the in-place buffer dealing with /dev/hda itself.
71 * * So I'm forced to another odd conclusion, which is that
72 * from-file blocks must be eliminated in _two_ places:
73 * when copying a cut buffer _from_ an in-place buffer,
74 * _and_ when pasting a cut buffer _into_ one.
75 */
76
11825bd4 77#include "tweak.h"
78
6e182d98 79#include <stdio.h>
80#include <stdlib.h>
81#include <string.h>
82#include <ctype.h>
83#include <assert.h>
84
85#if defined(unix) && !defined(GO32)
86#include <signal.h>
87#include <sys/ioctl.h>
88#include <termios.h>
89#elif defined(MSDOS)
90#include <dos.h>
91#include <process.h>
92#endif
93
6e182d98 94static void init(void);
95static void done(void);
96static void load_file (char *);
97
98char toprint[256]; /* LUT: printable versions of chars */
99char hex[256][3]; /* LUT: binary to hex, 1 byte */
100
101char message[80];
102
11825bd4 103char decstatus[] = "%s TWEAK "VER": %-18.18s %s posn=%-10"OFF"d size=%-10"OFF"d";
104char hexstatus[] = "%s TWEAK "VER": %-18.18s %s posn=0x%-8"OFF"X size=0x%-8"OFF"X";
6e182d98 105char *statfmt = hexstatus;
106
107char last_char;
108char *pname;
109char *filename = NULL;
110buffer *filedata, *cutbuffer = NULL;
111int fix_mode = FALSE;
112int look_mode = FALSE;
113int eager_mode = FALSE;
114int insert_mode = FALSE;
115int edit_type = 1; /* 1,2 are hex digits, 0=ascii */
116int finished = FALSE;
117int marking = FALSE;
118int modified = FALSE;
119int new_file = FALSE; /* shouldn't need initialisation -
120 * but let's not take chances :-) */
11825bd4 121fileoffset_t width = 16;
122fileoffset_t realoffset = 0, offset = 16;
6e182d98 123
124int ascii_enabled = TRUE;
125
11825bd4 126fileoffset_t file_size = 0, top_pos = 0, cur_pos = 0, mark_point = 0;
6e182d98 127
128int scrlines;
129
130/*
131 * Main program
132 */
133int main(int argc, char **argv) {
11825bd4 134 fileoffset_t newoffset = -1, newwidth = -1;
6e182d98 135
136 /*
137 * Parse command line arguments
138 */
139 pname = *argv; /* program name */
140 if (argc < 2) {
141 fprintf(stderr,
142 "usage: %s [-f] [-l] [-e] filename\n"
143 " or %s -D to write default tweak.rc to stdout\n",
144 pname, pname);
145 return 0;
146 }
147
148 while (--argc > 0) {
149 char c, *p = *++argv, *value;
150
151 if (*p == '-') {
152 p++;
153 while (*p) switch (c = *p++) {
154 case 'o': case 'O':
155 case 'w': case 'W':
156 /*
157 * these parameters require arguments
158 */
159 if (*p)
160 value = p, p = "";
161 else if (--argc)
162 value = *++argv;
163 else {
164 fprintf(stderr, "%s: option `-%c' requires an argument\n",
165 pname, c);
166 return 1;
167 }
168 switch (c) {
169 case 'o': case 'O':
11825bd4 170 newoffset = parse_num(value, NULL);
6e182d98 171 break;
172 case 'w': case 'W':
11825bd4 173 newwidth = parse_num(value, NULL);
6e182d98 174 break;
175 }
176 break;
177 case 'f': case 'F':
178 fix_mode = TRUE;
179 break;
180 case 'l': case 'L':
181 look_mode = TRUE;
182 break;
183 case 'e': case 'E':
184 eager_mode = TRUE;
185 break;
186 case 'D':
187 write_default_rc();
188 return 0;
189 break;
190 }
191 } else {
192 if (filename) {
193 fprintf(stderr, "%s: multiple filenames specified\n", pname);
194 return 1;
195 }
196 filename = p;
197 }
198 }
199
200 if (!filename) {
201 fprintf(stderr, "%s: no filename specified\n", pname);
202 return 1;
203 }
204
205 read_rc();
206 if (newoffset != -1)
207 realoffset = newoffset;
208 if (newwidth != -1)
209 width = newwidth;
210 load_file (filename);
211 init();
212 fix_offset();
213 do {
214 draw_scr ();
215 proc_key ();
216 } while (!finished);
217 done();
218
219 return 0;
220}
221
222/*
223 * Fix up `offset' to match `realoffset'. Also, while we're here,
224 * enable or disable ASCII mode and sanity-check the width.
225 */
226void fix_offset(void) {
227 if (3*width+11 > display_cols) {
228 width = (display_cols-11) / 3;
11825bd4 229 sprintf (message, "Width reduced to %"OFF"d to fit on the screen", width);
6e182d98 230 }
231 if (4*width+14 > display_cols) {
232 ascii_enabled = FALSE;
233 if (edit_type == 0)
234 edit_type = 1; /* force to hex mode */
235 } else
236 ascii_enabled = TRUE;
237 offset = realoffset % width;
238 if (!offset)
239 offset = width;
240}
241
242/*
243 * Initialise stuff at the beginning of the program: mostly the
244 * display.
245 */
246static void init(void) {
247 int i;
248
249 display_setup();
250
ef7de295 251 display_define_colour(COL_BUFFER, -1, -1, FALSE);
252 display_define_colour(COL_SELECT, 0, 7, TRUE);
253 display_define_colour(COL_STATUS, 11, 4, TRUE);
254 display_define_colour(COL_ESCAPE, 9, 0, FALSE);
255 display_define_colour(COL_INVALID, 11, 0, FALSE);
6e182d98 256
257 for (i=0; i<256; i++) {
258 sprintf(hex[i], "%02X", i);
259 toprint[i] = (i>=32 && i<127 ? i : '.');
260 }
261}
262
263/*
264 * Clean up all the stuff that init() did.
265 */
266static void done(void) {
267 display_cleanup();
268}
269
270/*
271 * Load the file specified on the command line.
272 */
273static void load_file (char *fname) {
274 FILE *fp;
275
276 file_size = 0;
277 if ( (fp = fopen (fname, "rb")) ) {
278 if (eager_mode) {
11825bd4 279 size_t len;
6e182d98 280 static char buffer[4096];
281
282 filedata = buf_new_empty();
283
284 file_size = 0;
285
286 /*
287 * We've opened the file. Load it.
288 */
289 while ( (len = fread (buffer, 1, sizeof(buffer), fp)) > 0 ) {
290 buf_insert_data (filedata, buffer, len, file_size);
291 file_size += len;
292 }
293 fclose (fp);
294 assert(file_size == buf_length(filedata));
11825bd4 295 sprintf(message, "loaded %s (size %"OFF"d == 0x%"OFF"X).",
6e182d98 296 fname, file_size, file_size);
297 } else {
298 filedata = buf_new_from_file(fp);
299 file_size = buf_length(filedata);
11825bd4 300 sprintf(message, "opened %s (size %"OFF"d == 0x%"OFF"X).",
6e182d98 301 fname, file_size, file_size);
302 }
303 new_file = FALSE;
304 } else {
305 if (look_mode || fix_mode) {
306 fprintf(stderr, "%s: file %s not found, and %s mode active\n",
307 pname, fname, (look_mode ? "LOOK" : "FIX"));
308 exit (1);
309 }
310 filedata = buf_new_empty();
311 sprintf(message, "New file %s.", fname);
312 new_file = TRUE;
313 }
314}
315
316/*
317 * Save the file. Return TRUE on success, FALSE on error.
318 */
319int save_file (void) {
320 FILE *fp;
11825bd4 321 fileoffset_t pos = 0;
6e182d98 322
323 if (look_mode)
324 return FALSE; /* do nothing! */
325
326 if ( (fp = fopen (filename, "wb")) ) {
327 static char buffer[SAVE_BLKSIZ];
328
329 while (pos < file_size) {
11825bd4 330 fileoffset_t size = file_size - pos;
6e182d98 331 if (size > SAVE_BLKSIZ)
332 size = SAVE_BLKSIZ;
333
334 buf_fetch_data (filedata, buffer, size, pos);
335 if (size != fwrite (buffer, 1, size, fp)) {
336 fclose (fp);
337 return FALSE;
338 }
339 pos += size;
340 }
341 } else
342 return FALSE;
343 fclose (fp);
344 return TRUE;
345}
346
347/*
348 * Make a backup of the file, if such has not already been done.
349 * Return TRUE on success, FALSE on error.
350 */
351int backup_file (void) {
352 char backup_name[FILENAME_MAX];
353
354 if (new_file)
355 return TRUE; /* unnecessary - pretend it's done */
356 strcpy (backup_name, filename);
357#if defined(unix) && !defined(GO32)
358 strcat (backup_name, ".bak");
359#elif defined(MSDOS)
360 {
361 char *p, *q;
362
363 q = NULL;
364 for (p = backup_name; *p; p++) {
365 if (*p == '\\')
366 q = NULL;
367 else if (*p == '.')
368 q = p;
369 }
370 if (!q)
371 q = p;
372 strcpy (q, ".BAK");
373 }
374#endif
375 remove (backup_name); /* don't care if this fails */
376 return !rename (filename, backup_name);
377}
378
379static unsigned char *scrbuf = NULL;
380static int scrbuflines = 0;
381
382/*
383 * Draw the screen, for normal usage.
384 */
385void draw_scr (void) {
386 int scrsize, scroff, llen, i, j;
11825bd4 387 fileoffset_t currpos;
388 fileoffset_t marktop, markbot;
389 int mark;
6e182d98 390 char *p;
391 unsigned char c, *q;
392 char *linebuf;
393
394 scrlines = display_rows - 2;
395 if (scrlines > scrbuflines) {
396 scrbuf = (scrbuf ?
397 realloc(scrbuf, scrlines*width) :
398 malloc(scrlines*width));
399 if (!scrbuf) {
400 done();
401 fprintf(stderr, "%s: out of memory!\n", pname);
402 exit (2);
403 }
404 scrbuflines = scrlines;
405 }
406
407 linebuf = malloc(width*4+20);
408 if (!linebuf) {
409 done();
410 fprintf(stderr, "%s: out of memory!\n", pname);
411 exit (2);
412 }
413 memset (linebuf, ' ', width*4+13);
414 linebuf[width*4+13] = '\0';
415
416 if (top_pos == 0)
417 scroff = width - offset;
418 else
419 scroff = 0;
420 scrsize = scrlines * width - scroff;
421 if (scrsize > file_size - top_pos)
422 scrsize = file_size - top_pos;
423
424 buf_fetch_data (filedata, scrbuf, scrsize, top_pos);
425
426 scrsize += scroff; /* hack but it'll work */
427
428 mark = marking && (cur_pos != mark_point);
429 if (mark) {
430 if (cur_pos > mark_point)
431 marktop = mark_point, markbot = cur_pos;
432 else
433 marktop = cur_pos, markbot = mark_point;
434 } else
435 marktop = markbot = 0; /* placate gcc */
436
437 currpos = top_pos;
438 q = scrbuf;
439
440 for (i=0; i<scrlines; i++) {
441 display_moveto (i, 0);
442 if (currpos<=cur_pos || currpos<file_size) {
443 p = hex[(currpos >> 24) & 0xFF];
444 linebuf[0]=p[0];
445 linebuf[1]=p[1];
446 p = hex[(currpos >> 16) & 0xFF];
447 linebuf[2]=p[0];
448 linebuf[3]=p[1];
449 p = hex[(currpos >> 8) & 0xFF];
450 linebuf[4]=p[0];
451 linebuf[5]=p[1];
452 p = hex[currpos & 0xFF];
453 linebuf[6]=p[0];
454 linebuf[7]=p[1];
455 for (j=0; j<width; j++) {
456 if (scrsize > 0) {
457 if (currpos == 0 && j < width-offset)
458 p = " ", c = ' ';
459 else
460 p = hex[*q], c = *q++;
461 scrsize--;
462 } else {
463 p = " ", c = ' ';
464 }
465 linebuf[11+3*j]=p[0];
466 linebuf[12+3*j]=p[1];
467 linebuf[13+3*width+j]=toprint[c];
468 }
469 llen = (currpos ? width : offset);
470 if (mark && currpos<markbot && currpos+llen>marktop) {
471 /*
472 * Some of this line is marked. Maybe all. Whatever
473 * the precise details, there will be two regions
474 * requiring highlighting: a hex bit and an ascii
475 * bit.
476 */
11825bd4 477 fileoffset_t localstart= (currpos<marktop ? marktop :
478 currpos) - currpos;
479 fileoffset_t localstop = (currpos+llen>markbot ? markbot :
480 currpos+llen) - currpos;
6e182d98 481 localstart += width-llen;
482 localstop += width-llen;
483 display_write_chars(linebuf, 11+3*localstart);
484 display_set_colour(COL_SELECT);
485 display_write_chars(linebuf+11+3*localstart,
486 3*(localstop-localstart)-1);
487 display_set_colour(COL_BUFFER);
488 if (ascii_enabled) {
489 display_write_chars(linebuf+10+3*localstop,
490 3+3*width+localstart-3*localstop);
491 display_set_colour(COL_SELECT);
492 display_write_chars(linebuf+13+3*width+localstart,
493 localstop-localstart);
494 display_set_colour(COL_BUFFER);
495 display_write_chars(linebuf+13+3*width+localstop,
496 width-localstop);
497 } else {
498 display_write_chars(linebuf+10+3*localstop,
499 2+3*width-3*localstop);
500 }
1610a3c6 501 } else {
502 display_set_colour(COL_BUFFER);
6e182d98 503 display_write_chars(linebuf,
504 ascii_enabled ? 13+4*width : 10+3*width);
1610a3c6 505 }
6e182d98 506 }
507 currpos += (currpos ? width : offset);
508 display_clear_to_eol();
509 }
510
511 {
512 char status[80];
513 int slen;
514 display_moveto (display_rows-2, 0);
515 display_set_colour(COL_STATUS);
516 sprintf(status, statfmt,
517 (modified ? "**" : " "),
518 filename,
519 (insert_mode ? "(Insert)" :
520 look_mode ? "(LOOK) " :
521 fix_mode ? "(FIX) " : "(Ovrwrt)"),
522 cur_pos, file_size);
523 slen = strlen(status);
524 if (slen > display_cols)
525 slen = display_cols;
526 display_write_chars(status, slen);
527 while (slen++ < display_cols)
528 display_write_str(" ");
529 display_set_colour(COL_BUFFER);
530 }
531
532 display_moveto (display_rows-1, 0);
533 display_write_str (message);
534 display_clear_to_eol();
535 message[0] = '\0';
536
537 i = cur_pos - top_pos;
538 if (top_pos == 0)
539 i += width - offset;
540 j = (edit_type ? (i%width)*3+10+edit_type : (i%width)+13+3*width);
541 if (j >= display_cols)
542 j = display_cols-1;
543 free (linebuf);
544 display_moveto (i/width, j);
545 display_refresh ();
546}
547
ef7de295 548volatile int safe_update, update_required;
549void update (void);
550
6e182d98 551/*
552 * Get a string, in the "minibuffer". Return TRUE on success, FALSE
553 * on break. Possibly syntax-highlight the entered string for
554 * backslash-escapes, depending on the "highlight" parameter.
555 */
556int get_str (char *prompt, char *buf, int highlight) {
557 int maxlen = 79 - strlen(prompt); /* limit to 80 - who cares? :) */
558 int len = 0;
559 int c;
560
561 for (EVER) {
562 display_moveto (display_rows-1, 0);
563 display_set_colour (COL_MINIBUF);
564 display_write_str (prompt);
565 if (highlight) {
566 char *q, *p = buf, *r = buf+len;
567 while (p<r) {
568 q = p;
569 if (*p == '\\') {
570 p++;
571 if (p<r && *p == '\\')
572 p++, display_set_colour(COL_ESCAPE);
40d827aa 573 else if (p>=r || !isxdigit ((unsigned char)*p))
6e182d98 574 display_set_colour(COL_INVALID);
40d827aa 575 else if (p+1>=r || !isxdigit ((unsigned char)p[1]))
6e182d98 576 p++, display_set_colour(COL_INVALID);
577 else
578 p+=2, display_set_colour(COL_ESCAPE);
579 } else {
580 while (p<r && *p != '\\')
581 p++;
582 display_set_colour (COL_MINIBUF);
583 }
584 display_write_chars (q, p-q);
585 }
586 } else
587 display_write_chars (buf, len);
588 display_set_colour (COL_MINIBUF);
589 display_clear_to_eol();
590 display_refresh();
591 if (update_required)
592 update();
593 safe_update = TRUE;
594 c = display_getkey();
595 safe_update = FALSE;
596 if (c == 13 || c == 10) {
597 buf[len] = '\0';
598 return TRUE;
599 } else if (c == 27 || c == 7) {
600 display_beep();
601 display_post_error();
602 strcpy (message, "User Break!");
603 return FALSE;
604 }
605
606 if (c >= 32 && c <= 126) {
607 if (len < maxlen)
608 buf[len++] = c;
609 else
610 display_beep();
611 }
612
613 if ((c == 127 || c == 8) && len > 0)
614 len--;
615
616 if (c == 'U'-'@') /* ^U kill line */
617 len = 0;
618 }
619}
620
621/*
622 * Take a buffer containing possible backslash-escapes, and return
623 * a buffer containing a (binary!) string. Since the string is
624 * binary, it cannot be null terminated: hence the length is
625 * returned from the function. The string is processed in place.
626 *
627 * Escapes are simple: a backslash followed by two hex digits
628 * represents that character; a doubled backslash represents a
629 * backslash itself; a backslash followed by anything else is
630 * invalid. (-1 is returned if an invalid sequence is detected.)
631 */
632int parse_quoted (char *buffer) {
633 char *p, *q;
634
635 p = q = buffer;
636 while (*p) {
637 while (*p && *p != '\\')
638 *q++ = *p++;
639 if (*p == '\\') {
640 p++;
641 if (*p == '\\')
642 *q++ = *p++;
40d827aa 643 else if (p[1] && isxdigit((unsigned char)*p) &&
644 isxdigit((unsigned char)p[1])) {
6e182d98 645 char buf[3];
646 buf[0] = *p++;
647 buf[1] = *p++;
648 buf[2] = '\0';
649 *q++ = strtol(buf, NULL, 16);
650 } else
651 return -1;
652 }
653 }
654 return q - buffer;
655}
656
657/*
658 * Suspend program. (Or shell out, depending on OS, of course.)
659 */
660void suspend(void) {
661#if defined(unix) && !defined(GO32)
662 done();
663 raise (SIGTSTP);
664 init();
665#elif defined(MSDOS)
666 done();
667 spawnl (P_WAIT, getenv("COMSPEC"), "", NULL);
668 init();
669#else
670 display_beep();
671 strcpy(message, "Suspend function not yet implemented.");
672#endif
673}
674
6e182d98 675void update (void) {
676 display_recheck_size();
677 fix_offset ();
678 draw_scr ();
679}
680
681void schedule_update(void) {
682 if (safe_update)
683 update();
684 else
685 update_required = TRUE;
686}
687
11825bd4 688fileoffset_t parse_num (char *buffer, int *error) {
6e182d98 689 if (error)
690 *error = FALSE;
691 if (!buffer[strspn(buffer, "0123456789")]) {
692 /* interpret as decimal */
11825bd4 693 return ATOOFF(buffer);
6e182d98 694 } else if (buffer[0]=='0' && (buffer[1]=='X' || buffer[1]=='x') &&
695 !buffer[2+strspn(buffer+2,"0123456789ABCDEFabcdef")]) {
11825bd4 696 return STRTOOFF(buffer+2, NULL, 16);
6e182d98 697 } else if (buffer[0]=='$' &&
698 !buffer[1+strspn(buffer+1,"0123456789ABCDEFabcdef")]) {
11825bd4 699 return STRTOOFF(buffer+1, NULL, 16);
6e182d98 700 } else {
701 return 0;
702 if (error)
703 *error = TRUE;
704 }
705}