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