--- /dev/null
+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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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 ();
+}
--- /dev/null
+#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)();
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+\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}.
--- /dev/null
+#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);
+}
--- /dev/null
+#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;
+}
--- /dev/null
+#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, ®s, ®s);
+ c = regs.h.ah;
+
+ regs.x.ax = 0x1130, regs.h.bh = 0;
+ int86 (0x10, ®s, ®s);
+ 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 ();
+}
--- /dev/null
+#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