de.c 16 KB

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