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