home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / sa104os2.zip / SATHR104.ZIP / SATHER / SYSTEM / GC / CORD / DE.C < prev    next >
C/C++ Source or Header  |  1994-09-19  |  16KB  |  593 lines

  1. /*
  2.  * Copyright (c) 1993-1994 by Xerox Corporation.  All rights reserved.
  3.  *
  4.  * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
  5.  * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
  6.  *
  7.  * Permission is hereby granted to use or copy this program
  8.  * for any purpose,  provided the above notices are retained on all copies.
  9.  * Permission to modify the code and to distribute modified code is granted,
  10.  * provided the above notices are retained, and a notice that the code was
  11.  * modified is included with the above copyright notice.
  12.  *
  13.  * Author: Hans-J. Boehm (boehm@parc.xerox.com)
  14.  */
  15. /*
  16.  * A really simple-minded text editor based on cords.
  17.  * Things it does right:
  18.  *     No size bounds.
  19.  *    Inbounded undo.
  20.  *    Shouldn't crash no matter what file you invoke it on (e.g. /vmunix)
  21.  *        (Make sure /vmunix is not writable before you try this.)
  22.  *    Scrolls horizontally.
  23.  * Things it does wrong:
  24.  *    It doesn't handle tabs reasonably (use "expand" first).
  25.  *    The command set is MUCH too small.
  26.  *    The redisplay algorithm doesn't let curses do the scrolling.
  27.  *    The rule for moving the window over the file is suboptimal.
  28.  */
  29. /* Boehm, June 13, 1994 2:35 pm PDT */
  30.  
  31. /* Boehm, May 19, 1994 2:20 pm PDT */
  32. #include <stdio.h>
  33. #include "gc.h"
  34. #include "cord.h"
  35.  
  36. #ifdef THINK_C
  37. #define MACINTOSH
  38. #include <ctype.h>
  39. #endif
  40.  
  41. #if defined(WIN32)
  42. #  include <windows.h>
  43. #  include "de_win.h"
  44. #elif defined(MACINTOSH)
  45. #    include <console.h>
  46. /* curses emulation. */
  47. #    define initscr()
  48. #    define endwin()
  49. #    define nonl()
  50. #    define noecho() csetmode(C_NOECHO, stdout)
  51. #    define cbreak() csetmode(C_CBREAK, stdout)
  52. #    define refresh()
  53. #    define addch(c) putchar(c)
  54. #    define standout() cinverse(1, stdout)
  55. #    define standend() cinverse(0, stdout)
  56. #    define move(line,col) cgotoxy(col + 1, line + 1, stdout)
  57. #    define clrtoeol() ccleol(stdout)
  58. #    define de_error(s) { fprintf(stderr, s); getchar(); }
  59. #    define LINES 25
  60. #    define COLS 80
  61. #else
  62. #  include <curses.h>
  63. #  define de_error(s) { fprintf(stderr, s); sleep(2); }
  64. #endif
  65. #include "de_cmds.h"
  66.  
  67. /* List of line number to position mappings, in descending order. */
  68. /* There may be holes.                          */
  69. typedef struct LineMapRep {
  70.     int line;
  71.     size_t pos;
  72.     struct LineMapRep * previous;
  73. } * line_map;
  74.  
  75. /* List of file versions, one per edit operation */
  76. typedef struct HistoryRep {
  77.     CORD file_contents;
  78.     struct HistoryRep * previous;
  79.     line_map map;    /* Invalid for first record "now" */
  80. } * history;
  81.  
  82. history now = 0;
  83. CORD current;        /* == now -> file_contents.    */
  84. size_t current_len;    /* Current file length.        */
  85. line_map current_map = 0;    /* Current line no. to pos. map     */
  86. size_t current_map_size = 0;    /* Number of current_map entries.    */
  87.                 /* Not always accurate, but reset    */
  88.                 /* by prune_map.            */
  89. # define MAX_MAP_SIZE 3000
  90.  
  91. /* Current display position */
  92. int dis_line = 0;
  93. int dis_col = 0;
  94.  
  95. # define ALL -1
  96. # define NONE - 2
  97. int need_redisplay = 0;    /* Line that needs to be redisplayed.    */
  98.  
  99.  
  100. /* Current cursor position. Always within file. */
  101. int line = 0; 
  102. int col = 0;
  103. size_t file_pos = 0;    /* Character position corresponding to cursor.    */
  104.  
  105. /* Invalidate line map for lines > i */
  106. void invalidate_map(int i)
  107. {
  108.     while(current_map -> line > i) {
  109.         current_map = current_map -> previous;
  110.         current_map_size--;
  111.     }
  112. }
  113.  
  114. /* Reduce the number of map entries to save space for huge files. */
  115. /* This also affects maps in histories.                  */
  116. void prune_map()
  117. {
  118.     line_map map = current_map;
  119.     int start_line = map -> line;
  120.     
  121.     current_map_size = 0;
  122.     for(; map != 0; map = map -> previous) {
  123.         current_map_size++;
  124.         if (map -> line < start_line - LINES && map -> previous != 0) {
  125.             map -> previous = map -> previous -> previous;
  126.         }
  127.     }
  128. }
  129. /* Add mapping entry */
  130. void add_map(int line, size_t pos)
  131. {
  132.     line_map new_map = GC_NEW(struct LineMapRep);
  133.     
  134.     if (current_map_size >= MAX_MAP_SIZE) prune_map();
  135.     new_map -> line = line;
  136.     new_map -> pos = pos;
  137.     new_map -> previous = current_map;
  138.     current_map = new_map;
  139.     current_map_size++;
  140. }
  141.  
  142.  
  143.  
  144. /* Return position of column *c of ith line in   */
  145. /* current file. Adjust *c to be within the line.*/
  146. /* A 0 pointer is taken as 0 column.         */
  147. /* Returns CORD_NOT_FOUND if i is too big.     */
  148. /* Assumes i > dis_line.             */
  149. size_t line_pos(int i, int *c)
  150. {
  151.     int j;
  152.     size_t cur;
  153.     size_t next;
  154.     line_map map = current_map;
  155.     
  156.     while (map -> line > i) map = map -> previous;
  157.     if (map -> line < i - 2) /* rebuild */ invalidate_map(i);
  158.     for (j = map -> line, cur = map -> pos; j < i;) {
  159.     cur = CORD_chr(current, cur, '\n');
  160.         if (cur == current_len-1) return(CORD_NOT_FOUND);
  161.         cur++;
  162.         if (++j > current_map -> line) add_map(j, cur);
  163.     }
  164.     if (c != 0) {
  165.         next = CORD_chr(current, cur, '\n');
  166.         if (next == CORD_NOT_FOUND) next = current_len - 1;
  167.         if (next < cur + *c) {
  168.             *c = next - cur;
  169.         }
  170.         cur += *c;
  171.     }
  172.     return(cur);
  173. }
  174.  
  175. void add_hist(CORD s)
  176. {
  177.     history new_file = GC_NEW(struct HistoryRep);
  178.     
  179.     new_file -> file_contents = current = s;
  180.     current_len = CORD_len(s);
  181.     new_file -> previous = now;
  182.     if (now != 0) now -> map = current_map;
  183.     now = new_file;
  184. }
  185.  
  186. void del_hist(void)
  187. {
  188.     now = now -> previous;
  189.     current = now -> file_contents;
  190.     current_map = now -> map;
  191.     current_len = CORD_len(current);
  192. }
  193.  
  194. /* Current screen_contents; a dynamically allocated array of CORDs    */
  195. CORD * screen = 0;
  196. int screen_size = 0;
  197.  
  198. # ifndef WIN32
  199. /* Replace a line in the curses stdscr.    All control characters are    */
  200. /* displayed as upper case characters in standout mode.  This isn't    */
  201. /* terribly appropriate for tabs.                                    */
  202. void replace_line(int i, CORD s)
  203. {
  204.     register int c;
  205.     CORD_pos p;
  206.     size_t len = CORD_len(s);
  207.     
  208.     if (screen == 0 || LINES > screen_size) {
  209.         screen_size = LINES;
  210.         screen = (CORD *)GC_MALLOC(screen_size * sizeof(CORD));
  211.     }
  212. #   if !defined(MACINTOSH)
  213.         /* A gross workaround for an apparent curses bug: */
  214.         if (i == LINES-1 && len == COLS) {
  215.             s = CORD_substr(s, 0, CORD_len(s) - 1);
  216.         }
  217. #   endif
  218.     if (CORD_cmp(screen[i], s) != 0) {
  219.         move(i, 0); clrtoeol(); move(i,0);
  220.  
  221.         CORD_FOR (p, s) {
  222.             c = CORD_pos_fetch(p) & 0x7f;
  223.             if (iscntrl(c)) {
  224.                 standout(); addch(c + 0x40); standend();
  225.             } else {
  226.                 addch(c);
  227.             }
  228.         }
  229.         screen[i] = s;
  230.     }
  231. }
  232. #else
  233. # define replace_line(i,s) invalidate_line(i)
  234. #endif
  235.  
  236. /* Return up to COLS characters of the line of s starting at pos,    */
  237. /* returning only characters after the given column.            */
  238. CORD retrieve_line(CORD s, size_t pos, unsigned column)
  239. {
  240.     CORD candidate = CORD_substr(s, pos, column + COLS);
  241.                 /* avoids scanning very long lines    */
  242.     int eol = CORD_chr(candidate, 0, '\n');
  243.     int len;
  244.     
  245.     if (eol == CORD_NOT_FOUND) eol = CORD_len(candidate);
  246.     len = (int)eol - (int)column;
  247.     if (len < 0) len = 0;
  248.     return(CORD_substr(s, pos + column, len));
  249. }
  250.  
  251. # ifdef WIN32
  252. #   define refresh();
  253.  
  254.     CORD retrieve_screen_line(int i)
  255.     {
  256.         register size_t pos;
  257.         
  258.         invalidate_map(dis_line + LINES);    /* Prune search */
  259.         pos = line_pos(dis_line + i, 0);
  260.         if (pos == CORD_NOT_FOUND) return(CORD_EMPTY);
  261.         return(retrieve_line(current, pos, dis_col));
  262.     }
  263. # endif
  264.  
  265. /* Display the visible section of the current file     */
  266. void redisplay(void)
  267. {
  268.     register int i;
  269.     
  270.     invalidate_map(dis_line + LINES);    /* Prune search */
  271.     for (i = 0; i < LINES; i++) {
  272.         if (need_redisplay == ALL || need_redisplay == i) {
  273.             register size_t pos = line_pos(dis_line + i, 0);
  274.             
  275.             if (pos == CORD_NOT_FOUND) break;
  276.             replace_line(i, retrieve_line(current, pos, dis_col));
  277.             if (need_redisplay == i) goto done;
  278.         }
  279.     }
  280.     for (; i < LINES; i++) replace_line(i, CORD_EMPTY);
  281. done:
  282.     refresh();
  283.     need_redisplay = NONE;
  284. }
  285.  
  286. int dis_granularity;
  287.  
  288. /* Update dis_line, dis_col, and dis_pos to make cursor visible.    */
  289. /* Assumes line, col, dis_line, dis_pos are in bounds.            */
  290. void normalize_display()
  291. {
  292.     int old_line = dis_line;
  293.     int old_col = dis_col;
  294.     
  295.     dis_granularity = 1;
  296.     if (LINES > 15 && COLS > 15) dis_granularity = 2;
  297.     while (dis_line > line) dis_line -= dis_granularity;
  298.     while (dis_col > col) dis_col -= dis_granularity;
  299.     while (line >= dis_line + LINES) dis_line += dis_granularity;
  300.     while (col >= dis_col + COLS) dis_col += dis_granularity;
  301.     if (old_line != dis_line || old_col != dis_col) {
  302.         need_redisplay = ALL;
  303.     }
  304. }
  305.  
  306. # if defined(WIN32)
  307. # elif defined(MACINTOSH)
  308. #        define move_cursor(x,y) cgotoxy(x + 1, y + 1, stdout)
  309. # else
  310. #        define move_cursor(x,y) move(y,x)
  311. # endif
  312.  
  313. /* Adjust display so that cursor is visible; move cursor into position    */
  314. /* Update screen if necessary.                        */
  315. void fix_cursor(void)
  316. {
  317.     normalize_display();
  318.     if (need_redisplay != NONE) redisplay();
  319.     move_cursor(col - dis_col, line - dis_line);
  320.     refresh();
  321. #   ifndef WIN32
  322.       fflush(stdout);
  323. #   endif
  324. }
  325.  
  326. /* Make sure line, col, and dis_pos are somewhere inside file.    */
  327. /* Recompute file_pos.    Assumes dis_pos is accurate or past eof    */
  328. void fix_pos()
  329. {
  330.     int my_col = col;
  331.     
  332.     if ((size_t)line > current_len) line = current_len;
  333.     file_pos = line_pos(line, &my_col);
  334.     if (file_pos == CORD_NOT_FOUND) {
  335.         for (line = current_map -> line, file_pos = current_map -> pos;
  336.              file_pos < current_len;
  337.              line++, file_pos = CORD_chr(current, file_pos, '\n') + 1);
  338.         line--;
  339.         file_pos = line_pos(line, &col);
  340.     } else {
  341.         col = my_col;
  342.     }
  343. }
  344.  
  345. #if defined(WIN32)
  346. #  define beep() Beep(1000 /* Hz */, 300 /* msecs */) 
  347. #elif defined(MACINTOSH)
  348. #    define beep() SysBeep(1)
  349. #else
  350. /*
  351.  * beep() is part of some curses packages and not others.
  352.  * We try to match the type of the builtin one, if any.
  353.  */
  354. #ifdef __STDC__
  355.     int beep(void)
  356. #else
  357.     int beep()
  358. #endif
  359. {
  360.     putc('\007', stderr);
  361.     return(0);
  362. }
  363. #endif
  364.  
  365. #   define NO_PREFIX -1
  366. #   define BARE_PREFIX -2
  367. int repeat_count = NO_PREFIX;    /* Current command prefix. */
  368.  
  369. int locate_mode = 0;            /* Currently between 2 ^Ls    */
  370. CORD locate_string = CORD_EMPTY;    /* Current search string.    */
  371.  
  372. char * arg_file_name;
  373.  
  374. #ifdef WIN32
  375. /* Change the current position to whatever is currently displayed at    */
  376. /* the given SCREEN coordinates.                    */
  377. void set_position(int c, int l)
  378. {
  379.     line = l + dis_line;
  380.     col = c + dis_col;
  381.     fix_pos();
  382.     move_cursor(col - dis_col, line - dis_line);
  383. }
  384. #endif /* WIN32 */
  385.  
  386. /* Perform the command associated with character c.  C may be an    */
  387. /* integer > 256 denoting a windows command, one of the above control    */
  388. /* characters, or another ASCII character to be used as either a     */
  389. /* character to be inserted, a repeat count, or a search string,     */
  390. /* depending on the current state.                    */
  391. void do_command(int c)
  392. {
  393.     int i;
  394.     int need_fix_pos;
  395.     FILE * out;
  396.     
  397.     if ( c == '\r') c = '\n';
  398.     if (locate_mode) {
  399.         size_t new_pos;
  400.           
  401.         if (c == LOCATE) {
  402.               locate_mode = 0;
  403.               locate_string = CORD_EMPTY;
  404.               return;
  405.         }
  406.         locate_string = CORD_cat_char(locate_string, (char)c);
  407.         new_pos = CORD_str(current, file_pos - CORD_len(locate_string) + 1,
  408.                      locate_string);
  409.         if (new_pos != CORD_NOT_FOUND) {
  410.             need_redisplay = ALL;
  411.             new_pos += CORD_len(locate_string);
  412.             for (;;) {
  413.                   file_pos = line_pos(line + 1, 0);
  414.                   if (file_pos > new_pos) break;
  415.                   line++;
  416.             }
  417.             col = new_pos - line_pos(line, 0);
  418.             file_pos = new_pos;
  419.             fix_cursor();
  420.         } else {
  421.             locate_string = CORD_substr(locate_string, 0,
  422.                               CORD_len(locate_string) - 1);
  423.             beep();
  424.         }
  425.         return;
  426.     }
  427.     if (c == REPEAT) {
  428.           repeat_count = BARE_PREFIX; return;
  429.     } else if (c < 0x100 && isdigit(c)){
  430.         if (repeat_count == BARE_PREFIX) {
  431.           repeat_count = c - '0'; return;
  432.         } else if (repeat_count != NO_PREFIX) {
  433.           repeat_count = 10 * repeat_count + c - '0'; return;
  434.         }
  435.     }
  436.     if (repeat_count == NO_PREFIX) repeat_count = 1;
  437.     if (repeat_count == BARE_PREFIX && (c == UP || c == DOWN)) {
  438.           repeat_count = LINES - dis_granularity;
  439.     }
  440.     if (repeat_count == BARE_PREFIX) repeat_count = 8;
  441.     need_fix_pos = 0;
  442.     for (i = 0; i < repeat_count; i++) {
  443.         switch(c) {
  444.           case LOCATE:
  445.             locate_mode = 1;
  446.             break;
  447.           case TOP:
  448.             line = col = file_pos = 0;
  449.             break;
  450.            case UP:
  451.              if (line != 0) {
  452.                  line--;
  453.                  need_fix_pos = 1;
  454.              }
  455.              break;
  456.            case DOWN:
  457.              line++;
  458.              need_fix_pos = 1;
  459.              break;
  460.            case LEFT:
  461.              if (col != 0) {
  462.                  col--; file_pos--;
  463.              }
  464.              break;
  465.            case RIGHT:
  466.              if (CORD_fetch(current, file_pos) == '\n') break;
  467.              col++; file_pos++;
  468.              break;
  469.            case UNDO:
  470.              del_hist();
  471.              need_redisplay = ALL; need_fix_pos = 1;
  472.              break;
  473.            case BS:
  474.              if (col == 0) {
  475.                  beep();
  476.                  break;
  477.              }
  478.              col--; file_pos--;
  479.              /* fall through: */
  480.            case DEL:
  481.              if (file_pos == current_len-1) break;
  482.                  /* Can't delete trailing newline */
  483.              if (CORD_fetch(current, file_pos) == '\n') {
  484.                  need_redisplay = ALL; need_fix_pos = 1;
  485.              } else {
  486.                  need_redisplay = line - dis_line;
  487.              }
  488.              add_hist(CORD_cat(
  489.                      CORD_substr(current, 0, file_pos),
  490.                      CORD_substr(current, file_pos+1, current_len)));
  491.              invalidate_map(line);
  492.              break;
  493.            case WRITE:
  494.             if ((out = fopen(arg_file_name, "wb")) == NULL
  495.               || CORD_put(current, out) == EOF) {
  496.             de_error("Write failed\n");
  497.             need_redisplay = ALL;
  498.             } else {
  499.                 fclose(out);
  500.             }
  501.             break;
  502.            default:
  503.              {
  504.                  CORD left_part = CORD_substr(current, 0, file_pos);
  505.                  CORD right_part = CORD_substr(current, file_pos, current_len);
  506.                  
  507.                  add_hist(CORD_cat(CORD_cat_char(left_part, (char)c),
  508.                            right_part));
  509.                  invalidate_map(line);
  510.                  if (c == '\n') {
  511.                      col = 0; line++; file_pos++;
  512.                      need_redisplay = ALL;
  513.                  } else {
  514.                      col++; file_pos++;
  515.                      need_redisplay = line - dis_line;
  516.                  }
  517.                  break;
  518.              }
  519.         }
  520.     }
  521.     if (need_fix_pos) fix_pos();
  522.     fix_cursor();
  523.     repeat_count = NO_PREFIX;
  524. }
  525.  
  526. /* OS independent initialization */
  527.  
  528. void generic_init(void)
  529. {
  530.     FILE * f;
  531.     CORD initial;
  532.     
  533.     if ((f = fopen(arg_file_name, "rb")) == NULL) {
  534.          initial = "\n";
  535.     } else {
  536.         initial = CORD_from_file(f);
  537.         if (initial == CORD_EMPTY
  538.             || CORD_fetch(initial, CORD_len(initial)-1) != '\n') {
  539.             initial = CORD_cat(initial, "\n");
  540.         }
  541.     }
  542.     add_map(0,0);
  543.     add_hist(initial);
  544.     now -> map = current_map;
  545.     now -> previous = now;  /* Can't back up further: beginning of the world */
  546.     need_redisplay = ALL;
  547.     fix_cursor();
  548. }
  549.  
  550. #ifndef WIN32
  551.  
  552. main(argc, argv)
  553. int argc;
  554. char ** argv;
  555. {
  556.     int c;
  557.     CORD initial;
  558.  
  559. #if defined(MACINTOSH)
  560.     console_options.title = "\pDumb Editor";
  561.     cshow(stdout);
  562.     GC_init();
  563.     argc = ccommand(&argv);
  564. #endif
  565.     
  566.     if (argc != 2) goto usage;
  567.     arg_file_name = argv[1];
  568.     setvbuf(stdout, GC_MALLOC_ATOMIC(8192), _IOFBF, 8192);
  569.     initscr();
  570.     noecho(); nonl(); cbreak();
  571.     generic_init();
  572.     while ((c = getchar()) != QUIT) {
  573.         if (c == EOF) break;
  574.         do_command(c);
  575.     }
  576. done:
  577.     move(LINES-1, 0);
  578.     clrtoeol();
  579.     refresh();
  580.     nl();
  581.     echo();
  582.     endwin();
  583.     exit(0);
  584. usage:
  585.     fprintf(stderr, "Usage: %s file\n", argv[0]);
  586.     fprintf(stderr, "Cursor keys: ^B(left) ^F(right) ^P(up) ^N(down)\n");
  587.     fprintf(stderr, "Undo: ^U    Write: ^W   Quit:^D  Repeat count: ^R[n]\n");
  588.     fprintf(stderr, "Top: ^T   Locate (search, find): ^L text ^L\n");
  589.     exit(1);
  590. }
  591.  
  592. #endif  /* !WIN32 */
  593.