From: simon Date: Fri, 19 Nov 2004 11:01:14 +0000 (+0000) Subject: My hex editor `axe' has existed in prototype for about ten years, X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/tweak/commitdiff_plain/6e182d98f1e2191523a4a0b8532a73819d6dca8d My hex editor `axe' has existed in prototype for about ten years, 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 --- 6e182d98f1e2191523a4a0b8532a73819d6dca8d diff --git a/Makefile b/Makefile new file mode 100644 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 index 0000000..c65c3e4 --- /dev/null +++ b/actions.c @@ -0,0 +1,655 @@ +#include +#include +#include +#include + +#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= '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 index 0000000..714de39 --- /dev/null +++ b/buffer.c @@ -0,0 +1,553 @@ +#include +#include +#include +#include +#include + +#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 index 0000000..f11d777 --- /dev/null +++ b/curses.c @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include + +#include + +#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 index 0000000..2943966 --- /dev/null +++ b/keytab.c @@ -0,0 +1,121 @@ +#include +#include +#include + +#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 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 +#include +#include +#include +#include + +#if defined(unix) && !defined(GO32) +#include +#include +#include +#elif defined(MSDOS) +#include +#include +#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> 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 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 && currposmarktop) { + /* + * 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= (currposmarkbot ? 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 || !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= 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 index 0000000..d38816a --- /dev/null +++ b/manpage.but @@ -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 index 0000000..146c8c2 --- /dev/null +++ b/rcfile.c @@ -0,0 +1,330 @@ +#include +#include +#include +#include + +#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 index 0000000..9616f64 --- /dev/null +++ b/search.c @@ -0,0 +1,49 @@ +#include +#include +#include + +#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 index 0000000..0dca727 --- /dev/null +++ b/slang.c @@ -0,0 +1,158 @@ +#include +#include +#include +#include +#include + +#if defined(unix) && !defined(GO32) +#include +#include +#endif + +#include + +#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 (); +} diff --git a/tweak.h b/tweak.h new file mode 100644 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