5e1dbfa29191d1004be426108980c0bd70f28d80
2 * TODO possibly after that:
4 * - Multiple buffers, multiple on-screen windows.
5 * + ^X^F to open new file
6 * + ^X^R to open new file RO
7 * + ^X b to switch buffers in a window
8 * + ^X o to switch windows
9 * + ^X 2 to split a window
10 * + ^X 1 to destroy all windows but this
11 * + ^X 0 to destroy this window
12 * + ^X ^ to enlarge this window by one line
13 * + width settings vary per buffer (aha, _that's_ why I wanted
14 * a buffer structure surrounding the raw B-tree)
15 * + hex-editor-style minibuffer for entering search terms,
16 * rather than the current rather crap one; in particular
17 * this enables pasting into the search string.
18 * + er, how exactly do we deal with the problem of saving over
19 * a file which we're maintaining references to in another
20 * buffer? The _current_ buffer can at least be sorted out by
21 * replacing it with a fresh tree containing a single
22 * file-data block, but other buffers are in trouble.
23 * * if we can rely on Unix fd semantics, this isn't too
24 * bad; we can just keep the fd open on the original file,
25 * and then the data stays around even after we rename(2)
26 * our new version over the top. Disk space usage gets
27 * silly after a few iterations, but it's better than
31 * + this actually doesn't seem _too_ horrid. For a start, one
32 * simple approach would be to clone the entire buffer B-tree
33 * every time we perform an operation! That's actually not
34 * _too_ expensive, if we maintain a limit on the number of
35 * operations we may undo.
36 * + I had also thought of cloning the tree we insert for each
37 * buf_insert_data and cloning the one removed for each
38 * buf_delete_data (both must be cloned for an overwrite),
39 * but I'm not convinced that simply cloning the entire thing
40 * isn't a superior option.
41 * + this really starts to show up the distinction between a
42 * `buffer' and a bare tree. A buffer is something which has
43 * an undo chain attached; so, in particular, the cut buffer
44 * shouldn't be one. Sort that out.
47 * + this is an extra option when running in Fix mode. It
48 * causes a change of semantics when saving: instead of
49 * constructing a new backup file and writing it over the old
50 * one, we simply seek within the original file and write out
51 * all the pieces that have changed.
52 * + Primarily useful for editing disk devices directly
53 * (yikes!), or other situations where you actually cannot
54 * create a fresh copy of the file and rename(2) it into
56 * + I had intended to suggest that in Fix mode this would be
57 * nice and easy, since every element of the buffer tree is
58 * either a literal block (needs writing) or a from-file
59 * block denoting the same file _in the same position_.
60 * However, this is not in fact the case because you can cut
61 * and paste, so it's not that easy.
62 * + So I'm forced to the conclusion that when operating in
63 * this mode, it becomes illegal to cut and paste from-file
64 * blocks: they must be loaded in full at some point.
65 * * Thinking ahead about multiple-buffer operation: it
66 * would be a bad idea to keep a from-file block
67 * referencing /dev/hda and paste it into another ordinary
68 * buffer. But _also_ it would be a bad idea to paste a
69 * from-file block referencing a file stored _on_ /dev/hda
70 * into the in-place buffer dealing with /dev/hda itself.
71 * * So I'm forced to another odd conclusion, which is that
72 * from-file blocks must be eliminated in _two_ places:
73 * when copying a cut buffer _from_ an in-place buffer,
74 * _and_ when pasting a cut buffer _into_ one.
85 #if defined(unix) && !defined(GO32)
87 #include <sys/ioctl.h>
94 static void init(void);
95 static void done(void);
96 static void load_file (char *);
98 char toprint
[256]; /* LUT: printable versions of chars */
99 char hex
[256][3]; /* LUT: binary to hex, 1 byte */
103 char decstatus
[] = "%s TWEAK "VER
": %-18.18s %s posn=%-10"OFF
"d size=%-10"OFF
"d";
104 char hexstatus
[] = "%s TWEAK "VER
": %-18.18s %s posn=0x%-8"OFF
"X size=0x%-8"OFF
"X";
105 char *statfmt
= hexstatus
;
109 char *filename
= NULL
;
110 buffer
*filedata
, *cutbuffer
= NULL
;
111 int fix_mode
= FALSE
;
112 int look_mode
= FALSE
;
113 int eager_mode
= FALSE
;
114 int insert_mode
= FALSE
;
115 int edit_type
= 1; /* 1,2 are hex digits, 0=ascii */
116 int finished
= FALSE
;
118 int modified
= FALSE
;
119 int new_file
= FALSE
; /* shouldn't need initialisation -
120 * but let's not take chances :-) */
121 fileoffset_t width
= 16;
122 fileoffset_t realoffset
= 0, offset
= 16;
124 int ascii_enabled
= TRUE
;
126 fileoffset_t file_size
= 0, top_pos
= 0, cur_pos
= 0, mark_point
= 0;
133 int main(int argc
, char **argv
) {
134 fileoffset_t newoffset
= -1, newwidth
= -1;
137 * Parse command line arguments
139 pname
= *argv
; /* program name */
142 "usage: %s [-f] [-l] [-e] filename\n"
143 " or %s -D to write default tweak.rc to stdout\n",
149 char c
, *p
= *++argv
, *value
;
153 while (*p
) switch (c
= *p
++) {
157 * these parameters require arguments
164 fprintf(stderr
, "%s: option `-%c' requires an argument\n",
170 newoffset
= parse_num(value
, NULL
);
173 newwidth
= parse_num(value
, NULL
);
193 fprintf(stderr
, "%s: multiple filenames specified\n", pname
);
201 fprintf(stderr
, "%s: no filename specified\n", pname
);
207 realoffset
= newoffset
;
210 load_file (filename
);
223 * Fix up `offset' to match `realoffset'. Also, while we're here,
224 * enable or disable ASCII mode and sanity-check the width.
226 void fix_offset(void) {
227 if (3*width
+11 > display_cols
) {
228 width
= (display_cols
-11) / 3;
229 sprintf (message
, "Width reduced to %"OFF
"d to fit on the screen", width
);
231 if (4*width
+14 > display_cols
) {
232 ascii_enabled
= FALSE
;
234 edit_type
= 1; /* force to hex mode */
236 ascii_enabled
= TRUE
;
237 offset
= realoffset
% width
;
243 * Initialise stuff at the beginning of the program: mostly the
246 static void init(void) {
251 display_define_colour(COL_BUFFER
, -1, -1, FALSE
);
252 display_define_colour(COL_SELECT
, 0, 7, TRUE
);
253 display_define_colour(COL_STATUS
, 11, 4, TRUE
);
254 display_define_colour(COL_ESCAPE
, 9, 0, FALSE
);
255 display_define_colour(COL_INVALID
, 11, 0, FALSE
);
257 for (i
=0; i
<256; i
++) {
258 sprintf(hex
[i
], "%02X", i
);
259 toprint
[i
] = (i
>=32 && i
<127 ? i
: '.');
264 * Clean up all the stuff that init() did.
266 static void done(void) {
271 * Load the file specified on the command line.
273 static void load_file (char *fname
) {
277 if ( (fp
= fopen (fname
, "rb")) ) {
280 static char buffer
[4096];
282 filedata
= buf_new_empty();
287 * We've opened the file. Load it.
289 while ( (len
= fread (buffer
, 1, sizeof(buffer
), fp
)) > 0 ) {
290 buf_insert_data (filedata
, buffer
, len
, file_size
);
294 assert(file_size
== buf_length(filedata
));
295 sprintf(message
, "loaded %s (size %"OFF
"d == 0x%"OFF
"X).",
296 fname
, file_size
, file_size
);
298 filedata
= buf_new_from_file(fp
);
299 file_size
= buf_length(filedata
);
300 sprintf(message
, "opened %s (size %"OFF
"d == 0x%"OFF
"X).",
301 fname
, file_size
, file_size
);
305 if (look_mode
|| fix_mode
) {
306 fprintf(stderr
, "%s: file %s not found, and %s mode active\n",
307 pname
, fname
, (look_mode ?
"LOOK" : "FIX"));
310 filedata
= buf_new_empty();
311 sprintf(message
, "New file %s.", fname
);
317 * Save the file. Return TRUE on success, FALSE on error.
319 int save_file (void) {
321 fileoffset_t pos
= 0;
324 return FALSE
; /* do nothing! */
326 if ( (fp
= fopen (filename
, "wb")) ) {
327 static char buffer
[SAVE_BLKSIZ
];
329 while (pos
< file_size
) {
330 fileoffset_t size
= file_size
- pos
;
331 if (size
> SAVE_BLKSIZ
)
334 buf_fetch_data (filedata
, buffer
, size
, pos
);
335 if (size
!= fwrite (buffer
, 1, size
, fp
)) {
348 * Make a backup of the file, if such has not already been done.
349 * Return TRUE on success, FALSE on error.
351 int backup_file (void) {
352 char backup_name
[FILENAME_MAX
];
355 return TRUE
; /* unnecessary - pretend it's done */
356 strcpy (backup_name
, filename
);
357 #if defined(unix) && !defined(GO32)
358 strcat (backup_name
, ".bak");
364 for (p
= backup_name
; *p
; p
++) {
375 remove (backup_name
); /* don't care if this fails */
376 return !rename (filename
, backup_name
);
379 static unsigned char *scrbuf
= NULL
;
380 static int scrbuflines
= 0;
383 * Draw the screen, for normal usage.
385 void draw_scr (void) {
386 int scrsize
, scroff
, llen
, i
, j
;
387 fileoffset_t currpos
;
388 fileoffset_t marktop
, markbot
;
394 scrlines
= display_rows
- 2;
395 if (scrlines
> scrbuflines
) {
397 realloc(scrbuf
, scrlines
*width
) :
398 malloc(scrlines
*width
));
401 fprintf(stderr
, "%s: out of memory!\n", pname
);
404 scrbuflines
= scrlines
;
407 linebuf
= malloc(width
*4+20);
410 fprintf(stderr
, "%s: out of memory!\n", pname
);
413 memset (linebuf
, ' ', width
*4+13);
414 linebuf
[width
*4+13] = '\0';
417 scroff
= width
- offset
;
420 scrsize
= scrlines
* width
- scroff
;
421 if (scrsize
> file_size
- top_pos
)
422 scrsize
= file_size
- top_pos
;
424 buf_fetch_data (filedata
, scrbuf
, scrsize
, top_pos
);
426 scrsize
+= scroff
; /* hack but it'll work */
428 mark
= marking
&& (cur_pos
!= mark_point
);
430 if (cur_pos
> mark_point
)
431 marktop
= mark_point
, markbot
= cur_pos
;
433 marktop
= cur_pos
, markbot
= mark_point
;
435 marktop
= markbot
= 0; /* placate gcc */
440 for (i
=0; i
<scrlines
; i
++) {
441 display_moveto (i
, 0);
442 if (currpos
<=cur_pos
|| currpos
<file_size
) {
443 p
= hex
[(currpos
>> 24) & 0xFF];
446 p
= hex
[(currpos
>> 16) & 0xFF];
449 p
= hex
[(currpos
>> 8) & 0xFF];
452 p
= hex
[currpos
& 0xFF];
455 for (j
=0; j
<width
; j
++) {
457 if (currpos
== 0 && j
< width
-offset
)
460 p
= hex
[*q
], c
= *q
++;
465 linebuf
[11+3*j
]=p
[0];
466 linebuf
[12+3*j
]=p
[1];
467 linebuf
[13+3*width
+j
]=toprint
[c
];
469 llen
= (currpos ? width
: offset
);
470 if (mark
&& currpos
<markbot
&& currpos
+llen
>marktop
) {
472 * Some of this line is marked. Maybe all. Whatever
473 * the precise details, there will be two regions
474 * requiring highlighting: a hex bit and an ascii
477 fileoffset_t localstart
= (currpos
<marktop ? marktop
:
479 fileoffset_t localstop
= (currpos
+llen
>markbot ? markbot
:
480 currpos
+llen
) - currpos
;
481 localstart
+= width
-llen
;
482 localstop
+= width
-llen
;
483 display_write_chars(linebuf
, 11+3*localstart
);
484 display_set_colour(COL_SELECT
);
485 display_write_chars(linebuf
+11+3*localstart
,
486 3*(localstop
-localstart
)-1);
487 display_set_colour(COL_BUFFER
);
489 display_write_chars(linebuf
+10+3*localstop
,
490 3+3*width
+localstart
-3*localstop
);
491 display_set_colour(COL_SELECT
);
492 display_write_chars(linebuf
+13+3*width
+localstart
,
493 localstop
-localstart
);
494 display_set_colour(COL_BUFFER
);
495 display_write_chars(linebuf
+13+3*width
+localstop
,
498 display_write_chars(linebuf
+10+3*localstop
,
499 2+3*width
-3*localstop
);
502 display_set_colour(COL_BUFFER
);
503 display_write_chars(linebuf
,
504 ascii_enabled ?
13+4*width
: 10+3*width
);
507 currpos
+= (currpos ? width
: offset
);
508 display_clear_to_eol();
514 display_moveto (display_rows
-2, 0);
515 display_set_colour(COL_STATUS
);
516 sprintf(status
, statfmt
,
517 (modified ?
"**" : " "),
519 (insert_mode ?
"(Insert)" :
520 look_mode ?
"(LOOK) " :
521 fix_mode ?
"(FIX) " : "(Ovrwrt)"),
523 slen
= strlen(status
);
524 if (slen
> display_cols
)
526 display_write_chars(status
, slen
);
527 while (slen
++ < display_cols
)
528 display_write_str(" ");
529 display_set_colour(COL_BUFFER
);
532 display_moveto (display_rows
-1, 0);
533 display_write_str (message
);
534 display_clear_to_eol();
537 i
= cur_pos
- top_pos
;
540 j
= (edit_type ?
(i
%width
)*3+10+edit_type
: (i
%width
)+13+3*width
);
541 if (j
>= display_cols
)
544 display_moveto (i
/width
, j
);
548 volatile int safe_update
, update_required
;
552 * Get a string, in the "minibuffer". Return TRUE on success, FALSE
553 * on break. Possibly syntax-highlight the entered string for
554 * backslash-escapes, depending on the "highlight" parameter.
556 int get_str (char *prompt
, char *buf
, int highlight
) {
557 int maxlen
= 79 - strlen(prompt
); /* limit to 80 - who cares? :) */
562 display_moveto (display_rows
-1, 0);
563 display_set_colour (COL_MINIBUF
);
564 display_write_str (prompt
);
566 char *q
, *p
= buf
, *r
= buf
+len
;
571 if (p
<r
&& *p
== '\\')
572 p
++, display_set_colour(COL_ESCAPE
);
573 else if (p
>=r
|| !isxdigit ((unsigned char)*p
))
574 display_set_colour(COL_INVALID
);
575 else if (p
+1>=r
|| !isxdigit ((unsigned char)p
[1]))
576 p
++, display_set_colour(COL_INVALID
);
578 p
+=2, display_set_colour(COL_ESCAPE
);
580 while (p
<r
&& *p
!= '\\')
582 display_set_colour (COL_MINIBUF
);
584 display_write_chars (q
, p
-q
);
587 display_write_chars (buf
, len
);
588 display_set_colour (COL_MINIBUF
);
589 display_clear_to_eol();
594 c
= display_getkey();
596 if (c
== 13 || c
== 10) {
599 } else if (c
== 27 || c
== 7) {
601 display_post_error();
602 strcpy (message
, "User Break!");
606 if (c
>= 32 && c
<= 126) {
613 if ((c
== 127 || c
== 8) && len
> 0)
616 if (c
== 'U'-'@') /* ^U kill line */
622 * Take a buffer containing possible backslash-escapes, and return
623 * a buffer containing a (binary!) string. Since the string is
624 * binary, it cannot be null terminated: hence the length is
625 * returned from the function. The string is processed in place.
627 * Escapes are simple: a backslash followed by two hex digits
628 * represents that character; a doubled backslash represents a
629 * backslash itself; a backslash followed by anything else is
630 * invalid. (-1 is returned if an invalid sequence is detected.)
632 int parse_quoted (char *buffer
) {
637 while (*p
&& *p
!= '\\')
643 else if (p
[1] && isxdigit((unsigned char)*p
) &&
644 isxdigit((unsigned char)p
[1])) {
649 *q
++ = strtol(buf
, NULL
, 16);
658 * Suspend program. (Or shell out, depending on OS, of course.)
661 #if defined(unix) && !defined(GO32)
667 spawnl (P_WAIT
, getenv("COMSPEC"), "", NULL
);
671 strcpy(message
, "Suspend function not yet implemented.");
676 display_recheck_size();
681 void schedule_update(void) {
685 update_required
= TRUE
;
688 fileoffset_t
parse_num (char *buffer
, int *error
) {
691 if (!buffer
[strspn(buffer
, "0123456789")]) {
692 /* interpret as decimal */
693 return ATOOFF(buffer
);
694 } else if (buffer
[0]=='0' && (buffer
[1]=='X' || buffer
[1]=='x') &&
695 !buffer
[2+strspn(buffer
+2,"0123456789ABCDEFabcdef")]) {
696 return STRTOOFF(buffer
+2, NULL
, 16);
697 } else if (buffer
[0]=='$' &&
698 !buffer
[1+strspn(buffer
+1,"0123456789ABCDEFabcdef")]) {
699 return STRTOOFF(buffer
+1, NULL
, 16);