From 6e182d98f1e2191523a4a0b8532a73819d6dca8d Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 19 Nov 2004 11:01:14 +0000 Subject: [PATCH] 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 --- Makefile | 41 ++++ actions.c | 655 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ buffer.c | 553 +++++++++++++++++++++++++++++++++++++++++++++++ curses.c | 139 ++++++++++++ keytab.c | 121 +++++++++++ main.c | 699 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ manpage.but | 277 ++++++++++++++++++++++++ rcfile.c | 330 ++++++++++++++++++++++++++++ search.c | 49 +++++ slang.c | 158 ++++++++++++++ tweak.h | 108 ++++++++++ 11 files changed, 3130 insertions(+) create mode 100644 Makefile create mode 100644 actions.c create mode 100644 buffer.c create mode 100644 curses.c create mode 100644 keytab.c create mode 100644 main.c create mode 100644 manpage.but create mode 100644 rcfile.c create mode 100644 search.c create mode 100644 slang.c create mode 100644 tweak.h 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 -- 2.11.0