diff options
Diffstat (limited to 'gc/cord/de.c')
-rw-r--r-- | gc/cord/de.c | 603 |
1 files changed, 603 insertions, 0 deletions
diff --git a/gc/cord/de.c b/gc/cord/de.c new file mode 100644 index 0000000..fda7142 --- /dev/null +++ b/gc/cord/de.c @@ -0,0 +1,603 @@ +/* + * Copyright (c) 1993-1994 by Xerox Corporation. All rights reserved. + * + * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED + * OR IMPLIED. ANY USE IS AT YOUR OWN RISK. + * + * Permission is hereby granted to use or copy this program + * for any purpose, provided the above notices are retained on all copies. + * Permission to modify the code and to distribute modified code is granted, + * provided the above notices are retained, and a notice that the code was + * modified is included with the above copyright notice. + * + * Author: Hans-J. Boehm (boehm@parc.xerox.com) + */ +/* + * A really simple-minded text editor based on cords. + * Things it does right: + * No size bounds. + * Inbounded undo. + * Shouldn't crash no matter what file you invoke it on (e.g. /vmunix) + * (Make sure /vmunix is not writable before you try this.) + * Scrolls horizontally. + * Things it does wrong: + * It doesn't handle tabs reasonably (use "expand" first). + * The command set is MUCH too small. + * The redisplay algorithm doesn't let curses do the scrolling. + * The rule for moving the window over the file is suboptimal. + */ +/* Boehm, February 6, 1995 12:27 pm PST */ + +/* Boehm, May 19, 1994 2:20 pm PDT */ +#include <stdio.h> +#include "gc.h" +#include "cord.h" + +#ifdef THINK_C +#define MACINTOSH +#include <ctype.h> +#endif + +#if defined(__BORLANDC__) && !defined(WIN32) + /* If this is DOS or win16, we'll fail anyway. */ + /* Might as well assume win32. */ +# define WIN32 +#endif + +#if defined(WIN32) +# include <windows.h> +# include "de_win.h" +#elif defined(MACINTOSH) +# include <console.h> +/* curses emulation. */ +# define initscr() +# define endwin() +# define nonl() +# define noecho() csetmode(C_NOECHO, stdout) +# define cbreak() csetmode(C_CBREAK, stdout) +# define refresh() +# define addch(c) putchar(c) +# define standout() cinverse(1, stdout) +# define standend() cinverse(0, stdout) +# define move(line,col) cgotoxy(col + 1, line + 1, stdout) +# define clrtoeol() ccleol(stdout) +# define de_error(s) { fprintf(stderr, s); getchar(); } +# define LINES 25 +# define COLS 80 +#else +# include <curses.h> +# define de_error(s) { fprintf(stderr, s); sleep(2); } +#endif +#include "de_cmds.h" + +/* List of line number to position mappings, in descending order. */ +/* There may be holes. */ +typedef struct LineMapRep { + int line; + size_t pos; + struct LineMapRep * previous; +} * line_map; + +/* List of file versions, one per edit operation */ +typedef struct HistoryRep { + CORD file_contents; + struct HistoryRep * previous; + line_map map; /* Invalid for first record "now" */ +} * history; + +history now = 0; +CORD current; /* == now -> file_contents. */ +size_t current_len; /* Current file length. */ +line_map current_map = 0; /* Current line no. to pos. map */ +size_t current_map_size = 0; /* Number of current_map entries. */ + /* Not always accurate, but reset */ + /* by prune_map. */ +# define MAX_MAP_SIZE 3000 + +/* Current display position */ +int dis_line = 0; +int dis_col = 0; + +# define ALL -1 +# define NONE - 2 +int need_redisplay = 0; /* Line that needs to be redisplayed. */ + + +/* Current cursor position. Always within file. */ +int line = 0; +int col = 0; +size_t file_pos = 0; /* Character position corresponding to cursor. */ + +/* Invalidate line map for lines > i */ +void invalidate_map(int i) +{ + while(current_map -> line > i) { + current_map = current_map -> previous; + current_map_size--; + } +} + +/* Reduce the number of map entries to save space for huge files. */ +/* This also affects maps in histories. */ +void prune_map() +{ + line_map map = current_map; + int start_line = map -> line; + + current_map_size = 0; + for(; map != 0; map = map -> previous) { + current_map_size++; + if (map -> line < start_line - LINES && map -> previous != 0) { + map -> previous = map -> previous -> previous; + } + } +} +/* Add mapping entry */ +void add_map(int line, size_t pos) +{ + line_map new_map = GC_NEW(struct LineMapRep); + + if (current_map_size >= MAX_MAP_SIZE) prune_map(); + new_map -> line = line; + new_map -> pos = pos; + new_map -> previous = current_map; + current_map = new_map; + current_map_size++; +} + + + +/* Return position of column *c of ith line in */ +/* current file. Adjust *c to be within the line.*/ +/* A 0 pointer is taken as 0 column. */ +/* Returns CORD_NOT_FOUND if i is too big. */ +/* Assumes i > dis_line. */ +size_t line_pos(int i, int *c) +{ + int j; + size_t cur; + size_t next; + line_map map = current_map; + + while (map -> line > i) map = map -> previous; + if (map -> line < i - 2) /* rebuild */ invalidate_map(i); + for (j = map -> line, cur = map -> pos; j < i;) { + cur = CORD_chr(current, cur, '\n'); + if (cur == current_len-1) return(CORD_NOT_FOUND); + cur++; + if (++j > current_map -> line) add_map(j, cur); + } + if (c != 0) { + next = CORD_chr(current, cur, '\n'); + if (next == CORD_NOT_FOUND) next = current_len - 1; + if (next < cur + *c) { + *c = next - cur; + } + cur += *c; + } + return(cur); +} + +void add_hist(CORD s) +{ + history new_file = GC_NEW(struct HistoryRep); + + new_file -> file_contents = current = s; + current_len = CORD_len(s); + new_file -> previous = now; + if (now != 0) now -> map = current_map; + now = new_file; +} + +void del_hist(void) +{ + now = now -> previous; + current = now -> file_contents; + current_map = now -> map; + current_len = CORD_len(current); +} + +/* Current screen_contents; a dynamically allocated array of CORDs */ +CORD * screen = 0; +int screen_size = 0; + +# ifndef WIN32 +/* Replace a line in the curses stdscr. All control characters are */ +/* displayed as upper case characters in standout mode. This isn't */ +/* terribly appropriate for tabs. */ +void replace_line(int i, CORD s) +{ + register int c; + CORD_pos p; + size_t len = CORD_len(s); + + if (screen == 0 || LINES > screen_size) { + screen_size = LINES; + screen = (CORD *)GC_MALLOC(screen_size * sizeof(CORD)); + } +# if !defined(MACINTOSH) + /* A gross workaround for an apparent curses bug: */ + if (i == LINES-1 && len == COLS) { + s = CORD_substr(s, 0, CORD_len(s) - 1); + } +# endif + if (CORD_cmp(screen[i], s) != 0) { + move(i, 0); clrtoeol(); move(i,0); + + CORD_FOR (p, s) { + c = CORD_pos_fetch(p) & 0x7f; + if (iscntrl(c)) { + standout(); addch(c + 0x40); standend(); + } else { + addch(c); + } + } + screen[i] = s; + } +} +#else +# define replace_line(i,s) invalidate_line(i) +#endif + +/* Return up to COLS characters of the line of s starting at pos, */ +/* returning only characters after the given column. */ +CORD retrieve_line(CORD s, size_t pos, unsigned column) +{ + CORD candidate = CORD_substr(s, pos, column + COLS); + /* avoids scanning very long lines */ + int eol = CORD_chr(candidate, 0, '\n'); + int len; + + if (eol == CORD_NOT_FOUND) eol = CORD_len(candidate); + len = (int)eol - (int)column; + if (len < 0) len = 0; + return(CORD_substr(s, pos + column, len)); +} + +# ifdef WIN32 +# define refresh(); + + CORD retrieve_screen_line(int i) + { + register size_t pos; + + invalidate_map(dis_line + LINES); /* Prune search */ + pos = line_pos(dis_line + i, 0); + if (pos == CORD_NOT_FOUND) return(CORD_EMPTY); + return(retrieve_line(current, pos, dis_col)); + } +# endif + +/* Display the visible section of the current file */ +void redisplay(void) +{ + register int i; + + invalidate_map(dis_line + LINES); /* Prune search */ + for (i = 0; i < LINES; i++) { + if (need_redisplay == ALL || need_redisplay == i) { + register size_t pos = line_pos(dis_line + i, 0); + + if (pos == CORD_NOT_FOUND) break; + replace_line(i, retrieve_line(current, pos, dis_col)); + if (need_redisplay == i) goto done; + } + } + for (; i < LINES; i++) replace_line(i, CORD_EMPTY); +done: + refresh(); + need_redisplay = NONE; +} + +int dis_granularity; + +/* Update dis_line, dis_col, and dis_pos to make cursor visible. */ +/* Assumes line, col, dis_line, dis_pos are in bounds. */ +void normalize_display() +{ + int old_line = dis_line; + int old_col = dis_col; + + dis_granularity = 1; + if (LINES > 15 && COLS > 15) dis_granularity = 2; + while (dis_line > line) dis_line -= dis_granularity; + while (dis_col > col) dis_col -= dis_granularity; + while (line >= dis_line + LINES) dis_line += dis_granularity; + while (col >= dis_col + COLS) dis_col += dis_granularity; + if (old_line != dis_line || old_col != dis_col) { + need_redisplay = ALL; + } +} + +# if defined(WIN32) +# elif defined(MACINTOSH) +# define move_cursor(x,y) cgotoxy(x + 1, y + 1, stdout) +# else +# define move_cursor(x,y) move(y,x) +# endif + +/* Adjust display so that cursor is visible; move cursor into position */ +/* Update screen if necessary. */ +void fix_cursor(void) +{ + normalize_display(); + if (need_redisplay != NONE) redisplay(); + move_cursor(col - dis_col, line - dis_line); + refresh(); +# ifndef WIN32 + fflush(stdout); +# endif +} + +/* Make sure line, col, and dis_pos are somewhere inside file. */ +/* Recompute file_pos. Assumes dis_pos is accurate or past eof */ +void fix_pos() +{ + int my_col = col; + + if ((size_t)line > current_len) line = current_len; + file_pos = line_pos(line, &my_col); + if (file_pos == CORD_NOT_FOUND) { + for (line = current_map -> line, file_pos = current_map -> pos; + file_pos < current_len; + line++, file_pos = CORD_chr(current, file_pos, '\n') + 1); + line--; + file_pos = line_pos(line, &col); + } else { + col = my_col; + } +} + +#if defined(WIN32) +# define beep() Beep(1000 /* Hz */, 300 /* msecs */) +#elif defined(MACINTOSH) +# define beep() SysBeep(1) +#else +/* + * beep() is part of some curses packages and not others. + * We try to match the type of the builtin one, if any. + */ +#ifdef __STDC__ + int beep(void) +#else + int beep() +#endif +{ + putc('\007', stderr); + return(0); +} +#endif + +# define NO_PREFIX -1 +# define BARE_PREFIX -2 +int repeat_count = NO_PREFIX; /* Current command prefix. */ + +int locate_mode = 0; /* Currently between 2 ^Ls */ +CORD locate_string = CORD_EMPTY; /* Current search string. */ + +char * arg_file_name; + +#ifdef WIN32 +/* Change the current position to whatever is currently displayed at */ +/* the given SCREEN coordinates. */ +void set_position(int c, int l) +{ + line = l + dis_line; + col = c + dis_col; + fix_pos(); + move_cursor(col - dis_col, line - dis_line); +} +#endif /* WIN32 */ + +/* Perform the command associated with character c. C may be an */ +/* integer > 256 denoting a windows command, one of the above control */ +/* characters, or another ASCII character to be used as either a */ +/* character to be inserted, a repeat count, or a search string, */ +/* depending on the current state. */ +void do_command(int c) +{ + int i; + int need_fix_pos; + FILE * out; + + if ( c == '\r') c = '\n'; + if (locate_mode) { + size_t new_pos; + + if (c == LOCATE) { + locate_mode = 0; + locate_string = CORD_EMPTY; + return; + } + locate_string = CORD_cat_char(locate_string, (char)c); + new_pos = CORD_str(current, file_pos - CORD_len(locate_string) + 1, + locate_string); + if (new_pos != CORD_NOT_FOUND) { + need_redisplay = ALL; + new_pos += CORD_len(locate_string); + for (;;) { + file_pos = line_pos(line + 1, 0); + if (file_pos > new_pos) break; + line++; + } + col = new_pos - line_pos(line, 0); + file_pos = new_pos; + fix_cursor(); + } else { + locate_string = CORD_substr(locate_string, 0, + CORD_len(locate_string) - 1); + beep(); + } + return; + } + if (c == REPEAT) { + repeat_count = BARE_PREFIX; return; + } else if (c < 0x100 && isdigit(c)){ + if (repeat_count == BARE_PREFIX) { + repeat_count = c - '0'; return; + } else if (repeat_count != NO_PREFIX) { + repeat_count = 10 * repeat_count + c - '0'; return; + } + } + if (repeat_count == NO_PREFIX) repeat_count = 1; + if (repeat_count == BARE_PREFIX && (c == UP || c == DOWN)) { + repeat_count = LINES - dis_granularity; + } + if (repeat_count == BARE_PREFIX) repeat_count = 8; + need_fix_pos = 0; + for (i = 0; i < repeat_count; i++) { + switch(c) { + case LOCATE: + locate_mode = 1; + break; + case TOP: + line = col = file_pos = 0; + break; + case UP: + if (line != 0) { + line--; + need_fix_pos = 1; + } + break; + case DOWN: + line++; + need_fix_pos = 1; + break; + case LEFT: + if (col != 0) { + col--; file_pos--; + } + break; + case RIGHT: + if (CORD_fetch(current, file_pos) == '\n') break; + col++; file_pos++; + break; + case UNDO: + del_hist(); + need_redisplay = ALL; need_fix_pos = 1; + break; + case BS: + if (col == 0) { + beep(); + break; + } + col--; file_pos--; + /* fall through: */ + case DEL: + if (file_pos == current_len-1) break; + /* Can't delete trailing newline */ + if (CORD_fetch(current, file_pos) == '\n') { + need_redisplay = ALL; need_fix_pos = 1; + } else { + need_redisplay = line - dis_line; + } + add_hist(CORD_cat( + CORD_substr(current, 0, file_pos), + CORD_substr(current, file_pos+1, current_len))); + invalidate_map(line); + break; + case WRITE: + { + CORD name = CORD_cat(CORD_from_char_star(arg_file_name), + ".new"); + + if ((out = fopen(CORD_to_const_char_star(name), "wb")) == NULL + || CORD_put(current, out) == EOF) { + de_error("Write failed\n"); + need_redisplay = ALL; + } else { + fclose(out); + } + } + break; + default: + { + CORD left_part = CORD_substr(current, 0, file_pos); + CORD right_part = CORD_substr(current, file_pos, current_len); + + add_hist(CORD_cat(CORD_cat_char(left_part, (char)c), + right_part)); + invalidate_map(line); + if (c == '\n') { + col = 0; line++; file_pos++; + need_redisplay = ALL; + } else { + col++; file_pos++; + need_redisplay = line - dis_line; + } + break; + } + } + } + if (need_fix_pos) fix_pos(); + fix_cursor(); + repeat_count = NO_PREFIX; +} + +/* OS independent initialization */ + +void generic_init(void) +{ + FILE * f; + CORD initial; + + if ((f = fopen(arg_file_name, "rb")) == NULL) { + initial = "\n"; + } else { + initial = CORD_from_file(f); + if (initial == CORD_EMPTY + || CORD_fetch(initial, CORD_len(initial)-1) != '\n') { + initial = CORD_cat(initial, "\n"); + } + } + add_map(0,0); + add_hist(initial); + now -> map = current_map; + now -> previous = now; /* Can't back up further: beginning of the world */ + need_redisplay = ALL; + fix_cursor(); +} + +#ifndef WIN32 + +main(argc, argv) +int argc; +char ** argv; +{ + int c; + +#if defined(MACINTOSH) + console_options.title = "\pDumb Editor"; + cshow(stdout); + GC_init(); + argc = ccommand(&argv); +#endif + + if (argc != 2) goto usage; + arg_file_name = argv[1]; + setvbuf(stdout, GC_MALLOC_ATOMIC(8192), _IOFBF, 8192); + initscr(); + noecho(); nonl(); cbreak(); + generic_init(); + while ((c = getchar()) != QUIT) { + if (c == EOF) break; + do_command(c); + } +done: + move(LINES-1, 0); + clrtoeol(); + refresh(); + nl(); + echo(); + endwin(); + exit(0); +usage: + fprintf(stderr, "Usage: %s file\n", argv[0]); + fprintf(stderr, "Cursor keys: ^B(left) ^F(right) ^P(up) ^N(down)\n"); + fprintf(stderr, "Undo: ^U Write to <file>.new: ^W"); + fprintf(stderr, "Quit:^D Repeat count: ^R[n]\n"); + fprintf(stderr, "Top: ^T Locate (search, find): ^L text ^L\n"); + exit(1); +} + +#endif /* !WIN32 */ |