My hex editor `axe' has existed in prototype for about ten years,
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Fri, 19 Nov 2004 11:01:14 +0000 (11:01 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Fri, 19 Nov 2004 11:01:14 +0000 (11:01 +0000)
but since I never published it, someone else has grabbed the name
out from under me in the meantime. Hence, I'm renaming it `tweak'.

git-svn-id: svn://svn.tartarus.org/sgt/tweak@4823 cda61777-01e9-0310-a592-d414129be87e

Makefile [new file with mode: 0644]
actions.c [new file with mode: 0644]
buffer.c [new file with mode: 0644]
curses.c [new file with mode: 0644]
keytab.c [new file with mode: 0644]
main.c [new file with mode: 0644]
manpage.but [new file with mode: 0644]
rcfile.c [new file with mode: 0644]
search.c [new file with mode: 0644]
slang.c [new file with mode: 0644]
tweak.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..339b0e7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,41 @@
+CC := gcc
+CFLAGS := -g -c -Wall $(XFLAGS)
+LINK := gcc
+LFLAGS :=
+LIBS := 
+
+TWEAK := main.o keytab.o actions.o search.o rcfile.o buffer.o btree.o
+
+ifeq ($(SLANG),yes)
+# INCLUDE += -I/path/to/slang/include
+# LIBS += -L/path/to/slang/lib
+LIBS += -lslang
+TWEAK += slang.o
+else
+LIBS += -lncurses
+TWEAK += curses.o
+endif
+
+.c.o:
+       $(CC) $(CFLAGS) $*.c
+
+all: tweak tweak.1
+
+tweak: $(TWEAK)
+       $(LINK) -o tweak $(TWEAK) $(LIBS)
+
+tweak.1:  manpage.but
+       halibut --man=$@ $<
+
+clean:
+       rm -f *.o tweak
+
+main.o: main.c tweak.h
+keytab.o: keytab.c tweak.h
+actions.o: actions.c tweak.h
+search.o: search.c tweak.h
+rcfile.o: rcfile.c tweak.h
+buffer.o: buffer.c tweak.h btree.h
+slang.o: slang.c tweak.h
+curses.o: curses.c tweak.h
+btree.o: btree.c btree.h
diff --git a/actions.c b/actions.c
new file mode 100644 (file)
index 0000000..c65c3e4
--- /dev/null
+++ b/actions.c
@@ -0,0 +1,655 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "tweak.h"
+
+static void act_exit (void);
+static void act_save (void);
+static void act_exitsave (void);
+static void act_top (void);
+static void act_pgup (void);
+static void act_up (void);
+static void act_home (void);
+static void act_left (void);
+static void act_right (void);
+static void act_end (void);
+static void act_down (void);
+static void act_pgdn (void);
+static void act_bottom (void);
+static void act_togins (void);
+static void act_chmode (void);
+extern void act_self_ins (void);       /* this one must be external */
+static void act_delete (void);
+static void act_delch (void);
+static void act_mark (void);
+static void act_cut (void);
+static void act_copy (void);
+static void act_paste (void);
+static void act_susp (void);
+static void act_goto (void);
+static void act_togstat (void);
+static void act_search (void);
+static void act_recentre (void);
+static void act_width (void);
+static void act_offset (void);
+#ifdef TEST_BUFFER
+static void act_diagnostics (void);
+#endif
+
+keyact parse_action (char *name) {
+    char *names[] = {
+       "exit", "top-of-file", "page-up", "move-up",
+       "begin-line", "move-left", "move-right", "end-line",
+       "move-down", "page-down", "bottom-of-file", "toggle-insert",
+       "change-mode", "delete-left", "delete-right", "mark-place",
+       "cut", "copy", "paste", "suspend", "goto-position",
+       "toggle-status", "search", "save-file", "exit-and-save",
+       "screen-recentre", "new-width", "new-offset"
+#ifdef TEST_BUFFER
+       , "diagnostics"
+#endif
+    };
+    keyact actions[] = {
+       act_exit, act_top, act_pgup, act_up, act_home, act_left,
+       act_right, act_end, act_down, act_pgdn, act_bottom,
+       act_togins, act_chmode, act_delete, act_delch, act_mark,
+       act_cut, act_copy, act_paste, act_susp, act_goto,
+       act_togstat, act_search, act_save, act_exitsave,
+       act_recentre, act_width, act_offset
+#ifdef TEST_BUFFER
+       , act_diagnostics
+#endif
+    };
+    int i;
+
+    for (i=0; i<sizeof(names)/sizeof(*names); i++)
+       if (!strcmp(name, names[i]))
+           return actions[i];
+    return NULL;
+}
+
+static int begline(int x) {
+    int y = x + width-offset;
+    y -= (y % width);
+    y -= width-offset;
+    if (y < 0)
+       y = 0;
+    return y;
+}
+
+static int endline(int x) {
+    int y = x + width-offset;
+    y -= (y % width);
+    y += width-1;
+    y -= width-offset;
+    if (y < 0)
+       y = 0;
+    return y;
+}
+
+static void act_exit(void) {
+    static char question[] = "File is modified. Save before quitting? [yn] ";
+    if (modified) {
+       int c;
+
+       display_moveto (display_rows-1, 0);
+       display_clear_to_eol ();
+       display_set_colour (COL_MINIBUF);
+       display_write_str (question);
+       display_refresh();
+       do {
+#if defined(unix) && !defined(GO32)
+           if (update_required) {
+               update();
+               display_moveto (display_rows-1, 0);
+               display_clear_to_eol ();
+               display_set_colour (COL_MINIBUF);
+               display_write_str (question);
+               display_refresh();
+           }
+           safe_update = TRUE;
+#endif
+           c = display_getkey();
+#if defined(unix) && !defined(GO32)
+           safe_update = FALSE;
+#endif
+           if (c >= 'a' && c <= 'z')
+               c += 'A'-'a';
+       } while (c != 'Y' && c != 'N' && c != '\007');
+       if (c == 'Y') {
+           act_save();
+           if (modified)
+               return;                /* couldn't save, so don't quit */
+           draw_scr();                /* update the ** on status line! */
+       } else if (c == '\007') {
+           return;                    /* don't even quit */
+       }
+    }
+    finished = TRUE;
+}
+
+static void act_save(void) {
+    static int backed_up = FALSE;
+
+    if (!backed_up) {
+       if (!backup_file()) {
+           display_beep();
+           strcpy (message, "Unable to back up file!");
+           return;
+       }
+       backed_up = TRUE;
+    }
+    if (!save_file()) {
+       display_beep();
+       strcpy (message, "Unable to save file!");
+       return;
+    }
+    modified = FALSE;
+}
+
+static void act_exitsave(void) {
+    act_save();
+    draw_scr();                               /* update ** on status line */
+    act_exit();
+}
+
+static void act_top (void) {
+    cur_pos = top_pos = 0;
+    edit_type = !!edit_type;
+}
+
+static void act_pgup(void) {
+    cur_pos -= (scrlines-1)*width;
+    if (cur_pos < 0) {
+       cur_pos = 0;
+       edit_type = !!edit_type;
+    }
+    if (top_pos > cur_pos)
+       top_pos = begline(cur_pos);
+}
+
+static void act_up(void) {
+    cur_pos -= width;
+    if (cur_pos < 0) {
+       cur_pos = 0;
+       edit_type = !!edit_type;
+    }
+    if (top_pos > cur_pos)
+       top_pos = begline(cur_pos);
+}
+
+static void act_home(void) {
+    cur_pos = begline(cur_pos);
+    if (cur_pos < 0)
+       cur_pos = 0;
+    if (top_pos > cur_pos)
+       top_pos = begline(cur_pos);
+    edit_type = !!edit_type;
+}
+
+static void act_left(void) {
+    if (edit_type == 2) {
+       edit_type = 1;
+       return;
+    } else {
+       cur_pos--;
+       edit_type = 2*!!edit_type;
+       if (cur_pos < 0) {
+           cur_pos = 0;
+           edit_type = !!edit_type;
+       }
+       if (top_pos > cur_pos)
+           top_pos = begline(cur_pos);
+    }
+}
+
+static void act_right(void) {
+    int new_top;
+
+    if (edit_type == 1) {
+       if (cur_pos < file_size)
+           edit_type = 2;
+       return;
+    } else {
+       cur_pos++;
+       if (cur_pos > file_size)
+           cur_pos = file_size;
+       new_top = cur_pos - (scrlines-1) * width;
+       if (new_top < 0)
+           new_top = 0;
+       new_top = begline(new_top);
+       if (top_pos < new_top)
+           top_pos = new_top;
+       edit_type = !!edit_type;
+    }
+}
+
+static void act_end(void) {
+    int new_top;
+
+    cur_pos = endline(cur_pos);
+    edit_type = !!edit_type;
+    if (cur_pos >= file_size)
+       cur_pos = file_size;
+    new_top = cur_pos - (scrlines-1) * width;
+    if (new_top < 0)
+       new_top = 0;
+    new_top = begline(new_top);
+    if (top_pos < new_top)
+       top_pos = new_top;
+}
+
+static void act_down(void) {
+    int new_top;
+
+    cur_pos += width;
+    if (cur_pos >= file_size) {
+       cur_pos = file_size;
+       edit_type = !!edit_type;
+    }
+    new_top = cur_pos - (scrlines-1) * width;
+    if (new_top < 0)
+       new_top = 0;
+    new_top = begline(new_top);
+    if (top_pos < new_top)
+       top_pos = new_top;
+}
+
+static void act_pgdn(void) {
+    int new_top;
+
+    cur_pos += (scrlines-1) * width;
+    if (cur_pos >= file_size) {
+       cur_pos = file_size;
+       edit_type = !!edit_type;
+    }
+    new_top = cur_pos - (scrlines-1) * width;
+    if (new_top < 0)
+       new_top = 0;
+    new_top = begline(new_top);
+    if (top_pos < new_top)
+       top_pos = new_top;
+}
+
+static void act_bottom (void) {
+    int new_top;
+
+    cur_pos = file_size;
+    edit_type = !!edit_type;
+    new_top = cur_pos - (scrlines-1) * width;
+    if (new_top < 0)
+       new_top = 0;
+    new_top = begline(new_top);
+    if (top_pos < new_top)
+       top_pos = new_top;
+}
+
+static void act_togins(void) {
+    if (look_mode || fix_mode) {
+       display_beep();
+       sprintf(message, "Can't engage Insert mode when in %s mode",
+               (look_mode ? "LOOK" : "FIX"));
+       insert_mode = FALSE;           /* safety! */
+    } else
+       insert_mode = !insert_mode;
+}
+
+static void act_chmode(void) {
+    if (ascii_enabled)
+       edit_type = !edit_type;        /* 0 -> 1, [12] -> 0 */
+    else if (edit_type == 0)          /* just in case */
+       edit_type = 1;
+}
+
+void act_self_ins(void) {
+    int insert = insert_mode;
+    unsigned char c;
+
+    if (look_mode) {
+       display_beep();
+       strcpy (message, "Can't modify file in LOOK mode");
+       return;
+    }
+
+    if (edit_type) {
+       if (last_char >= '0' && last_char <= '9')
+           last_char -= '0';
+       else if (last_char >= 'A' && last_char <= 'F')
+           last_char -= 'A'-10;
+       else if (last_char >= 'a' && last_char <= 'f')
+           last_char -= 'a'-10;
+       else {
+           display_beep();
+           strcpy(message, "Not a valid character when in hex editing mode");
+           return;
+       }
+    }
+
+    if ( (!insert || edit_type == 2) && cur_pos == file_size) {
+       display_beep();
+       strcpy(message, "End of file reached");
+       return;
+    }
+
+    switch (edit_type) {
+      case 0:                         /* ascii mode */
+       c = last_char;
+       break;
+      case 1:                         /* hex, first digit */
+       if (insert)
+           c = 0;
+       else
+           buf_fetch_data(filedata, &c, 1, cur_pos);
+       c &= 0xF;
+       c |= 16 * last_char;
+       break;
+      case 2:                         /* hex, second digit */
+       buf_fetch_data(filedata, &c, 1, cur_pos);
+       c &= 0xF0;
+       c |= last_char;
+       insert = FALSE;
+       break;
+    }
+
+    if (insert) {
+       buf_insert_data(filedata, &c, 1, cur_pos);
+       file_size++;
+       modified = TRUE;
+    } else if (cur_pos < file_size) {
+       buf_overwrite_data(filedata, &c, 1, cur_pos);
+       modified = TRUE;
+    } else {
+       display_beep();
+       strcpy(message, "End of file reached");
+    }
+    act_right();
+}
+
+static void act_delete(void) {
+    if (!insert_mode || (edit_type!=2 && cur_pos==0)) {
+       display_beep();
+       strcpy (message, "Can't delete while not in Insert mode");
+    } else if (cur_pos > 0 || edit_type == 2) {
+       act_left();
+       buf_delete (filedata, 1, cur_pos);
+       file_size--;
+       edit_type = !!edit_type;
+       modified = TRUE;
+    }
+}
+
+static void act_delch(void) {
+    if (!insert_mode) {
+       display_beep();
+       strcpy (message, "Can't delete while not in Insert mode");
+    } else if (cur_pos < file_size) {
+       buf_delete (filedata, 1, cur_pos);
+       file_size--;
+       edit_type = !!edit_type;
+       modified = TRUE;
+    }
+}
+
+static void act_mark (void) {
+    if (look_mode) {
+       display_beep();
+       strcpy (message, "Can't cut or paste in LOOK mode");
+       marking = FALSE;               /* safety */
+       return;
+    }
+    marking = !marking;
+    mark_point = cur_pos;
+}
+
+static void act_cut (void) {
+    long marktop, marksize;
+
+    if (!marking || mark_point==cur_pos) {
+       display_beep();
+       strcpy (message, "Set mark first");
+       return;
+    }
+    if (!insert_mode) {
+       display_beep();
+       strcpy (message, "Can't cut while not in Insert mode");
+       return;
+    }
+    marktop = cur_pos;
+    marksize = mark_point - cur_pos;
+    if (marksize < 0) {
+       marktop += marksize;
+       marksize = -marksize;
+    }
+    if (cutbuffer)
+       buf_free (cutbuffer);
+    cutbuffer = buf_cut (filedata, marksize, marktop);
+    file_size -= marksize;
+    cur_pos = marktop;
+    if (cur_pos < 0)
+       cur_pos = 0;
+    if (top_pos > cur_pos)
+       top_pos = begline(cur_pos);
+    edit_type = !!edit_type;
+    modified = TRUE;
+    marking = FALSE;
+}
+
+static void act_copy (void) {
+    int marktop, marksize;
+
+    if (!marking) {
+       display_beep();
+       strcpy (message, "Set mark first");
+       return;
+    }
+    marktop = cur_pos;
+    marksize = mark_point - cur_pos;
+    if (marksize < 0) {
+       marktop += marksize;
+       marksize = -marksize;
+    }
+    if (cutbuffer)
+       buf_free (cutbuffer);
+    cutbuffer = buf_copy (filedata, marksize, marktop);
+    marking = FALSE;
+}
+
+static void act_paste (void) {
+    int cutsize, new_top;
+
+    cutsize = buf_length (cutbuffer);
+    if (!insert_mode) {
+       if (cur_pos + cutsize > file_size) {
+           display_beep();
+           strcpy (message, "Too close to end of file to paste");
+           return;
+       }
+       buf_delete (filedata, cutsize, cur_pos);
+       file_size -= cutsize;
+    }
+    buf_paste (filedata, cutbuffer, cur_pos);
+    modified = TRUE;
+    cur_pos += cutsize;
+    file_size += cutsize;
+    edit_type = !!edit_type;
+    new_top = cur_pos - (scrlines-1) * width;
+    if (new_top < 0)
+       new_top = 0;
+    new_top = begline(new_top);
+    if (top_pos < new_top)
+       top_pos = new_top;
+}
+
+static void act_susp (void) {
+    suspend();
+}
+
+static void act_goto (void) {
+    char buffer[80];
+    long position, new_top;
+    int error;
+
+    if (!get_str("Enter position to go to: ", buffer, FALSE))
+       return;                        /* user break */
+
+    position = parse_num (buffer, &error);
+    if (error) {
+       display_beep();
+       strcpy (message, "Unable to parse position value");
+       return;
+    }
+
+    if (position < 0 || position > file_size) {
+       display_beep();
+       strcpy (message, "Position is outside bounds of file");
+       return;
+    }
+
+    cur_pos = position;
+    edit_type = !!edit_type;
+    new_top = cur_pos - (scrlines-1) * width;
+    if (new_top < 0)
+       new_top = 0;
+    new_top = begline(new_top);
+    if (top_pos > cur_pos)
+       top_pos = begline(cur_pos);
+    if (top_pos < new_top)
+       top_pos = new_top;
+}
+
+static void act_togstat (void) {
+    if (statfmt == decstatus)
+       statfmt = hexstatus;
+    else
+       statfmt = decstatus;
+}
+
+static void act_search (void) {
+    char buffer[80];
+    int len, posn, dfapos;
+    DFA dfa;
+    static unsigned char sblk[SEARCH_BLK];
+    static char withdef[] = "Search for (default=last): ";
+    static char withoutdef[] = "Search for: ";
+
+    dfa = last_dfa();
+
+    if (!get_str(dfa ? withdef : withoutdef, buffer, TRUE))
+       return;                        /* user break */
+    if (!dfa && !*buffer) {
+       strcpy (message, "Search aborted.");
+       return;
+    }
+
+    if (!*buffer) {
+       len = last_len();
+    } else {
+       len = parse_quoted (buffer);
+       if (len == -1) {
+           display_beep();
+           strcpy (message, "Invalid escape sequence in search string");
+           return;
+       }
+       dfa = build_dfa (buffer, len);
+    }
+
+    dfapos = 0;
+
+    for (posn = cur_pos+1; posn < file_size; posn++) {
+       unsigned char *q;
+       int size = SEARCH_BLK;
+
+       if (size > file_size-posn)
+           size = file_size-posn;
+       buf_fetch_data (filedata, sblk, size, posn);
+       q = sblk;
+       while (size--) {
+           posn++;
+           dfapos = dfa[dfapos][*q++];
+           if (dfapos == len) {
+               int new_top;
+
+               cur_pos = posn - len;
+               edit_type = !!edit_type;
+               new_top = cur_pos - (scrlines-1) * width;
+               new_top = begline(new_top);
+               if (top_pos < new_top)
+                   top_pos = new_top;
+               return;
+           }
+       }
+    }
+    strcpy (message, "Not found.");
+}
+
+static void act_recentre (void) {
+    top_pos = cur_pos - (display_rows-2)/2 * width;
+    if (top_pos < 0)
+       top_pos = 0;
+    top_pos = begline(top_pos);
+}
+
+static void act_width (void) {
+    char buffer[80];
+    char prompt[80];
+    long w;
+    long new_top;
+    int error;
+
+    sprintf (prompt, "Enter screen width in bytes (now %d): ", width);
+    if (!get_str (prompt, buffer, FALSE))
+       return;
+    w = parse_num (buffer, &error);
+    if (error) {
+       display_beep();
+       strcpy (message, "Unable to parse width value");
+       return;
+    }
+    if (w > 0) {
+       width = w;
+       fix_offset();
+       new_top = cur_pos - (scrlines-1) * width;
+       new_top = begline(new_top);
+       if (top_pos < new_top)
+           top_pos = new_top;
+    }
+}
+
+static void act_offset (void) {
+    char buffer[80];
+    char prompt[80];
+    long o;
+    long new_top;
+    int error;
+
+    sprintf (prompt, "Enter start-of-file offset in bytes (now %d): ",
+            realoffset);
+    if (!get_str (prompt, buffer, FALSE))
+       return;
+    o = parse_num (buffer, &error);
+    if (error) {
+       display_beep();
+       strcpy (message, "Unable to parse offset value");
+       return;
+    }
+    if (o >= 0) {
+       realoffset = o;
+       fix_offset();
+       new_top = cur_pos - (scrlines-1) * width;
+       new_top = begline(new_top);
+       if (top_pos < new_top)
+           top_pos = new_top;
+    }
+}
+
+#ifdef TEST_BUFFER
+static void act_diagnostics(void)
+{
+    extern void buffer_diagnostic(buffer *buf, char *title);
+
+    buffer_diagnostic(filedata, "filedata");
+    buffer_diagnostic(cutbuffer, "cutbuffer");
+}
+#endif
diff --git a/buffer.c b/buffer.c
new file mode 100644 (file)
index 0000000..714de39
--- /dev/null
+++ b/buffer.c
@@ -0,0 +1,553 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "tweak.h"
+#include "btree.h"
+
+#ifdef TEST_BUFFER
+#define BLKMIN 4
+#else
+#define BLKMIN 512
+#endif
+
+#define BLKMAX (2*BLKMIN)
+
+struct file {
+    FILE *fp;
+    int refcount;
+};
+
+struct buffer {
+    btree *bt;
+};
+
+struct bufblk {
+    int len;                          /* number of bytes in block, always */
+    struct file *file;                /* non-NULL indicates a file block */
+    int filepos;                      /* only meaningful if fp!=NULL */
+    unsigned char *data;              /* only used if fp==NULL */
+};
+
+typedef int filesize;                 /* FIXME: should be larger */
+
+static bt_element_t bufblkcopy(void *state, void *av)
+{
+    struct bufblk *a = (struct bufblk *)av;
+    struct bufblk *ret;
+
+    if (a->file) {
+       ret = (struct bufblk *)malloc(sizeof(struct bufblk));
+       ret->data = NULL;
+       a->file->refcount++;
+    } else {
+       ret = (struct bufblk *)malloc(sizeof(struct bufblk) + BLKMAX);
+       ret->data = (unsigned char *)(ret+1);
+       memcpy(ret->data, a->data, BLKMAX);
+    }
+
+    ret->file = a->file;
+    ret->filepos = a->filepos;
+    ret->len = a->len;
+
+    return ret;
+}
+
+static void bufblkfree(void *state, void *av)
+{
+    struct bufblk *a = (struct bufblk *)av;
+
+    if (a->file) {
+       a->file->refcount--;
+
+       if (a->file->refcount == 0) {
+           fclose(a->file->fp);
+           free(a->file);
+       }
+    }
+
+    free(a);
+}
+
+void bufblkpropmake(void *state, bt_element_t av, void *destv)
+{
+    struct bufblk *a = (struct bufblk *)av;
+    filesize *dest = (filesize *)destv;
+
+    *dest = a->len;
+}
+
+/* s1 may be NULL (indicating copy s2 into dest). s2 is never NULL. */
+void bufblkpropmerge(void *state, void *s1v, void *s2v, void *destv)
+{
+    filesize *s1 = (filesize *)s1v;
+    filesize *s2 = (filesize *)s2v;
+    filesize *dest = (filesize *)destv;
+    if (!s1 && !s2) return;           /* don't need to free anything */
+    *dest = *s2 + (s1 ? *s1 : 0);
+}
+
+static buffer *buf_new_from_bt(btree *bt)
+{
+    buffer *buf = (buffer *)malloc(sizeof(buffer));
+
+    buf->bt = bt;
+
+    return buf;
+}
+
+static btree *buf_bt_new(void)
+{
+    return bt_new(NULL, bufblkcopy, bufblkfree, sizeof(filesize),
+                 alignof(filesize), bufblkpropmake, bufblkpropmerge,
+                 NULL, 2);
+}
+
+extern void buf_free(buffer *buf)
+{
+    bt_free(buf->bt);
+    free(buf);
+}
+
+static int bufblksearch(void *tstate, void *sstate, int ntrees,
+                       void **props, int *counts,
+                       bt_element_t *elts, int *is_elt)
+{
+    int *disttogo = (int *)sstate;
+    int distsofar = 0;
+    int i;
+
+    for (i = 0; i < ntrees; i++) {
+       struct bufblk *blk;
+       int sublen = props[i] ? *(filesize *)props[i] : 0;
+
+       if ((props[i] && *disttogo < distsofar + sublen) ||
+           (*disttogo == distsofar + sublen && i == ntrees-1)) {
+           *disttogo -= distsofar;
+           /*
+            * Descend into this subtree.
+            */
+           *is_elt = FALSE;
+           return i;
+       }
+
+       distsofar += sublen;
+
+       if (i < ntrees-1) {
+           blk = (struct bufblk *)elts[i];
+
+           if (*disttogo < distsofar + blk->len) {
+               /*
+                * Select this element.
+                */
+               *disttogo -= distsofar;
+               *is_elt = TRUE;
+               return i;
+           }
+
+           distsofar += blk->len;
+       }
+    }
+
+    assert(!"We should never reach here");
+    return 0;                         /* placate gcc */
+}
+
+static int buf_bt_find_pos(btree *bt, int pos, int *poswithin)
+{
+    int index;
+
+    bt_propfind(bt, bufblksearch, &pos, &index);
+
+    *poswithin = pos;
+    return index;
+}
+
+/*
+ * Convert a file-data block of size at most BUFMAX into a
+ * literal-data block. Returns the replacement block (the old one
+ * still needs freeing) or NULL if no conversion performed.
+ */
+static struct bufblk *buf_convert_to_literal(struct bufblk *blk)
+{
+    if (blk->file && blk->len <= BLKMAX) {
+       struct bufblk *ret =
+           (struct bufblk *)malloc(sizeof(struct bufblk) + BLKMAX);
+       ret->data = (unsigned char *)(ret+1);
+       ret->file = NULL;
+       ret->filepos = 0;
+       ret->len = blk->len;
+       fseek(blk->file->fp, blk->filepos, SEEK_SET);
+       fread(ret->data, blk->len, 1, blk->file->fp);
+
+       return ret;
+    }
+
+    return NULL;
+}
+
+/*
+ * Look at blocks `index' and `index+1' of buf. If they're both
+ * literal-data blocks and one of them is undersized, merge or
+ * redistribute. Returns 0 if it has not changed the number of
+ * blocks, or 1 if it has merged two.
+ */
+static int buf_bt_cleanup(btree *bt, int index)
+{
+    struct bufblk *a, *b, *cvt;
+    int totallen;
+    unsigned char tmpdata[BLKMAX*2];
+
+    if (index < 0) return 0;
+
+    a = (struct bufblk *)bt_index(bt, index);
+    b = (struct bufblk *)bt_index(bt, index+1);
+
+    if ( a && (cvt = buf_convert_to_literal(a)) != NULL ) {
+       bt_replace(bt, cvt, index);
+       bufblkfree(NULL, a);
+       a = cvt;
+    }
+
+    if ( b && (cvt = buf_convert_to_literal(b)) != NULL ) {
+       bt_replace(bt, cvt, index+1);
+       bufblkfree(NULL, b);
+       b = cvt;
+    }
+
+    if (!a || !b || a->file || b->file) return 0;
+
+    if (a->len >= BLKMIN && b->len >= BLKMIN) return 0;
+
+    assert(a->len <= BLKMAX && b->len <= BLKMAX);
+
+    /* Use bt_index_w to ensure reference count of 1 on both blocks */
+    a = (struct bufblk *)bt_index_w(bt, index);
+    b = (struct bufblk *)bt_index_w(bt, index+1);
+
+    /*
+     * So, we have one block with size at most BLKMIN, and another
+     * with size at most BLKMAX. Combined, their maximum possible
+     * size is in excess of BLKMAX, so we can't guaranteeably merge
+     * them into one. If they won't merge, we instead redistribute
+     * data between them.
+     */
+    totallen = a->len + b->len;
+    memcpy(tmpdata, a->data, a->len);
+    memcpy(tmpdata + a->len, b->data, b->len);
+
+    if (totallen >= BLKMAX) {
+       /*
+        * Redistribute into two (nearly) equal-sized blocks.
+        */
+       a->len = totallen / 2;
+       b->len = totallen - a->len;
+
+       memcpy(a->data, tmpdata, a->len);
+       memcpy(b->data, tmpdata + a->len, b->len);
+
+       bt_replace(bt, a, index);
+       bt_replace(bt, b, index+1);
+
+       return 0;
+    } else {
+       /*
+        * Just merge into one.
+        */
+       a->len = totallen;
+       memcpy(a->data, tmpdata, a->len);
+
+       bt_replace(bt, a, index);
+       free(bt_delpos(bt, index+1));
+
+       return 1;
+    }
+}
+
+static int buf_bt_splitpoint(btree *bt, int pos)
+{
+    int poswithin, index;
+    struct bufblk *blk, *newblk;
+
+    index = buf_bt_find_pos(bt, pos, &poswithin);
+
+    if (!poswithin)
+       return index;                  /* the nice simple case */
+
+    /*
+     * Now split element `index' at position `poswithin'.
+     */
+    blk = (struct bufblk *)bt_index_w(bt, index);   /* ensure ref count == 1 */
+    newblk = (struct bufblk *)bufblkcopy(NULL, blk);
+
+    if (!newblk->file) {
+       memcpy(newblk->data, blk->data + poswithin, blk->len - poswithin);
+    } else {
+       newblk->filepos += poswithin;
+    }
+    blk->len = poswithin;
+    bt_replace(bt, blk, index);
+    newblk->len -= poswithin;
+    bt_addpos(bt, newblk, index+1);
+
+    buf_bt_cleanup(bt, index+1);
+    index -= buf_bt_cleanup(bt, index-1);
+
+    return index + 1;
+}
+
+static btree *buf_bt_split(btree *bt, int pos, int before)
+{
+    int index = buf_bt_splitpoint(bt, pos);
+    return bt_splitpos(bt, index, before);
+}
+
+static btree *buf_bt_join(btree *a, btree *b)
+{
+    int index = bt_count(a) - 1;
+    btree *ret;
+
+    ret = bt_join(a, b);
+
+    buf_bt_cleanup(ret, index);
+
+    return ret;
+}
+
+static void buf_insert_bt(buffer *buf, btree *bt, int pos)
+{
+    btree *right = buf_bt_split(buf->bt, pos, FALSE);
+    buf->bt = buf_bt_join(buf->bt, bt);
+    buf->bt = buf_bt_join(buf->bt, right);
+}
+
+static int bufblklensearch(void *tstate, void *sstate, int ntrees,
+                          void **props, int *counts,
+                          bt_element_t *elts, int *is_elt)
+{
+    int *output = (int *)sstate;
+    int i, size = 0;
+
+    for (i = 0; i < ntrees; i++) {
+       struct bufblk *blk;
+
+       if (props[i])
+           size += *(filesize *)props[i];
+
+       if (i < ntrees-1) {
+           blk = (struct bufblk *)elts[i];
+
+           size += blk->len;
+       }
+    }
+
+    *output = size;
+
+    /* Actual return value doesn't matter */
+    *is_elt = TRUE;
+    return 1;
+}
+
+static int buf_bt_length(btree *bt)
+{
+    int length;
+
+    bt_propfind(bt, bufblklensearch, &length, NULL);
+
+    return length;
+}
+
+extern int buf_length(buffer *buf)
+{
+    return buf_bt_length(buf->bt);
+}
+
+extern buffer *buf_new_empty(void)
+{
+    buffer *buf = (buffer *)malloc(sizeof(buffer));
+
+    buf->bt = buf_bt_new();
+
+    return buf;
+}
+
+extern buffer *buf_new_from_file(FILE *fp)
+{
+    buffer *buf = buf_new_empty();
+    struct bufblk *blk;
+    struct file *file;
+
+    file = (struct file *)malloc(sizeof(struct file));
+    file->fp = fp;
+    file->refcount = 1;                       /* the reference we're about to make */
+
+    blk = (struct bufblk *)malloc(sizeof(struct bufblk));
+    blk->data = NULL;
+    blk->file = file;
+    blk->filepos = 0;
+
+    fseek(fp, 0, SEEK_END);
+    blk->len = ftell(fp);
+
+    bt_addpos(buf->bt, blk, 0);
+
+    buf_bt_cleanup(buf->bt, 0);
+
+    return buf;
+}
+
+extern void buf_fetch_data(buffer *buf, void *vdata, int len, int pos)
+{
+    int index, poswithin, thislen;
+    unsigned char *data = (unsigned char *)vdata;
+
+    index = buf_bt_find_pos(buf->bt, pos, &poswithin);
+
+    while (len > 0) {
+       struct bufblk *blk = (struct bufblk *)bt_index(buf->bt, index);
+
+       thislen = blk->len - poswithin;
+       if (thislen > len)
+           thislen = len;
+
+       if (blk->file) {
+           fseek(blk->file->fp, blk->filepos + poswithin, SEEK_SET);
+           fread(data, thislen, 1, blk->file->fp);
+       } else {
+           memcpy(data, blk->data + poswithin, thislen);
+       }
+
+       data += thislen;
+       len -= thislen;
+
+       poswithin = 0;
+
+       index++;
+    }
+}
+
+extern void buf_insert_data(buffer *buf, void *vdata, int len, int pos)
+{
+    btree *bt = buf_bt_new();
+    int nblocks, blklen1, extra;
+    int i, origlen = len;
+    unsigned char *data = (unsigned char *)vdata;
+
+    nblocks = len / ((BLKMIN + BLKMAX)/2);
+    if (nblocks * BLKMAX < len)
+       nblocks++;
+    blklen1 = len / nblocks;
+    extra = len % nblocks;
+    assert(blklen1 >= BLKMIN || nblocks == 1);
+    assert(blklen1 <= BLKMAX - (extra!=0));
+
+    for (i = 0; i < nblocks; i++) {
+       struct bufblk *blk;
+       int blklen = blklen1 + (i < extra);
+
+       blk = (struct bufblk *)malloc(sizeof(struct bufblk) + BLKMAX);
+       blk->data = (unsigned char *)(blk+1);
+       memcpy(blk->data, data, blklen);
+       blk->len = blklen;
+       blk->file = NULL;
+       blk->filepos = 0;
+
+       data += blklen;
+       len -= blklen;
+
+       bt_addpos(bt, blk, i);
+       assert(origlen == buf_bt_length(bt) + len);
+    }
+
+    assert(len == 0);
+    assert(origlen == buf_bt_length(bt));
+
+    buf_insert_bt(buf, bt, pos);
+}
+
+extern void buf_delete(buffer *buf, int len, int pos)
+{
+    btree *left = buf_bt_split(buf->bt, pos, TRUE);
+    btree *right = buf_bt_split(buf->bt, len, FALSE);
+
+    bt_free(buf->bt);
+
+    buf->bt = buf_bt_join(left, right);
+}
+
+extern void buf_overwrite_data(buffer *buf, void *data, int len, int pos)
+{
+    buf_delete(buf, len, pos);
+    buf_insert_data(buf, data, len, pos);
+}
+
+extern buffer *buf_cut(buffer *buf, int len, int pos)
+{
+    btree *left = buf_bt_split(buf->bt, pos, TRUE);
+    btree *right = buf_bt_split(buf->bt, len, FALSE);
+    btree *ret = buf->bt;
+
+    buf->bt = buf_bt_join(left, right);
+
+    return buf_new_from_bt(ret);
+}
+
+extern buffer *buf_copy(buffer *buf, int len, int pos)
+{
+    btree *left = buf_bt_split(buf->bt, pos, TRUE);
+    btree *right = buf_bt_split(buf->bt, len, FALSE);
+    btree *ret = bt_clone(buf->bt);
+
+    buf->bt = buf_bt_join(left, buf->bt);
+    buf->bt = buf_bt_join(buf->bt, right);
+
+    return buf_new_from_bt(ret);
+}
+
+extern void buf_paste(buffer *buf, buffer *cutbuffer, int pos)
+{
+    btree *bt = bt_clone(cutbuffer->bt);
+    buf_insert_bt(buf, bt, pos);
+}
+
+#ifdef TEST_BUFFER
+static FILE *debugfp = NULL;
+extern void buffer_diagnostic(buffer *buf, char *title)
+{
+    int i, offset;
+    struct bufblk *blk;
+
+    if (!debugfp) {
+       debugfp = fdopen(3, "w");
+       if (!debugfp)
+           debugfp = fopen("debug.log", "w");
+    }
+
+    if (!buf) {
+       fprintf(debugfp, "Buffer [%s] is null\n", title);
+       return;
+    }
+
+    fprintf(debugfp, "Listing of buffer [%s]:\n", title);
+    offset = 0;
+    for (i = 0; (blk = (struct bufblk *)bt_index(buf->bt, i)) != NULL; i++) {
+       fprintf(debugfp, "%08x: %p, len =%8d,", offset, blk, blk->len);
+       if (blk->file) {
+           fprintf(debugfp, " file %p pos %8d\n", blk->file, blk->filepos);
+       } else {
+           int j;
+
+           for (j = 0; j < blk->len; j++)
+               fprintf(debugfp, " %02x", blk->data[j]);
+
+           fprintf(debugfp, "\n");
+       }
+       offset += blk->len;
+    }
+    fprintf(debugfp, "Listing concluded\n\n");
+
+    fflush(debugfp);
+}
+#endif
diff --git a/curses.c b/curses.c
new file mode 100644 (file)
index 0000000..f11d777
--- /dev/null
+++ b/curses.c
@@ -0,0 +1,139 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <curses.h>
+
+#include "tweak.h"
+
+int display_rows, display_cols;
+
+void display_beep(void)
+{
+    beep();
+}
+
+static void get_screen_size (void) {
+    getmaxyx(stdscr, display_rows, display_cols);
+}
+
+void display_setup(void)
+{
+    initscr();
+    noecho();
+    keypad(stdscr, 0);
+    raw();
+    move(0,0);
+    refresh();
+    get_screen_size();
+    if (has_colors())
+       start_color();
+}
+
+void display_cleanup(void)
+{
+    endwin();
+}
+
+void display_moveto(int y, int x)
+{
+    wmove(stdscr, y, x);
+}
+
+void display_refresh(void)
+{
+    refresh();
+}
+
+void display_write_str(char *str)
+{
+    waddstr(stdscr, str);
+}
+
+void display_write_chars(char *str, int len)
+{
+    waddnstr(stdscr, str, len);
+}
+
+#define MAXCOLOURS 32
+int attrs[MAXCOLOURS];
+
+void display_define_colour(int colour, int fg, int bg)
+{
+    static int colours[8] = {
+        COLOR_BLACK,
+        COLOR_RED,
+        COLOR_GREEN,
+        COLOR_YELLOW,
+        COLOR_BLUE,
+        COLOR_MAGENTA,
+        COLOR_CYAN,
+        COLOR_WHITE,
+    };
+
+    assert(colour >= 0 && colour < MAXCOLOURS && colour < COLOR_PAIRS-2);
+
+    assert(!(bg & ~7));                       /* bold backgrounds are nonportable */
+    init_pair(colour+1, colours[fg & 7], colours[bg]);
+    attrs[colour] = (fg & 8 ? A_BOLD : 0) | COLOR_PAIR(colour+1);
+}
+
+void display_set_colour(int colour)
+{
+    wattrset(stdscr, attrs[colour]);
+}
+
+void display_clear_to_eol(void)
+{
+    wclrtoeol(stdscr);
+}
+
+int last_getch = ERR;
+
+int display_getkey(void)
+{
+    int ret;
+    extern void schedule_update(void);
+
+    if (last_getch != ERR) {
+       int ret = last_getch;
+       last_getch = ERR;
+       return ret;
+    }
+    while (1) {
+       ret = getch();
+       if (ret == KEY_RESIZE) {
+           schedule_update();
+           continue;
+       }
+       return ret;
+    }
+}
+
+int display_input_to_flush(void)
+{
+    int ret;
+    if (last_getch != ERR)
+       return TRUE;
+
+    nodelay(stdscr, 1);
+    ret = getch();
+    nodelay(stdscr, 0);
+    if (ret == ERR)
+       return FALSE;
+
+    last_getch = ret;
+    return TRUE;
+}
+
+void display_post_error(void)
+{
+    /* I don't _think_ we need do anything here */
+}
+
+void display_recheck_size(void)
+{
+    get_screen_size ();
+}
diff --git a/keytab.c b/keytab.c
new file mode 100644 (file)
index 0000000..2943966
--- /dev/null
+++ b/keytab.c
@@ -0,0 +1,121 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "tweak.h"
+
+typedef union keytab keytab;
+
+union keytab {
+    enum {ACTION, EXTENDED} type;
+    struct {
+       int type;
+       keyact action;
+    } a;
+    struct {
+       int type;
+       keytab *extended[256];
+    } e;
+};
+
+keytab *base[256] = { NULL256 };
+
+/*
+ * Bind a key sequence to an action.
+ */
+void bind_key (char *sequence, int len, keyact action) {
+    keytab *(*table)[256];
+    int k, i;
+
+    table = &base;
+    while (--len) {
+       k = (unsigned char) *sequence++;
+       if ( !(*table)[k] ) {
+           /*
+            * We must create an EXTENDED entry.
+            */
+           (*table)[k] = malloc(sizeof(base[0]->e));
+           (*table)[k]->type = EXTENDED;
+           for (i=0; i<256; i++)
+               (*table)[k]->e.extended[i] = NULL;
+       } else if ( (*table)[k]->type == ACTION ) {
+           /*
+            * A subsequence is already bound: fail.
+            */
+           return;
+       }
+       table = &(*table)[k]->e.extended;
+    }
+    k = (unsigned char) *sequence;
+    if ( !(*table)[k] ) {
+       /*
+        * We can bind the key.
+        */
+       (*table)[k] = malloc(sizeof(base[0]->a));
+       (*table)[k]->type = ACTION;
+       (*table)[k]->a.action = action;
+    }
+}
+
+/*
+ * Format an ASCII code into a printable description of the key stroke.
+ */
+static void strkey (char *s, int k) {
+    k &= 255;                         /* force unsigned */
+    if (k==27)
+       strcpy(s, " ESC");
+    else if (k<32 || k==127)
+       sprintf(s, " ^%c", k ^ 64);
+    else if (k<127)
+       sprintf(s, " %c", k);
+    else
+       sprintf(s, " <0x%2X>", k);
+}
+
+/*
+ * Get and process a key stroke.
+ */
+void proc_key (void) {
+    keytab *kt;
+
+#if defined(unix) && !defined(GO32)
+    if (update_required)
+       update();
+    safe_update = TRUE;
+#endif
+    last_char = display_getkey();
+#if defined(unix) && !defined(GO32)
+    safe_update = FALSE;
+#endif
+    strcpy(message, "Unknown key sequence");
+    strkey(message+strlen(message), last_char);
+    kt = base[(unsigned char) last_char];
+    if (!kt) {
+       display_beep();
+       while (display_input_to_flush())
+           strkey(message+strlen(message), display_getkey());
+       return;
+    }
+
+    while (kt->type == EXTENDED) {
+#if defined(unix) && !defined(GO32)
+       if (update_required)
+           update();
+       safe_update = TRUE;
+#endif
+       last_char = display_getkey();
+#if defined(unix) && !defined(GO32)
+       safe_update = FALSE;
+#endif
+       strkey(message+strlen(message), last_char);
+       kt = kt->e.extended[(unsigned char) last_char];
+       if (!kt) {
+           display_beep();
+           while (display_input_to_flush())
+               strkey(message+strlen(message), display_getkey());
+           return;
+       }
+    }
+    message[0] = '\0';                /* clear the "unknown" message */
+    (*kt->a.action)();
+}
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..05df563
--- /dev/null
+++ b/main.c
@@ -0,0 +1,699 @@
+/*
+ * TODO before releasable quality:
+ * 
+ *  - Thorough testing.
+ * 
+ *  - Half decent build system.
+ * 
+ * TODO possibly after that:
+ * 
+ *  - Need to handle >2Gb files! Up the `filesize' type to long
+ *    long and use it everywhere.
+ *
+ *  - Multiple buffers, multiple on-screen windows.
+ *     + ^X^F to open new file
+ *     + ^X^R to open new file RO
+ *     + ^X b to switch buffers in a window
+ *     + ^X o to switch windows
+ *     + ^X 2 to split a window
+ *     + ^X 1 to destroy all windows but this
+ *     + ^X 0 to destroy this window
+ *     + ^X ^ to enlarge this window by one line
+ *     + width settings vary per buffer (aha, _that's_ why I wanted
+ *      a buffer structure surrounding the raw B-tree)
+ *     + hex-editor-style minibuffer for entering search terms,
+ *      rather than the current rather crap one; in particular
+ *      this enables pasting into the search string.
+ *     + er, how exactly do we deal with the problem of saving over
+ *      a file which we're maintaining references to?
+ * 
+ *  - Undo!
+ *     + this actually doesn't seem _too_ horrid. For a start, one
+ *      simple approach would be to clone the entire buffer B-tree
+ *      every time we perform an operation! That's actually not
+ *      _too_ expensive, if we maintain a limit on the number of
+ *      operations we may undo.
+ *     + I had also thought of cloning the tree we insert for each
+ *      buf_insert_data and cloning the one removed for each
+ *      buf_delete_data (both must be cloned for an overwrite),
+ *      but I'm not convinced that simply cloning the entire thing
+ *      isn't a superior option.
+ * 
+ *  - Reverse search.
+ *     + need to construct a reverse DFA.
+ *     + probably should construct both every time, so that you can
+ *      search forward for a thing and then immediately change
+ *      your mind and search backward for the same thing.
+ * 
+ *  - In-place editing.
+ *     + this is an extra option when running in Fix mode. It
+ *      causes a change of semantics when saving: instead of
+ *      constructing a new backup file and writing it over the old
+ *      one, we simply seek within the original file and write out
+ *      all the pieces that have changed.
+ *     + Primarily useful for editing disk devices directly
+ *      (yikes!).
+ *     + I had intended to suggest that in Fix mode this would be
+ *      nice and easy, since every element of the buffer tree is
+ *      either a literal block (needs writing) or a from-file
+ *      block denoting the same file _in the same position_.
+ *      However, this is not in fact the case because you can cut
+ *      and paste, so it's not that easy.
+ *     + So I'm forced to the conclusion that when operating in
+ *      this mode, it becomes illegal to cut and paste from-file
+ *      blocks: they must be loaded in full at some point.
+ *       * Thinking ahead about multiple-buffer operation: it
+ *         would be a bad idea to keep a from-file block
+ *         referencing /dev/hda and paste it into another ordinary
+ *         buffer. But _also_ it would be a bad idea to paste a
+ *         from-file block referencing a file stored _on_ /dev/hda
+ *         into the in-place buffer dealing with /dev/hda itself.
+ *       * So I'm forced to another odd conclusion, which is that
+ *         from-file blocks must be eliminated in _two_ places:
+ *         when copying a cut buffer _from_ an in-place buffer,
+ *         _and_ when pasting a cut buffer _into_ one.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#if defined(unix) && !defined(GO32)
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#elif defined(MSDOS)
+#include <dos.h>
+#include <process.h>
+#endif
+
+#include "tweak.h"
+
+static void init(void);
+static void done(void);
+static void load_file (char *);
+
+char toprint[256];                    /* LUT: printable versions of chars */
+char hex[256][3];                     /* LUT: binary to hex, 1 byte */
+
+char message[80];
+
+char decstatus[] = "%s TWEAK "VER": %-18.18s %s posn=%-10d size=%-10d";
+char hexstatus[] = "%s TWEAK "VER": %-18.18s %s posn=0x%-8X size=0x%-8X";
+char *statfmt = hexstatus;
+
+char last_char;
+char *pname;
+char *filename = NULL;
+buffer *filedata, *cutbuffer = NULL;
+int fix_mode = FALSE;
+int look_mode = FALSE;
+int eager_mode = FALSE;
+int insert_mode = FALSE;
+int edit_type = 1;                    /* 1,2 are hex digits, 0=ascii */
+int finished = FALSE;
+int marking = FALSE;
+int modified = FALSE;
+int new_file = FALSE;                 /* shouldn't need initialisation -
+                                       * but let's not take chances :-) */
+int width = 16;
+int realoffset = 0, offset = 16;
+
+int ascii_enabled = TRUE;
+
+long file_size = 0, top_pos = 0, cur_pos = 0, mark_point = 0;
+
+int scrlines;
+
+/*
+ * Main program
+ */
+int main(int argc, char **argv) {
+    int newoffset = -1, newwidth = -1;
+
+    /*
+     * Parse command line arguments
+     */
+    pname = *argv;                    /* program name */
+    if (argc < 2) {
+       fprintf(stderr,
+               "usage: %s [-f] [-l] [-e] filename\n"
+               "    or %s -D to write default tweak.rc to stdout\n",
+               pname, pname);
+       return 0;
+    }
+
+    while (--argc > 0) {
+       char c, *p = *++argv, *value;
+
+       if (*p == '-') {
+           p++;
+           while (*p) switch (c = *p++) {
+             case 'o': case 'O':
+             case 'w': case 'W':
+               /*
+                * these parameters require arguments
+                */
+               if (*p)
+                   value = p, p = "";
+               else if (--argc)
+                   value = *++argv;
+               else {
+                   fprintf(stderr, "%s: option `-%c' requires an argument\n",
+                           pname, c);
+                   return 1;
+               }
+               switch (c) {
+                 case 'o': case 'O':
+                   newoffset = strtol(value, NULL, 0);   /* allow `0xXX' */
+                   break;
+                 case 'w': case 'W':
+                   newwidth = strtol(value, NULL, 0);
+                   break;
+               }
+               break;
+             case 'f': case 'F':
+               fix_mode = TRUE;
+               break;
+             case 'l': case 'L':
+               look_mode = TRUE;
+               break;
+             case 'e': case 'E':
+               eager_mode = TRUE;
+               break;
+             case 'D':
+               write_default_rc();
+               return 0;
+               break;
+           }
+       } else {
+           if (filename) {
+               fprintf(stderr, "%s: multiple filenames specified\n", pname);
+               return 1;
+           }
+           filename = p;
+       }
+    }
+
+    if (!filename) {
+       fprintf(stderr, "%s: no filename specified\n", pname);
+       return 1;
+    }
+
+    read_rc();
+    if (newoffset != -1)
+       realoffset = newoffset;
+    if (newwidth != -1)
+       width = newwidth;
+    load_file (filename);
+    init();
+    fix_offset();
+    do {
+       draw_scr ();
+       proc_key ();
+    } while (!finished);
+    done();
+
+    return 0;
+}
+
+/*
+ * Fix up `offset' to match `realoffset'. Also, while we're here,
+ * enable or disable ASCII mode and sanity-check the width.
+ */
+void fix_offset(void) {
+    if (3*width+11 > display_cols) {
+       width = (display_cols-11) / 3;
+       sprintf (message, "Width reduced to %d to fit on the screen", width);
+    }
+    if (4*width+14 > display_cols) {
+       ascii_enabled = FALSE;
+       if (edit_type == 0)
+           edit_type = 1;             /* force to hex mode */
+    } else
+       ascii_enabled = TRUE;
+    offset = realoffset % width;
+    if (!offset)
+       offset = width;
+}
+
+/*
+ * Initialise stuff at the beginning of the program: mostly the
+ * display.
+ */
+static void init(void) {
+    int i;
+
+    display_setup();
+
+    display_define_colour(COL_BUFFER, 7, 0);
+    display_define_colour(COL_SELECT, 0, 7);
+    display_define_colour(COL_STATUS, 11, 4);
+    display_define_colour(COL_ESCAPE, 9, 0);
+    display_define_colour(COL_INVALID, 11, 0);
+
+    for (i=0; i<256; i++) {
+       sprintf(hex[i], "%02X", i);
+       toprint[i] = (i>=32 && i<127 ? i : '.');
+    }
+}
+
+/*
+ * Clean up all the stuff that init() did.
+ */
+static void done(void) {
+    display_cleanup();
+}
+
+/*
+ * Load the file specified on the command line.
+ */
+static void load_file (char *fname) {
+    FILE *fp;
+
+    file_size = 0;
+    if ( (fp = fopen (fname, "rb")) ) {
+       if (eager_mode) {
+           long len;
+           static char buffer[4096];
+
+           filedata = buf_new_empty();
+
+           file_size = 0;
+
+           /*
+            * We've opened the file. Load it.
+            */
+           while ( (len = fread (buffer, 1, sizeof(buffer), fp)) > 0 ) {
+               buf_insert_data (filedata, buffer, len, file_size);
+               file_size += len;
+           }
+           fclose (fp);
+           assert(file_size == buf_length(filedata));
+           sprintf(message, "loaded %s (size %ld == 0x%lX).",
+                   fname, file_size, file_size);
+       } else {
+           filedata = buf_new_from_file(fp);
+           file_size = buf_length(filedata);
+           sprintf(message, "opened %s (size %ld == 0x%lX).",
+                   fname, file_size, file_size);
+       }
+       new_file = FALSE;
+    } else {
+       if (look_mode || fix_mode) {
+           fprintf(stderr, "%s: file %s not found, and %s mode active\n",
+                   pname, fname, (look_mode ? "LOOK" : "FIX"));
+           exit (1);
+       }
+       filedata = buf_new_empty();
+       sprintf(message, "New file %s.", fname);
+       new_file = TRUE;
+    }
+}
+
+/*
+ * Save the file. Return TRUE on success, FALSE on error.
+ */
+int save_file (void) {
+    FILE *fp;
+    long pos = 0;
+
+    if (look_mode)
+       return FALSE;                  /* do nothing! */
+
+    if ( (fp = fopen (filename, "wb")) ) {
+       static char buffer[SAVE_BLKSIZ];
+
+       while (pos < file_size) {
+           long size = file_size - pos;
+           if (size > SAVE_BLKSIZ)
+               size = SAVE_BLKSIZ;
+
+           buf_fetch_data (filedata, buffer, size, pos);
+           if (size != fwrite (buffer, 1, size, fp)) {
+               fclose (fp);
+               return FALSE;
+           }
+           pos += size;
+       }
+    } else
+       return FALSE;
+    fclose (fp);
+    return TRUE;
+}
+
+/*
+ * Make a backup of the file, if such has not already been done.
+ * Return TRUE on success, FALSE on error.
+ */
+int backup_file (void) {
+    char backup_name[FILENAME_MAX];
+
+    if (new_file)
+       return TRUE;                   /* unnecessary - pretend it's done */
+    strcpy (backup_name, filename);
+#if defined(unix) && !defined(GO32)
+    strcat (backup_name, ".bak");
+#elif defined(MSDOS)
+    {
+       char *p, *q;
+
+       q = NULL;
+       for (p = backup_name; *p; p++) {
+           if (*p == '\\')
+               q = NULL;
+           else if (*p == '.')
+               q = p;
+       }
+       if (!q)
+           q = p;
+       strcpy (q, ".BAK");
+    }
+#endif
+    remove (backup_name);             /* don't care if this fails */
+    return !rename (filename, backup_name);
+}
+
+static unsigned char *scrbuf = NULL;
+static int scrbuflines = 0;
+
+/*
+ * Draw the screen, for normal usage.
+ */
+void draw_scr (void) {
+    int scrsize, scroff, llen, i, j;
+    long currpos;
+    int marktop, markbot, mark;
+    char *p;
+    unsigned char c, *q;
+    char *linebuf;
+
+    scrlines = display_rows - 2;
+    if (scrlines > scrbuflines) {
+       scrbuf = (scrbuf ?
+                 realloc(scrbuf, scrlines*width) :
+                 malloc(scrlines*width));
+       if (!scrbuf) {
+           done();
+           fprintf(stderr, "%s: out of memory!\n", pname);
+           exit (2);
+       }
+       scrbuflines = scrlines;
+    }
+
+    linebuf = malloc(width*4+20);
+    if (!linebuf) {
+       done();
+       fprintf(stderr, "%s: out of memory!\n", pname);
+       exit (2);
+    }
+    memset (linebuf, ' ', width*4+13);
+    linebuf[width*4+13] = '\0';
+
+    if (top_pos == 0)
+       scroff = width - offset;
+    else
+       scroff = 0;
+    scrsize = scrlines * width - scroff;
+    if (scrsize > file_size - top_pos)
+       scrsize = file_size - top_pos;
+
+    buf_fetch_data (filedata, scrbuf, scrsize, top_pos);
+
+    scrsize += scroff;                /* hack but it'll work */
+
+    mark = marking && (cur_pos != mark_point);
+    if (mark) {
+       if (cur_pos > mark_point)
+           marktop = mark_point, markbot = cur_pos;
+       else
+           marktop = cur_pos, markbot = mark_point;
+    } else
+       marktop = markbot = 0;         /* placate gcc */
+
+    currpos = top_pos;
+    q = scrbuf;
+
+    for (i=0; i<scrlines; i++) {
+       display_moveto (i, 0);
+       if (currpos<=cur_pos || currpos<file_size) {
+           p = hex[(currpos >> 24) & 0xFF];
+           linebuf[0]=p[0];
+           linebuf[1]=p[1];
+           p = hex[(currpos >> 16) & 0xFF];
+           linebuf[2]=p[0];
+           linebuf[3]=p[1];
+           p = hex[(currpos >> 8) & 0xFF];
+           linebuf[4]=p[0];
+           linebuf[5]=p[1];
+           p = hex[currpos & 0xFF];
+           linebuf[6]=p[0];
+           linebuf[7]=p[1];
+           for (j=0; j<width; j++) {
+               if (scrsize > 0) {
+                   if (currpos == 0 && j < width-offset)
+                       p = "  ", c = ' ';
+                   else
+                       p = hex[*q], c = *q++;
+                   scrsize--;
+               } else {
+                   p = "  ", c = ' ';
+               }
+               linebuf[11+3*j]=p[0];
+               linebuf[12+3*j]=p[1];
+               linebuf[13+3*width+j]=toprint[c];
+           }
+           llen = (currpos ? width : offset);
+           if (mark && currpos<markbot && currpos+llen>marktop) {
+               /*
+                * Some of this line is marked. Maybe all. Whatever
+                * the precise details, there will be two regions
+                * requiring highlighting: a hex bit and an ascii
+                * bit.
+                */
+               int localstart= (currpos<marktop?marktop:currpos) - currpos;
+               int localstop = (currpos+llen>markbot ? markbot :
+                                currpos+llen) - currpos;
+               localstart += width-llen;
+               localstop += width-llen;
+               display_write_chars(linebuf, 11+3*localstart);
+               display_set_colour(COL_SELECT);
+               display_write_chars(linebuf+11+3*localstart,
+                                  3*(localstop-localstart)-1);
+               display_set_colour(COL_BUFFER);
+               if (ascii_enabled) {
+                   display_write_chars(linebuf+10+3*localstop,
+                                      3+3*width+localstart-3*localstop);
+                   display_set_colour(COL_SELECT);
+                   display_write_chars(linebuf+13+3*width+localstart,
+                                      localstop-localstart);
+                   display_set_colour(COL_BUFFER);
+                   display_write_chars(linebuf+13+3*width+localstop,
+                                      width-localstop);
+               } else {
+                   display_write_chars(linebuf+10+3*localstop,
+                                      2+3*width-3*localstop);
+               }
+           } else
+               display_write_chars(linebuf,
+                                  ascii_enabled ? 13+4*width : 10+3*width);
+       }
+       currpos += (currpos ? width : offset);
+       display_clear_to_eol();
+    }
+
+    {
+       char status[80];
+       int slen;
+       display_moveto (display_rows-2, 0);
+       display_set_colour(COL_STATUS);
+       sprintf(status, statfmt,
+               (modified ? "**" : "  "),
+               filename,
+               (insert_mode ? "(Insert)" :
+                look_mode ? "(LOOK)  " :
+                fix_mode ? "(FIX)   " : "(Ovrwrt)"),
+               cur_pos, file_size);
+       slen = strlen(status);
+       if (slen > display_cols)
+           slen = display_cols;
+       display_write_chars(status, slen);
+       while (slen++ < display_cols)
+           display_write_str(" ");
+       display_set_colour(COL_BUFFER);
+    }
+
+    display_moveto (display_rows-1, 0);
+    display_write_str (message);
+    display_clear_to_eol();
+    message[0] = '\0';
+
+    i = cur_pos - top_pos;
+    if (top_pos == 0)
+       i += width - offset;
+    j = (edit_type ? (i%width)*3+10+edit_type : (i%width)+13+3*width);
+    if (j >= display_cols)
+       j = display_cols-1;
+    free (linebuf);
+    display_moveto (i/width, j);
+    display_refresh ();
+}
+
+/*
+ * Get a string, in the "minibuffer". Return TRUE on success, FALSE
+ * on break. Possibly syntax-highlight the entered string for
+ * backslash-escapes, depending on the "highlight" parameter.
+ */
+int get_str (char *prompt, char *buf, int highlight) {
+    int maxlen = 79 - strlen(prompt);  /* limit to 80 - who cares? :) */
+    int len = 0;
+    int c;
+
+    for (EVER) {
+       display_moveto (display_rows-1, 0);
+       display_set_colour (COL_MINIBUF);
+       display_write_str (prompt);
+       if (highlight) {
+           char *q, *p = buf, *r = buf+len;
+           while (p<r) {
+               q = p;
+               if (*p == '\\') {
+                   p++;
+                   if (p<r && *p == '\\')
+                       p++, display_set_colour(COL_ESCAPE);
+                   else if (p>=r || !isxdigit (*p))
+                       display_set_colour(COL_INVALID);
+                   else if (p+1>=r || !isxdigit (p[1]))
+                       p++, display_set_colour(COL_INVALID);
+                   else
+                       p+=2, display_set_colour(COL_ESCAPE);
+               } else {
+                   while (p<r && *p != '\\')
+                       p++;
+                   display_set_colour (COL_MINIBUF);
+               }
+               display_write_chars (q, p-q);
+           }
+       } else
+           display_write_chars (buf, len);
+       display_set_colour (COL_MINIBUF);
+       display_clear_to_eol();
+       display_refresh();
+       if (update_required)
+           update();
+       safe_update = TRUE;
+       c = display_getkey();
+       safe_update = FALSE;
+       if (c == 13 || c == 10) {
+           buf[len] = '\0';
+           return TRUE;
+       } else if (c == 27 || c == 7) {
+           display_beep();
+           display_post_error();
+           strcpy (message, "User Break!");
+           return FALSE;
+       }
+
+       if (c >= 32 && c <= 126) {
+           if (len < maxlen)
+               buf[len++] = c;
+           else
+               display_beep();
+       }
+
+       if ((c == 127 || c == 8) && len > 0)
+           len--;
+
+       if (c == 'U'-'@')              /* ^U kill line */
+           len = 0;
+    }
+}
+
+/*
+ * Take a buffer containing possible backslash-escapes, and return
+ * a buffer containing a (binary!) string. Since the string is
+ * binary, it cannot be null terminated: hence the length is
+ * returned from the function. The string is processed in place.
+ * 
+ * Escapes are simple: a backslash followed by two hex digits
+ * represents that character; a doubled backslash represents a
+ * backslash itself; a backslash followed by anything else is
+ * invalid. (-1 is returned if an invalid sequence is detected.)
+ */
+int parse_quoted (char *buffer) {
+    char *p, *q;
+
+    p = q = buffer;
+    while (*p) {
+       while (*p && *p != '\\')
+           *q++ = *p++;
+       if (*p == '\\') {
+           p++;
+           if (*p == '\\')
+               *q++ = *p++;
+           else if (p[1] && isxdigit(*p) && isxdigit(p[1])) {
+               char buf[3];
+               buf[0] = *p++;
+               buf[1] = *p++;
+               buf[2] = '\0';
+               *q++ = strtol(buf, NULL, 16);
+           } else
+               return -1;
+       }
+    }
+    return q - buffer;
+}
+
+/*
+ * Suspend program. (Or shell out, depending on OS, of course.)
+ */
+void suspend(void) {
+#if defined(unix) && !defined(GO32)
+    done();
+    raise (SIGTSTP);
+    init();
+#elif defined(MSDOS)
+    done();
+    spawnl (P_WAIT, getenv("COMSPEC"), "", NULL);
+    init();
+#else
+    display_beep();
+    strcpy(message, "Suspend function not yet implemented.");
+#endif
+}
+
+volatile int safe_update, update_required;
+
+void update (void) {
+    display_recheck_size();
+    fix_offset ();
+    draw_scr ();
+}
+
+void schedule_update(void) {
+    if (safe_update)
+       update();
+    else
+       update_required = TRUE;
+}
+
+long parse_num (char *buffer, int *error) {
+    if (error)
+       *error = FALSE;
+    if (!buffer[strspn(buffer, "0123456789")]) {
+       /* interpret as decimal */
+       return atoi(buffer);
+    } else if (buffer[0]=='0' && (buffer[1]=='X' || buffer[1]=='x') &&
+              !buffer[2+strspn(buffer+2,"0123456789ABCDEFabcdef")]) {
+       return strtol(buffer+2, NULL, 16);
+    } else if (buffer[0]=='$' &&
+              !buffer[1+strspn(buffer+1,"0123456789ABCDEFabcdef")]) {
+       return strtol(buffer+1, NULL, 16);
+    } else {
+       return 0;
+       if (error)
+           *error = TRUE;
+    }
+}
diff --git a/manpage.but b/manpage.but
new file mode 100644 (file)
index 0000000..d38816a
--- /dev/null
@@ -0,0 +1,277 @@
+\cfg{man-identity}{tweak}{1}{2004-11-05}{Simon Tatham}{Simon Tatham}
+\cfg{man-mindepth}{1}
+
+\C{tweak-manpage} Man page for \cw{tweak}
+
+\H{tweak-manpage-name} NAME
+
+\cw{tweak} - efficient hex editor
+
+\H{tweak-manpage-synopsis} SYNOPSIS
+
+\c tweak [-l | -f] [-e] [-w width] [-o offset] filename
+\e bbbbb  bb   bb   bb   bb iiiii   bb iiiiii  iiiiiiii
+
+\H{tweak-manpage-description} DESCRIPTION
+
+\cw{tweak} is a hex editor. It allows you to edit a file at very low
+level, letting you see the full and exact binary contents of the
+file. It can be useful for modifying binary files such as
+executables, editing disk or CD images, debugging programs that
+generate binary file formats incorrectly, and many other things.
+
+Unlike simpler hex editors, \cw{tweak} possesses a fully functional
+insert mode. This is not useful when editing many of the types of
+file described above, but can be useful in other situations. Also,
+an insert mode makes it easy to use \cw{tweak} to construct new files
+from scratch.
+
+When you open a file in \cw{tweak}, you can expect to see the screen
+contents looking something like this:
+
+\c 00000000   7F 45 4C 46 01 01 01 00   .ELF....
+\c 00000008   00 00 00 00 00 00 00 00   ........
+\c 00000010   02 00 03 00 01 00 00 00   ........
+\c 00000018   D0 8E 04 08 34 00 00 00   ....4...
+\c 00000020   2C EF 01 00 00 00 00 00   ,.......
+
+The central column shows you the hexadecimal value of each byte in
+the file you are editing. The column on the right shows the ASCII
+interpretation of those bytes, where applicable. In the example
+above, the sequence \c{45 4C 46} on the first line translates into
+the ASCII upper-case letters \q{ELF}, but the subsequent sequence
+\c{01 01 01 00} does not have any printable ASCII representation and
+so the right-hand column simply prints dots.
+
+The column on the left shows the position within the file of the
+start of each row.
+
+In fact, when you start \cw{tweak}, you will usually see 16 bytes of
+the file per row, not 8 as shown above. However, this is
+configurable if your screen is narrower - or wider - than the usual
+80 columns, or if the file you are editing consists of fixed-size
+records of some other size.
+
+By default, \cw{tweak} does not load its entire input file into
+memory. Instead, it loads it \e{lazily}, reading from the file on
+disk when you request a view of a part of the file it doesn't have
+stored. When you modify the file, it stores your modifications in
+memory, but continues to refer to the original disk file for the
+parts you have not touched. This means you can edit extremely large
+files (for example, entire CD images) without difficulty; opening
+such a file is instantaneous, making modifications causes \cw{tweak}'s
+memory usage to grow with the size of the changes rather than the
+size of the whole file, and only when saving the altered version
+will \cw{tweak} have to read through the entire input file to write
+the output.
+
+However, this mode of operation has a disadvantage, which is that if
+the input file is modified by another program while \cw{tweak} is
+running, \cw{tweak}'s internal data structures will not be sufficient
+to keep track, and it is likely that the file written out will
+contain a mixture of the old and new contents of the input file.
+Therefore, you can disable this lazy loading if you need to; see the
+\cw{-e} option below.
+
+\H{tweak-manpage-options} OPTIONS
+
+This section lists the command-line options supported by \cw{tweak}.
+
+\dt \cw{-f}
+
+\dd Runs \cw{tweak} in \q{fix} mode, i.e. with the insert function
+entirely disabled. This might be useful if you are editing a file in
+which the insert mode is of no use (executables, for example, tend
+to have strong dependencies on precise file offsets which make it
+almost impossible to insert data in one without rendering it
+unusable) and you want to avoid turning it on by mistake.
+
+\dt \cw{-l}
+
+\dd Runs \cw{tweak} in \q{look} mode. In this mode \cw{tweak} does not
+allow you to modify the data at all; it becomes simply a tool for
+examining a file in detail.
+
+\dt \cw{-e}
+
+\dd Runs \cw{tweak} in \q{eager} mode. In this mode \cw{tweak} will read
+its entire input file when starting up. This causes it to take up
+more memory, but means that it has no dependency on the input file
+remaining unmodified, and other programs can alter it if they need
+to without causing trouble.
+
+\dt \cw{-w} \e{width}
+
+\dd Specifies the number of bytes \cw{tweak} will display per line.
+The default is 16, which fits neatly in an 80-column screen.
+
+\dt \cw{-o} \e{offset}
+
+\dd If this option is specified, \cw{tweak} will ensure that the given
+file offset occurs at the start of a line. For example, if you
+loaded a file using the options \cw{-w 8 -o 0x13}, you might see a
+display a bit like this:
+
+\lcont{
+
+\c 00000000                  7F 45 4C        .EL
+\c 00000003   46 01 01 01 00 00 00 00   F.......
+\c 0000000B   00 00 00 00 00 02 00 03   ........
+\c 00000013   00 01 00 00 00 D0 8E 04   ........
+\c 0000001B   08 34 00 00 00 2C EF 01   .4...,..
+
+By putting only three bytes of the file on the very first line,
+\cw{tweak} has arranged that the file offset 0x13 (19 in decimal)
+appears at the beginning of the fourth line.
+
+You might use this option if you knew you were editing a file in a
+particular format. For example, if your file contained a 53-byte
+header followed by a series of 22-byte records, you might find it
+useful to specify the options \cw{-w 22 -o 53}. This would arrange
+that after the header, each individual record of the file would
+appear on precisely one line of \cw{tweak}'s display.
+
+}
+
+\dt \cw{-D}
+
+\dd If this option is specified, \cw{tweak} will not attempt to load
+and edit a file at all, but will simply produce its default
+\cw{.tweakrc} file on standard output. This is a useful way to give
+yourself a starting point if you want to begin reconfiguring
+\cw{tweak}'s keyboard layout.
+
+\H{tweak-manpage-keys} KEYS
+
+This section describes all the editing keys supported by \cw{tweak} by
+default. The default key bindings for \cw{tweak} are basically
+Emacs-like.
+
+\S{tweak-manpage-keys-movement} Movement keys
+
+The Emacs cursor movement keys should all work, and their
+counterparts in ordinary function keys ought to work too:
+
+\b \cw{^P} and \cw{^N} go to the previous and next lines; Up and
+Down should do the same.
+
+\b \cw{^B} and \cw{^F} go back and forward one character; Left and
+Right should do the same.
+
+\b \cw{M-v} and \cw{^V} go up and down one screenful at a time; Page
+Up and Page Down should do the same.
+
+\b \cw{^A} and \cw{^E} go to the beginning and end of the line; Home
+and End should do the same.
+
+Press \cw{M-<} and \cw{M->} go to the beginning and end of the file.
+
+Press \cw{^X g} to go to a particular byte position in the file; you
+will be asked to type in the position you want. You can enter it in
+decimal, or as a hex number with \cq{0x} before it.
+
+\S{tweak-manpage-keys-editing} Editing keys
+
+Press Return to move the cursor between the hex section of the
+screen and the ASCII section.
+
+When in the hex section, you can enter hexadecimal digits to alter
+data; when in the ASCII section, you can directly type ASCII text.
+
+In ASCII mode, you can also press \cw{^Q} to literally quote the
+next input character; for example, if you want to insert a
+Control-V, you can press \cw{^Q^V} and \cw{tweak} will automatically
+insert the byte value 0x16.
+
+Press \cw{^X^I}, or the Insert key if you have one, to toggle
+between overwrite mode and insert mode. In insert mode, typing hex
+or ASCII input will insert new bytes containing the values you
+provide. Also, you can then press Backspace to delete the byte to
+the left of the cursor, or \cw{^D} or Delete to delete the byte
+under the cursor.
+
+\S{tweak-manpage-keys-cnp} Cut and paste
+
+Press \cw{^@} (this character may be generated by the key
+combination Control-@, or Control-2, or Control-Space) to mark the
+end of a selection region. After you do this, the bytes between that
+mark and the cursor will be highlighted. Press \cw{^@} again to
+abandon the selection.
+
+Press \cw{M-w} while a selection is active to copy the selected
+region into \cw{tweak}'s cut buffer.
+
+In insert mode, you also have the option of pressing \cw{^W} to
+\e{cut} the selected region completely out of the file and place it
+in the cut buffer.
+
+Finally, press \cw{^Y} to paste the cut buffer contents back into
+the file (this will overwrite or insert depending on the current
+mode).
+
+\S{tweak-manpage-keys-search} Searching
+
+Press \cw{^S} to search for a byte sequence. You will be asked to
+enter some text to search for on the bottom line of the screen. You
+can type this text in ASCII, or as a sequence of hex byte values
+prefixed with backslashes (\cw{\\}). For example, if you wanted to
+search for the byte value 5 followed by the word \q{hello}, you
+might enter \cw{\\05hello}. If you want to specify a literal
+backslash character, you can either enter it in hex (as \cw{\\5C}),
+or simply double it on input (\cw{\\\\}).
+
+Since \cw{tweak} deals in pure binary data, searches are always
+case-sensitive.
+
+\S{tweak-manpage-keys-display} Controlling the display
+
+If you press \cw{^X w}, you will be asked to enter a new display
+width. This has the same effect as passing the \cw{-w} option on the
+command line. Similarly, pressing \cw{^X o} allows you to enter a
+new display offset, equivalent to the \cw{-o} option.
+
+By default, the current file position and file size are displayed on
+\cw{tweak}'s status line in hex. If you prefer them in decimal, you
+can press \cw{^X x} or \cw{^X h} to toggle them between hex and
+decimal.
+
+\S{tweak-manpage-keys-misc} Miscellaneous
+
+Press \cw{^L} to redraw the screen and recentre the cursor. Press
+\cw{^Z} to suspend \cw{tweak} and return temporarily to the shell.
+
+Press \cw{^X^S} to save the file you are editing.
+
+Press \cw{^X^C} to exit \cw{tweak}. (If you do this with changes
+unsaved, you will be asked whether you want to save them.)
+
+\H{tweak-manpage-cfg} CONFIGURATION FILE
+
+\cw{tweak}'s keyboard bindings are configurable. It will attempt to
+read a file from your home directory called \cw{.tweakrc}, and if it
+finds one it will use the keyboard bindings described in it. If it
+does not find one, it will use its internal default bindings.
+
+Most of the directives in \cw{.tweakrc} are of the form \cq{bind
+command-name key}. For example, \cq{bind exit ^X^C}.
+Additionally, there are two other directives, \cw{width} and
+\cw{offset}, which give the default display parameters if no \cw{-w}
+and \cw{-o} options are specified.
+
+The easiest way to learn about the \cw{.tweakrc} file is to begin by
+having \cw{tweak} output its internal default one:
+
+\c tweak -D > $HOME/.tweakrc
+
+Then you can read the default file, learn the \cw{tweak} internal
+command names, and edit the file to do what you want.
+
+\H{tweak-manpage-bugs} BUGS
+
+Currently, \cw{tweak} cannot handle files larger than 2 gigabytes.
+
+There is no reverse search function.
+
+This man page probably ought to contain an explicit list of internal
+command names, rather than simply referring you to the default
+\cw{.tweakrc}.
diff --git a/rcfile.c b/rcfile.c
new file mode 100644 (file)
index 0000000..146c8c2
--- /dev/null
+++ b/rcfile.c
@@ -0,0 +1,330 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "tweak.h"
+
+#if defined(unix) && !defined(GO32)
+#define RCNAME ".tweakrc"
+#elif defined(MSDOS)
+#define RCNAME "tweak.rc"
+#endif
+
+static char *default_rc[] = {
+    "# Default "RCNAME" generated by `tweak -D'.",
+    "#",
+    "# Key bindings: movement keys",
+    "bind top-of-file ^[<",
+#if defined(unix) && !defined(GO32)
+    "bind page-up ^[[5~",
+#elif defined(MSDOS)
+    "bind page-up ^@I",
+    "bind page-up ^@/",
+#endif
+    "bind page-up ^[V",
+    "bind page-up ^[v",
+    "bind move-up ^P",
+#if defined(unix) && !defined(GO32)
+    "bind move-up ^[[A",
+#elif defined(MSDOS)
+    "bind move-up ^@H",
+#endif
+    "bind begin-line ^A",
+#if defined(unix) && !defined(GO32)
+    "bind begin-line ^[[H",
+    "bind begin-line ^[[1~",
+#elif defined(MSDOS)
+    "bind begin-line ^@G",
+#endif
+    "bind move-left ^B",
+#if defined(unix) && !defined(GO32)
+    "bind move-left ^[[D",
+#elif defined(MSDOS)
+    "bind move-left ^@K",
+#endif
+    "bind move-right ^F",
+#if defined(unix) && !defined(GO32)
+    "bind move-right ^[[C",
+#elif defined(MSDOS)
+    "bind move-right ^@M",
+#endif
+    "bind end-line ^E",
+#if defined(unix) && !defined(GO32)
+    "bind end-line ^[Ow",
+    "bind end-line ^[[4~",
+#elif defined(MSDOS)
+    "bind end-line ^@O",
+#endif
+    "bind move-down ^N",
+#if defined(unix) && !defined(GO32)
+    "bind move-down ^[[B",
+#elif defined(MSDOS)
+    "bind move-down ^@P",
+#endif
+    "bind page-down ^V",
+#if defined(unix) && !defined(GO32)
+    "bind page-down ^[[6~",
+#elif defined(MSDOS)
+    "bind page-down ^@Q",
+#endif
+    "bind bottom-of-file ^[>",
+    "",
+    "# Key bindings: miscellaneous editing keys",
+    "bind toggle-insert ^X^I",
+#if defined(unix) && !defined(GO32)
+    "bind toggle-insert ^[[2~",
+#elif defined(MSDOS)
+    "bind toggle-insert ^@R",
+#endif
+    "bind change-mode ^M",
+    "bind change-mode ^J",
+    "bind quote-next ^Q",
+    "bind toggle-status ^XH",
+    "bind toggle-status ^Xh",
+    "bind toggle-status ^XX",
+    "bind toggle-status ^Xx",
+    "",
+    "# Key bindings: deletion keys",
+    "bind delete-left ^?",
+    "bind delete-left ^H",
+    "bind delete-right ^D",
+#if defined(unix) && !defined(GO32)
+    "bind delete-right ^[[3~",
+#elif defined(MSDOS)
+    "bind delete-right ^@S",
+#endif
+    "",
+    "# Key bindings: cut and paste keys",
+#if defined(unix) && !defined(GO32)
+    "bind mark-place ^@",
+#elif defined(MSDOS)
+    "bind mark-place ^@^C",
+#endif
+    "bind cut ^W",
+    "bind copy ^[W",
+    "bind copy ^[w",
+#ifdef MSDOS
+    "bind copy ^@^Q",
+#endif
+    "bind paste ^Y",
+    "",
+    "# Key bindings: additional movement keys",
+    "bind search ^S",
+    "bind goto-position ^XG",
+    "bind goto-position ^Xg",
+    "bind screen-recentre ^L",
+    "",
+    "# Standard screen size parameters, plus keybindings to alter them",
+    "width 16",
+    "offset 0",
+    "bind new-width ^XW",
+    "bind new-width ^Xw",
+    "bind new-offset ^XO",
+    "bind new-offset ^Xo",
+    "",
+    "# Key bindings: overall program/file control",
+    "bind suspend ^Z",
+    "bind exit ^X^C",
+    "bind save-file ^X^S",
+    "# unbound by default: exit-and-save",
+    "",
+#ifdef TEST_BUFFER
+    "bind diagnostics ^X^D",
+    "",
+#endif
+    "# End of default "RCNAME,
+    NULL
+};
+
+extern char *pname;
+
+void read_rc (void) {
+    FILE *fp;
+    char **p, *q, *r, *s, *keyseq;
+    char rcbuffer[256];
+    char rcname[FILENAME_MAX];
+    int lineno = 0;
+    int errors = FALSE, errors_here;
+
+#if defined(unix) && !defined(GO32)
+    rcname[0] = '\0';
+    if (getenv("HOME"))
+       strcpy (rcname, getenv("HOME"));
+    strcat (rcname, "/.tweakrc");
+#elif defined(MSDOS)
+    /*
+     * Use environment variable TWEAKRC if set. Otherwise, look for
+     * TWEAK.RC in the same directory as TWEAK.EXE, if _that_ exists,
+     * and failing everything else, try C:\TWEAK\TWEAK.RC.
+     */
+    if (getenv("TWEAKRC"))
+       strcpy (rcname, getenv("TWEAKRC"));
+    else {
+       if ( (q = strrchr(pname, '\\')) != NULL) {
+           FILE *tempfp;
+
+           strncpy (rcname, pname, q+1-pname);
+           strcpy (rcname+(q+1-pname), "TWEAK.RC");
+           if ( (tempfp = fopen(rcname, "r")) != NULL)
+               fclose (tempfp);
+           else
+               strcpy (rcname, "C:\\TWEAK\\TWEAK.RC");
+       } else
+           strcpy (rcname, "C:\\TWEAK\\TWEAK.RC");
+    }
+#endif
+
+    {                                 /* easy keybindings: self inserts */
+       int i;
+       char c;
+       for (i=32; i<127; i++) {
+           c = i;
+           bind_key (&c, 1, act_self_ins);
+       }
+    }
+
+    fp = fopen(rcname, "r");
+    p = default_rc;
+    for (EVER) {
+       if (fp) {
+           if (!fgets(rcbuffer, sizeof(rcbuffer), fp)) {
+               fclose (fp);
+               break;
+           }
+           rcbuffer[strcspn(rcbuffer, "\r\n")] = '\0';
+       } else {
+           if (!*p)
+               break;
+           strcpy (rcbuffer, *p++);
+       }
+       lineno++;
+       errors_here = FALSE;
+
+       /*
+        * Now we have a line from the .rc file, wherever it's
+        * really come from. Process it.
+        */
+       q = rcbuffer;
+       while (*q && isspace(*q))
+           q++;
+
+       if (!*q || *q == '#')
+           continue;                  /* comment or blank line */
+
+       r = q;
+       while (*r && !isspace(*r))
+           r++;
+       if (*r)
+           *r++ = '\0';
+
+       /*
+        * Now "q" points to the command word, "r" to the rest of
+        * the line.
+        */
+       if (!strcmp(q, "bind")) {
+           /*
+            * It's a "bind" directive. The rest of the line should
+            * consist of an action name, then a single whitespace
+            * character, then a key sequence.
+            */
+           keyact action;
+
+           while (*r && isspace(*r))
+               r++;
+
+           q = r;
+           while (*q && !isspace(*q))
+               q++;
+           if (*q)
+               *q++ = '\0';
+           else {
+               fprintf(stderr, "%s: no key sequence after \"bind\" command"
+                       " on line %d of "RCNAME, pname, lineno);
+               errors = TRUE;
+               continue;
+           }
+
+           /*
+            * "r" points to the action name; "q" to the key sequence.
+            */
+           keyseq = s = q;
+           while (*q) {
+               if (*q == '^') {
+                   if (!*++q) {
+                       fprintf(stderr, "%s: nothing follows `^' on line %d"
+                               " of "RCNAME, pname, lineno);
+                       errors = TRUE;
+                       errors_here = TRUE;
+                   } else {
+                       *s++ = *q++ ^ 0x40;
+                   }
+               } else if (*q == '\\') {
+                   if (!*++q) {
+                       fprintf(stderr, "%s: nothing follows `\\' on line %d"
+                               " of "RCNAME, pname, lineno);
+                       errors = TRUE;
+                       errors_here = TRUE;
+                   } else if (*q == '\\' || *q == '^') {
+                       *s++ = *q++;
+                   } else if (isxdigit(*q) && q[1] && isxdigit(q[1])) {
+                       char buf[3];
+                       buf[0] = *q++;
+                       buf[1] = *q++;
+                       buf[2] = '\0';
+                       *s++ = strtol (buf, NULL, 16);
+                   } else {
+                       fprintf(stderr, "%s: badly formed `\\' sequence on"
+                               " line %d of "RCNAME, pname, lineno);
+                       errors = TRUE;
+                       errors_here = TRUE;
+                   }
+               } else
+                   *s++ = *q++;
+           }
+           if (errors_here)
+               continue;
+
+           if (!strcmp(r, "quote-next")) {
+               /*
+                * The "quote next" sequence requires special
+                * treatment.
+                */
+               int i;
+
+               for (i=0; i<256; i++) {
+                   *s = i;
+                   bind_key (keyseq, s-keyseq+1, act_self_ins);
+               }
+           } else if ( (action = parse_action (r)) ) {
+               /*
+                * An ordinary action, requiring ordinary treatment.
+                */
+               bind_key (keyseq, s-keyseq, action);
+           } else {
+               fprintf(stderr, "%s: unrecognised key action \"%s\""
+                       " at line %d of "RCNAME"\n",
+                       pname, r, lineno);
+               errors = TRUE;
+           }
+       } else if (!strcmp(q, "width")) {
+           width = atoi(r);
+       } else if (!strcmp(q, "offset")) {
+           realoffset = atoi(r);
+       } else {
+           fprintf(stderr, "%s: unrecognised "RCNAME" directive \"%s\""
+                   " at line %d of "RCNAME"\n",
+                   pname, q, lineno);
+           errors = TRUE;
+       }
+    }
+    if (errors)
+       exit(1);
+}
+
+void write_default_rc (void) {
+    char **p;
+
+    for (p = default_rc; *p; p++)
+       puts (*p);
+}
diff --git a/search.c b/search.c
new file mode 100644 (file)
index 0000000..9616f64
--- /dev/null
+++ b/search.c
@@ -0,0 +1,49 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tweak.h"
+
+static DFA dfa = NULL;
+static char *tmp = NULL;
+static int dfa_size = 0, dfa_len = 0;
+
+DFA build_dfa (char *pattern, int len) {
+    int i, j, k, b;
+
+    if (dfa_size < len) {
+       dfa_size = len;
+       dfa = (dfa ? realloc(dfa, dfa_size * sizeof(*dfa)) :
+              malloc(dfa_size * sizeof(*dfa)));
+       if (!dfa)
+           return NULL;
+       tmp = (tmp ? realloc(tmp, dfa_size) : malloc(dfa_size));
+       if (!tmp)
+           return NULL;
+    }
+
+    memcpy (tmp, pattern, len);
+
+    for (i=len; i-- ;) {
+       j = i+1;
+       for (b=0; b<256; b++) {
+           dfa[i][b] = 0;
+           if (memchr(pattern, b, len)) {
+               tmp[j-1] = b;
+               for (k=1; k<=j; k++)
+                   if (!memcmp(tmp+j-k, pattern, k))
+                       dfa[i][b] = k;
+           }
+       }
+    }
+    dfa_len = len;
+    return dfa;
+}
+
+DFA last_dfa (void) {
+    return dfa;
+}
+
+int last_len (void) {
+    return dfa_len;
+}
diff --git a/slang.c b/slang.c
new file mode 100644 (file)
index 0000000..0dca727
--- /dev/null
+++ b/slang.c
@@ -0,0 +1,158 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#if defined(unix) && !defined(GO32)
+#include <signal.h>
+#include <sys/ioctl.h>
+#endif
+
+#include <slang.h>
+
+#include "tweak.h"
+
+#if defined(unix) && !defined(GO32)
+static int sigwinch (int sigtype)
+{
+    extern void schedule_update(void);
+    schedule_update();
+    signal (SIGWINCH, (void *) sigwinch);
+    return 0;
+}
+#endif
+
+int display_rows, display_cols;
+
+void display_beep(void)
+{
+    SLtt_beep();
+}
+
+static void get_screen_size (void) {
+    int r = 0, c = 0;
+
+#ifdef TIOCGWINSZ
+    struct winsize wind_struct;
+
+    if ((ioctl(1,TIOCGWINSZ,&wind_struct) == 0)
+       || (ioctl(0, TIOCGWINSZ, &wind_struct) == 0)
+       || (ioctl(2, TIOCGWINSZ, &wind_struct) == 0)) {
+        c = (int) wind_struct.ws_col;
+        r = (int) wind_struct.ws_row;
+    }
+#elif defined(MSDOS)
+    union REGS regs;
+
+    regs.h.ah = 0x0F;
+    int86 (0x10, &regs, &regs);
+    c = regs.h.ah;
+
+    regs.x.ax = 0x1130, regs.h.bh = 0;
+    int86 (0x10, &regs, &regs);
+    r = regs.h.dl + 1;
+#endif
+
+    if ((r <= 0) || (r > 200)) r = 24;
+    if ((c <= 0) || (c > 250)) c = 80;
+    display_rows = SLtt_Screen_Rows = r;
+    display_cols = SLtt_Screen_Cols = c;
+}
+
+void display_setup(void)
+{
+    SLtt_get_terminfo();
+
+    if (SLang_init_tty (ABORT, 1, 0) == -1) {
+       fprintf(stderr, "tweak: SLang_init_tty: returned error code\n");
+       exit (1);
+    }
+    SLang_set_abort_signal (NULL);
+    SLtt_Use_Ansi_Colors = TRUE;
+
+    get_screen_size ();
+    if (SLsmg_init_smg () < 0) {
+       fprintf(stderr, "tweak: SLsmg_init_smg: returned error code\n");
+       SLang_reset_tty ();
+       exit (1);
+    }
+
+#if defined(unix) && !defined(GO32)
+    signal (SIGWINCH, (void *) sigwinch);
+#endif
+}
+
+void display_cleanup(void)
+{
+    SLsmg_reset_smg ();
+    SLang_reset_tty ();
+}
+
+void display_moveto(int y, int x)
+{
+    SLsmg_gotorc(y, x);
+}
+
+void display_refresh(void)
+{
+    SLsmg_refresh();
+}
+
+void display_write_str(char *str)
+{
+    SLsmg_write_nchars(str, strlen(str));
+}
+
+void display_write_chars(char *str, int len)
+{
+    SLsmg_write_nchars(str, len);
+}
+
+void display_define_colour(int colour, int fg, int bg)
+{
+    static char *colours[16] = {
+       "black", "red", "green", "brown",
+       "blue", "magenta", "cyan", "lightgray",
+       "gray", "brightred", "brightgreen", "yellow",
+       "brightblue", "brightmagenta", "brightcyan", "white",
+    };
+    char cname[40];
+
+    sprintf(cname, "colour%d", colour);
+
+    SLtt_set_color(colour, cname, colours[fg], colours[bg]);
+}
+
+void display_set_colour(int colour)
+{
+    SLsmg_set_color(colour);
+}
+
+void display_clear_to_eol(void)
+{
+    SLsmg_erase_eol();
+}
+
+int display_getkey(void)
+{
+    return SLang_getkey();
+}
+
+int display_input_to_flush(void)
+{
+    return SLang_input_pending(0);
+}
+
+void display_post_error(void)
+{
+    SLKeyBoard_Quit = 0;
+    SLang_Error = 0;
+}
+
+void display_recheck_size(void)
+{
+    SLsmg_reset_smg ();
+    get_screen_size ();
+    SLsmg_init_smg ();
+}
diff --git a/tweak.h b/tweak.h
new file mode 100644 (file)
index 0000000..d349486
--- /dev/null
+++ b/tweak.h
@@ -0,0 +1,108 @@
+#ifndef TWEAK_TWEAK_H
+#define TWEAK_TWEAK_H
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#define EVER ;;
+
+#ifdef MSDOS
+#define ABORT 34                      /* scan code for ^G */
+#else
+#define ABORT 7                               /* character code for ^G */
+#endif
+
+#define VER "B2.99"                   /* version, must be 5 chars */
+
+#define SEARCH_BLK 65536              /* so can this */
+#define SAVE_BLKSIZ 32768             /* and this too */
+
+#define COL_BUFFER 0                  /* normal buffer colour */
+#define COL_SELECT 1                  /* selected-area colour */
+#define COL_STATUS 2                  /* status-line colour */
+#define COL_ESCAPE 3                  /* escape sequences in minibuffer */
+#define COL_INVALID 4                 /* invalid escape sequence in m/b */
+#define COL_MINIBUF COL_BUFFER        /* these should be the same */
+
+#define NULL4   NULL,  NULL,  NULL,  NULL
+#define NULL16  NULL4, NULL4, NULL4, NULL4
+#define NULL64  NULL16,NULL16,NULL16,NULL16
+#define NULL256 NULL64,NULL64,NULL64,NULL64
+
+typedef int (*DFA)[256];
+typedef void (*keyact) (void);
+
+typedef struct buffer buffer;
+
+extern char toprint[256], hex[256][3], message[80];
+extern char decstatus[], hexstatus[], *statfmt;
+extern char last_char, *pname, *filename;
+extern buffer *filedata, *cutbuffer;
+extern int fix_mode, look_mode, insert_mode, edit_type, finished, marking;
+extern long file_size, top_pos, cur_pos, mark_point;
+extern int scrlines, modified, new_file;
+extern int width, offset, realoffset, ascii_enabled;
+
+#ifdef unix
+extern volatile int safe_update, update_required;
+extern void update (void);
+#endif
+
+extern void fix_offset(void);
+extern long parse_num (char *buffer, int *error);
+
+extern void draw_scr (void);
+extern int backup_file (void);
+extern int save_file (void);
+
+extern void act_self_ins (void);
+extern keyact parse_action (char *);
+
+extern void proc_key (void);
+extern void bind_key (char *, int, keyact);
+
+extern DFA build_dfa (char *, int);
+extern DFA last_dfa (void);
+extern int last_len (void);
+
+extern int get_str (char *, char *, int);
+extern int parse_quoted (char *);
+extern void suspend (void);
+
+extern void read_rc (void);
+extern void write_default_rc (void);
+
+extern buffer *buf_new_empty(void);
+extern buffer *buf_new_from_file(FILE *fp);
+extern void buf_free(buffer *buf);
+
+extern void buf_insert_data(buffer *buf, void *data, int len, int pos);
+extern void buf_fetch_data(buffer *buf, void *data, int len, int pos);
+extern void buf_overwrite_data(buffer *buf, void *data, int len, int pos);
+extern void buf_delete(buffer *buf, int len, int pos);
+extern buffer *buf_cut(buffer *buf, int len, int pos);
+extern buffer *buf_copy(buffer *buf, int len, int pos);
+extern void buf_paste(buffer *buf, buffer *cutbuffer, int pos);
+extern int buf_length(buffer *buf);
+
+extern void display_setup(void);
+extern void display_cleanup(void);
+extern void display_beep(void);
+extern int display_rows, display_cols;
+extern void display_moveto(int y, int x);
+extern void display_refresh(void);
+extern void display_write_str(char *str);
+extern void display_write_chars(char *str, int len);
+extern void display_define_colour(int colour, int fg, int bg);
+extern void display_set_colour(int colour);
+extern void display_clear_to_eol(void);
+extern int display_getkey(void);
+extern int display_input_to_flush(void);
+extern void display_post_error(void);
+extern void display_recheck_size(void);
+
+#endif TWEAK_TWEAK_H