1 /* 2 * Part one of the mined editor. 3 */ 4 5 /* 6 * Author: Michiel Huisjes. 7 * 8 * 1. General remarks. 9 * 10 * Mined is a screen editor designed for the MINIX operating system. 11 * It is meant to be used on files not larger than 50K and to be fast. 12 * When mined starts up, it reads the file into its memory to minimize 13 * disk access. The only time that disk access is needed is when certain 14 * save, write or copy commands are given. 15 * 16 * Mined has the style of Emacs or Jove, that means that there are no modes. 17 * Each character has its own entry in an 256 pointer to function array, 18 * which is called when that character is typed. Only ASCII characters are 19 * connected with a function that inserts that character at the current 20 * location in the file. Two execptions are <linefeed> and <tab> which are 21 * inserted as well. Note that the mapping between commands and functions 22 * called is implicit in the table. Changing the mapping just implies 23 * changing the pointers in this table. 24 * 25 * The display consists of SCREENMAX + 1 lines and XMAX + 1 characters. When 26 * a line is larger (or gets larger during editing) than XBREAK characters, 27 * the line is either shifted SHIFT_SIZE characters to the left (which means 28 * that the first SHIFT_SIZE characters are not printed) or the end of the 29 * line is marked with the SHIFT_MARK character and the rest of the line is 30 * not printed. A line can never exceed MAX_CHARS characters. Mined will 31 * always try to keep the cursor on the same line and same (relative) 32 * x-coordinate if nothing changed. So if you scroll one line up, the cursor 33 * stays on the same line, or when you move one line down, the cursor will 34 * move to the same place on the line as it was on the previous. 35 * Every character on the line is available for editing including the 36 * linefeed at the the of the line. When the linefeed is deleted, the current 37 * line and the next line are joined. The last character of the file (which 38 * is always a linefeed) can never be deleted. 39 * The bottomline (as indicated by YMAX + 1) is used as a status line during 40 * editing. This line is usually blank or contains information mined needs 41 * during editing. This information (or rather questions) is displayed in 42 * reverse video. 43 * 44 * The terminal modes are changed completely. All signals like start/stop, 45 * interrupt etc. are unset. The only signal that remains is the quit signal. 46 * The quit signal (^\) is the general abort signal for mined. Typing a ^\ 47 * during searching or when mined is asking for filenames, etc. will abort 48 * the function and mined will return to the main loop. Sending a quit 49 * signal during the main loop will abort the session (after confirmation) 50 * and the file is not (!) saved. 51 * The session will also be aborted when an unrecoverable error occurs. E.g 52 * when there is no more memory available. If the file has been modified, 53 * mined will ask if the file has to be saved or not. 54 * If there is no more space left on the disk, mined will just give an error 55 * message and continue. 56 * 57 * The number of system calls are minized. This is done to keep the editor 58 * as fast as possible. I/O is done in SCREEN_SIZE reads/writes. Accumulated 59 * output is also flushed at the end of each character typed. 60 * 61 * 2. Regular expressions 62 * 63 * Mined has a build in regular expression matcher, which is used for 64 * searching and replace routines. A regular expression consists of a 65 * sequence of: 66 * 67 * 1. A normal character matching that character. 68 * 2. A . matching any character. 69 * 3. A ^ matching the begin of a line. 70 * 4. A $ (as last character of the pattern) mathing the end of a line. 71 * 5. A \<character> matching <character>. 72 * 6. A number of characters enclosed in [] pairs matching any of these 73 * characters. A list of characters can be indicated by a '-'. So 74 * [a-z] matches any letter of the alphabet. If the first character 75 * after the '[' is a '^' then the set is negated (matching none of 76 * the characters). 77 * A ']', '^' or '-' can be escaped by putting a '\' in front of it. 78 * Of course this means that a \ must be represented by \\. 79 * 7. If one of the expressions as described in 1-6 is followed by a 80 * '*' than that expressions matches a sequence of 0 or more of 81 * that expression. 82 * 83 * Parsing of regular expression is done in two phases. In the first phase 84 * the expression is compiled into a more comprehensible form. In the second 85 * phase the actual matching is done. For more details see 3.6. 86 * 87 * 88 * 3. Implementation of mined. 89 * 90 * 3.1 Data structures. 91 * 92 * The main data structures are as follows. The whole file is kept in a 93 * double linked list of lines. The LINE structure looks like this: 94 * 95 * typedef struct Line { 96 * struct Line *next; 97 * struct Line *prev; 98 * char *text; 99 * unsigned char shift_count; 100 * } LINE; 101 * 102 * Each line entry contains a pointer to the next line, a pointer to the 103 * previous line and a pointer to the text of that line. A special field 104 * shift_count contains the number of shifts (in units of SHIFT_SIZE) 105 * that is performed on that line. The total size of the structure is 7 106 * bytes so a file consisting of 1000 empty lines will waste a lot of 107 * memory. A LINE structure is allocated for each line in the file. After 108 * that the number of characters of the line is counted and sufficient 109 * space is allocated to store them (including a linefeed and a '\0'). 110 * The resulting address is assigned to the text field in the structure. 111 * 112 * A special structure is allocated and its address is assigned to the 113 * variable header as well as the variable tail. The text field of this 114 * structure is set to NULL. The tail->prev of this structure points 115 * to the last LINE of the file and the header->next to the first LINE. 116 * Other LINE *variables are top_line and bot_line which point to the 117 * first line resp. the last line on the screen. 118 * Two other variables are important as well. First the LINE *cur_line, 119 * which points to the LINE currently in use and the char *cur_text, 120 * which points to the character at which the cursor stands. 121 * Whenever an ASCII character is typed, a new line is build with this 122 * character inserted. Then the old data space (pointed to by 123 * cur_line->text) is freed, data space for the new line is allocated and 124 * assigned to cur_line->text. 125 * 126 * Two global variables called x and y represent the x and y coordinates 127 * from the cursor. The global variable nlines contains the number of 128 * lines in the file. Last_y indicates the maximum y coordinate of the 129 * screen (which is usually SCREENMAX). 130 * 131 * A few strings must be initialized by hand before compiling mined. 132 * These string are enter_string, which is printed upon entering mined, 133 * rev_video (turn on reverse video), normal_video, rev_scroll (perform a 134 * reverse scroll) and pos_string. The last string should hold the 135 * absolute position string to be printed for cursor motion. The #define 136 * X_PLUS and Y_PLUS should contain the characters to be added to the 137 * coordinates x and y (both starting at 0) to finish cursor positioning. 138 * 139 * 3.2 Starting up. 140 * 141 * Mined can be called with or without argument and the function 142 * load_file () is called with these arguments. load_file () checks 143 * if the file exists if it can be read and if it is writable and 144 * sets the writable flag accordingly. If the file can be read, 145 * load_file () reads a line from the file and stores this line into 146 * a structure by calling install_line () and line_insert () which 147 * installs the line into the double linked list, until the end of the 148 * file is reached. 149 * Lines are read by the function get_line (), which buffers the 150 * reading in blocks of SCREEN_SIZE. Load_file () also initializes the 151 * LINE *variables described above. 152 * 153 * 3.3 Moving around. 154 * 155 * Several commands are implemented for moving through the file. 156 * Moving up (UP1), down (DN1) left (LF1) and right (RT1) are done by the 157 * arrow keys. Moving one line below the screen scrolls the screen one 158 * line up. Moving one line above the screen scrolls the screen one line 159 * down. The functions forward_scroll () and reverse_scroll () take care 160 * of that. 161 * Several other move functions exist: move to begin of line (BL), end of 162 * line (EL) top of screen (HIGH), bottom of screen (LOW), top of file 163 * (HO), end of file (EF), scroll one page down (PD), scroll one page up 164 * (PU), scroll one line down (SD), scroll one line up (SU) and move to a 165 * certain line number (GOTO). 166 * Two functions called MN () and MP () each move one word further or 167 * backwards. A word is a number of non-blanks seperated by a space, a 168 * tab or a linefeed. 169 * 170 * 3.4 Modifying text. 171 * 172 * The modifying commands can be separated into two modes. The first 173 * being inserting text, and the other deleting text. Two functions are 174 * created for these purposes: insert () and delete (). Both are capable 175 * of deleting or inserting large amounts of text as well as one 176 * character. Insert () must be given the line and location at which 177 * the text must be inserted. Is doesn't make any difference whether this 178 * text contains linefeeds or not. Delete () must be given a pointer to 179 * the start line, a pointer from where deleting should start on that 180 * line and the same information about the end position. The last 181 * character of the file will never be deleted. Delete () will make the 182 * necessary changes to the screen after deleting, but insert () won't. 183 * The functions for modifying text are: insert one char (S), insert a 184 * file (file_insert (fd)), insert a linefeed and put cursor back to 185 * end of line (LIB), delete character under the cursor (DCC), delete 186 * before cursor (even linefeed) (DPC), delete next word (DNW), delete 187 * previous word (DPC) and delete to end of line (if the cursor is at 188 * a linefeed delete line) (DLN). 189 * 190 * 3.5 Yanking. 191 * 192 * A few utilities are provided for yanking pieces of text. The function 193 * MA () marks the current position in the file. This is done by setting 194 * LINE *mark_line and char *mark_text to the current position. Yanking 195 * of text can be done in two modes. The first mode just copies the text 196 * from the mark to the current position (or visa versa) into a buffer 197 * (YA) and the second also deletes the text (DT). Both functions call 198 * the function set_up () with the delete flag on or off. Set_up () 199 * checks if the marked position is still a valid one (by using 200 * check_mark () and legal ()), and then calls the function yank () with 201 * a start and end position in the file. This function copies the text 202 * into a scratch_file as indicated by the variable yank_file. This 203 * scratch_file is made uniq by the function scratch_file (). At the end 204 * of copying yank will (if necessary) delete the text. A global flag 205 * called yank_status keeps track of the buffer (or file) status. It is 206 * initialized on NOT_VALID and set to EMPTY (by set_up ()) or VALID (by 207 * yank ()). Several things can be done with the buffer. It can be 208 * inserted somewhere else in the file (PT) or it can be copied into 209 * another file (WB), which will be prompted for. 210 * 211 * 3.6 Search and replace routines. 212 * 213 * Searching for strings and replacing strings are done by regular 214 * expressions. For any expression the function compile () is called 215 * with as argument the expression to compile. Compile () returns a 216 * pointer to a structure which looks like this: 217 * 218 * typedef struct regex { 219 * union { 220 * char *err_mess; 221 * int *expression; 222 * } result; 223 * char status; 224 * char *start_ptr; 225 * char *end_ptr; 226 * } REGEX; 227 * 228 * If something went wrong during compiling (e.g. an illegal expression 229 * was given), the function reg_error () is called, which sets the status 230 * field to REG_ERROR and the err_mess field to the error message. If the 231 * match must be anchored at the beginning of the line (end of line), the 232 * status field is set to BEGIN_LINE (END_LINE). If none of these special 233 * cases are true, the field is set to 0 and the function finished () is 234 * called. Finished () allocates space to hold the compiled expression 235 * and copies this expression into the expression field of the union 236 * (bcopy ()). Matching is done by the routines match() and line_check(). 237 * Match () takes as argument the REGEX *program, a pointer to the 238 * startposition on the current line, and a flag indicating FORWARD or 239 * REVERSE search. Match () checks out the whole file until a match is 240 * found. If match is found it returns a pointer to the line in which the 241 * match was found else it returns a NULL. Line_check () takes the 242 * same arguments, but return either MATCH or NO_MATCH. 243 * During checking, the start_ptr and end_ptr fields of the REGEX 244 * structure are assigned to the start and end of the match. 245 * Both functions try to find a match by walking through the line 246 * character by character. For each possibility, the function 247 * check_string () is called with as arguments the REGEX *program and the 248 * string to search in. It starts walking through the expression until 249 * the end of the expression or the end of the string is reached. 250 * Whenever a * is encountered, this position of the string is marked, 251 * the maximum number of matches are performed and the function star () 252 * is called in order to try to find the longest match possible. Star () 253 * takes as arguments the REGEX program, the current position of the 254 * string, the marked position and the current position of the expression 255 * Star () walks from the current position of the string back to the 256 * marked position, and calls string_check () in order to find a match. 257 * It returns MATCH or NO_MATCH, just as string_check () does. 258 * Searching is now easy. Both search routines (forward (SF) and 259 * backwards search (SR)) call search () with an apropiate message and a 260 * flag indicating FORWARD or REVERSE search. Search () will get an 261 * expression from the user by calling get_expression(). Get_expression() 262 * returns a pointer to a REGEX structure or NULL upon errors and 263 * prompts for the expression. If no expression if given, the previous is 264 * used instead. After that search will call match (), and if a match is 265 * found, we can move to that place in the file by the functions find_x() 266 * and find_y () which will find display the match on the screen. 267 * Replacing can be done in two ways. A global replace (GR) or a line 268 * replace (LR). Both functions call change () with a message an a flag 269 * indicating global or line replacement. Change () will prompt for the 270 * expression and for the replacement. Every & in the replacement pattern 271 * means substitute the match instead. An & can be escaped by a \. When 272 * a match is found, the function substitute () will perform the 273 * substitution. 274 * 275 * 3.6 Miscellaneous commands. 276 * 277 * A few commands haven't be discussed yet. These are redraw the screen 278 * (RD) fork a shell (SH), print file status (FS), write file to disc 279 * (WT), insert a file at current position (IF), leave editor (XT) and 280 * visit another file (VI). The last two functions will check if the file 281 * has been modified. If it has, they will ask if you want to save the 282 * file by calling ask_save (). 283 * The function ESC () will repeat a command n times. It will prompt for 284 * the number. Aborting the loop can be done by sending the ^\ signal. 285 * 286 * 3.7 Utility functions. 287 * 288 * Several functions exists for internal use. First allocation routines: 289 * alloc (bytes) and newline () will return a pointer to free data space 290 * if the given size. If there is no more memory available, the function 291 * panic () is called. 292 * Signal handling: The only signal that can be send to mined is the 293 * SIGQUIT signal. This signal, functions as a general abort command. 294 * Mined will abort if the signal is given during the main loop. The 295 * function abort_mined () takes care of that. 296 * Panic () is a function with as argument a error message. It will print 297 * the message and the error number set by the kernel (errno) and will 298 * ask if the file must be saved or not. It resets the terminal 299 * (raw_mode ()) and exits. 300 * String handling routines like copy_string(to, from), length_of(string) 301 * and build_string (buffer, format, arg1, arg2, ...). The latter takes 302 * a description of the string out out the format field and puts the 303 * result in the buffer. (It works like printf (3), but then into a 304 * string). The functions status_line (string1, string2), error (string1, 305 * string2), clear_status () and bottom_line () all print information on 306 * the status line. 307 * Get_string (message, buffer) reads a string and getch () reads one 308 * character from the terminal. 309 * Num_out ((long) number) prints the number into a 11 digit field 310 * without leading zero's. It returns a pointer to the resulting string. 311 * File_status () prints all file information on the status line. 312 * Set_cursor (x, y) prints the string to put the cursor at coordinates 313 * x and y. 314 * Output is done by four functions: writeline(fd,string), clear_buffer() 315 * write_char (fd, c) and flush_buffer (fd). Three defines are provided 316 * to write on filedescriptor STD_OUT (terminal) which is used normally: 317 * string_print (string), putch (c) and flush (). All these functions 318 * use the global I/O buffer screen and the global index for this array 319 * called out_count. In this way I/O can be buffered, so that reads or 320 * writes can be done in blocks of SCREEN_SIZE size. 321 * The following functions all handle internal line maintenance. The 322 * function proceed (start_line, count) returns the count'th line after 323 * start_line. If count is negative, the count'th line before the 324 * start_line is returned. If header or tail is encountered then that 325 * will be returned. Display (x, y, start_line, count) displays count 326 * lines starting at coordinates [x, y] and beginning at start_line. If 327 * the header or tail is encountered, empty lines are displayed instead. 328 * The function reset (head_line, ny) reset top_line, last_y, bot_line, 329 * cur_line and y-coordinate. This is not a neat way to do the 330 * maintenance, but it sure saves a lot of code. It is usually used in 331 * combination with display (). 332 * Put_line(line, offset, clear_line), prints a line (skipping characters 333 * according to the line->shift_size field) until XBREAK - offset 334 * characters are printed or a '\n' is encountered. If clear_line is 335 * TRUE, spaces are printed until XBREAK - offset characters. 336 * Line_print (line) is a #define from put_line (line, 0, TRUE). 337 * Moving is done by the functions move_to (x, y), move_addres (address) 338 * and move (x, adress, y). This function is the most important one in 339 * mined. New_y must be between 0 and last_y, new_x can be about 340 * anything, address must be a pointer to an character on the current 341 * line (or y). Move_to () first adjust the y coordinate together with 342 * cur_line. If an address is given, it finds the corresponding 343 * x-coordinate. If an new x-coordinate was given, it will try to locate 344 * the corresponding character. After that it sets the shift_count field 345 * of cur_line to an apropiate number according to new_x. The only thing 346 * left to do now is to assign the new values to cur_line, cur_text, x 347 * and y. 348 * 349 * 4. Summary of commands. 350 * 351 * CURSOR MOTION 352 * up-arrow Move cursor 1 line up. At top of screen, reverse scroll 353 * down-arrow Move cursor 1 line down. At bottom, scroll forward. 354 * left-arrow Move cursor 1 character left or to end of previous line 355 * right-arrow Move cursor 1 character right or to start of next line 356 * CTRL-A Move cursor to start of current line 357 * CTRL-Z Move cursor to end of current line 358 * CTRL-^ Move cursor to top of screen 359 * CTRL-_ Move cursor to bottom of screen 360 * CTRL-F Forward to start of next word (even to next line) 361 * CTRL-B Backward to first character of previous word 362 * 363 * SCREEN MOTION 364 * Home key Move cursor to first character of file 365 * End key Move cursor to last character of file 366 * PgUp Scroll backward 1 page. Bottom line becomes top line 367 * PgD Scroll backward 1 page. Top line becomes bottom line 368 * CTRL-D Scroll screen down one line (reverse scroll) 369 * CTRL-U Scroll screen up one line (forward scroll) 370 * 371 * MODIFYING TEXT 372 * ASCII char Self insert character at cursor 373 * tab Insert tab at cursor 374 * backspace Delete the previous char (left of cursor), even line feed 375 * Del Delete the character under the cursor 376 * CTRL-N Delete next word 377 * CTRL-P Delete previous word 378 * CTRL-O Insert line feed at cursor and back up 1 character 379 * CTRL-T Delete tail of line (cursor to end); if empty, delete line 380 * CTRL-@ Set the mark (remember the current location) 381 * CTRL-K Delete text from the mark to current position save on file 382 * CTRL-C Save the text from the mark to the current position 383 * CTRL-Y Insert the contents of the save file at current position 384 * CTRL-Q Insert the contents of the save file into a new file 385 * CTRL-G Insert a file at the current position 386 * 387 * MISCELLANEOUS 388 * CTRL-E Erase and redraw the screen 389 * CTRL-V Visit file (read a new file); complain if old one changed 390 * CTRL-W Write the current file back to the disk 391 * numeric + Search forward (prompt for regular expression) 392 * numeric - Search backward (prompt for regular expression) 393 * numeric 5 Print the current status of the file 394 * CTRL-R (Global) Replace str1 by str2 (prompts for each string) 395 * CTRL-L (Line) Replace string1 by string2 396 * CTRL-S Fork off a shell and wait for it to finish 397 * CTRL-X EXIT (prompt if file modified) 398 * CTRL-] Go to a line. Prompts for linenumber 399 * CTRL-\ Abort whatever editor was doing and start again 400 * escape key Repeat a command count times; (prompts for count) 401 */ 402 403 /* ======================================================================== * 404 * Utilities * 405 * ======================================================================== */ 406 407 #include "mined.h" 408 #include <signal.h> 409 #include <termios.h> 410 #include <limits.h> 411 #include <errno.h> 412 #include <sys/wait.h> 413 #include <sys/ioctl.h> 414 #include <stdarg.h> 415 416 extern int errno; 417 int ymax = YMAX; 418 int screenmax = SCREENMAX; 419 420 421 /* 422 * Print file status. 423 */ 424 void FS(void) 425 { 426 fstatus(file_name[0] ? "" : "[buffer]", -1L); 427 } 428 429 /* 430 * Visit (edit) another file. If the file has been modified, ask the user if 431 * he wants to save it. 432 */ 433 void VI(void) 434 { 435 char new_file[LINE_LEN]; /* Buffer to hold new file name */ 436 437 if (modified == TRUE && ask_save() == ERRORS) 438 return; 439 440 /* Get new file name */ 441 if (get_file("Visit file:", new_file) == ERRORS) 442 return; 443 444 /* Free old linked list, initialize global variables and load new file */ 445 initialize(); 446 tputs(CL, 0, _putch); 447 load_file(new_file[0] == '\0' ? NULL : new_file); 448 } 449 450 /* 451 * Write file in core to disc. 452 */ 453 int WT(void) 454 { 455 register LINE *line; 456 register long count = 0L; /* Nr of chars written */ 457 char file[LINE_LEN]; /* Buffer for new file name */ 458 int fd; /* Filedescriptor of file */ 459 460 if (modified == FALSE) { 461 error ("Write not necessary.", NULL); 462 return FINE; 463 } 464 465 /* Check if file_name is valid and if file can be written */ 466 if (file_name[0] == '\0' || writable == FALSE) { 467 if (get_file("Enter file name:", file) != FINE) 468 return ERRORS; 469 copy_string(file_name, file); /* Save file name */ 470 } 471 if ((fd = creat(file_name, 0644)) < 0) { /* Empty file */ 472 error("Cannot create ", file_name); 473 writable = FALSE; 474 return ERRORS; 475 } 476 else 477 writable = TRUE; 478 479 clear_buffer(); 480 481 status_line("Writing ", file_name); 482 for (line = header->next; line != tail; line = line->next) { 483 if (line->shift_count & DUMMY) { 484 if (line->next == tail && line->text[0] == '\n') 485 continue; 486 } 487 if (writeline(fd, line->text) == ERRORS) { 488 count = -1L; 489 break; 490 } 491 count += (long) length_of(line->text); 492 } 493 494 if (count > 0L && flush_buffer(fd) == ERRORS) 495 count = -1L; 496 497 (void) close(fd); 498 499 if (count == -1L) 500 return ERRORS; 501 502 modified = FALSE; 503 rpipe = FALSE; /* File name is now assigned */ 504 505 /* Display how many chars (and lines) were written */ 506 fstatus("Wrote", count); 507 return FINE; 508 } 509 510 /* Call WT and discard value returned. */ 511 void XWT(void) 512 { 513 (void) WT(); 514 } 515 516 517 518 /* 519 * Call an interactive shell. 520 */ 521 void SH(void) 522 { 523 register int w; 524 int pid, status; 525 char *shell; 526 527 if ((shell = getenv("SHELL")) == NULL) shell = "/bin/sh"; 528 529 switch (pid = fork()) { 530 case -1: /* Error */ 531 error("Cannot fork.", NULL); 532 return; 533 case 0: /* This is the child */ 534 set_cursor(0, ymax); 535 putch('\n'); 536 flush(); 537 raw_mode(OFF); 538 if (rpipe) { /* Fix stdin */ 539 close (0); 540 if (open("/dev/tty", 0) < 0) 541 exit (126); 542 } 543 execl(shell, shell, (char *) 0); 544 exit(127); /* Exit with 127 */ 545 default : /* This is the parent */ 546 signal(SIGINT, SIG_IGN); 547 signal(SIGQUIT, SIG_IGN); 548 do { 549 w = wait(&status); 550 } while (w != -1 && w != pid); 551 } 552 553 raw_mode(ON); 554 RD(); 555 556 if ((status >> 8) == 127) /* Child died with 127 */ 557 error("Cannot exec ", shell); 558 else if ((status >> 8) == 126) 559 error("Cannot open /dev/tty as fd #0", NULL); 560 } 561 562 /* 563 * Proceed returns the count'th line after `line'. When count is negative 564 * it returns the count'th line before `line'. When the next (previous) 565 * line is the tail (header) indicating EOF (tof) it stops. 566 */ 567 LINE *proceed(register LINE *line, register int count) 568 { 569 if (count < 0) 570 while (count++ < 0 && line != header) 571 line = line->prev; 572 else 573 while (count-- > 0 && line != tail) 574 line = line->next; 575 return line; 576 } 577 578 /* 579 * Show concatenation of s1 and s2 on the status line (bottom of screen) 580 * If revfl is TRUE, turn on reverse video on both strings. Set stat_visible 581 * only if bottom_line is visible. 582 */ 583 int bottom_line(FLAG revfl, char *s1, char *s2, char *inbuf, FLAG statfl) 584 { 585 int ret = FINE; 586 char buf[LINE_LEN]; 587 register char *p = buf; 588 589 *p++ = ' '; 590 if (s1 != NULL) 591 while ((*p = *s1++)) 592 p++; 593 if (s2 != NULL) 594 while ((*p = *s2++)) 595 p++; 596 *p++ = ' '; 597 *p++ = 0; 598 599 if (revfl == ON && stat_visible == TRUE) 600 clear_status (); 601 set_cursor(0, ymax); 602 if (revfl == ON) { /* Print rev. start sequence */ 603 tputs(SO, 0, _putch); 604 stat_visible = TRUE; 605 } 606 else /* Used as clear_status() */ 607 stat_visible = FALSE; 608 609 string_print(buf); 610 611 if (inbuf != NULL) 612 ret = input(inbuf, statfl); 613 614 /* Print normal video */ 615 tputs(SE, 0, _putch); 616 tputs(CE, 0, _putch); /* Clear the rest of the line */ 617 if (inbuf != NULL) 618 set_cursor(0, ymax); 619 else 620 set_cursor(x, y); /* Set cursor back to old position */ 621 flush(); /* Perform the actual write */ 622 if (ret != FINE) 623 clear_status(); 624 return ret; 625 } 626 627 /* 628 * Count_chars() count the number of chars that the line would occupy on the 629 * screen. Counting starts at the real x-coordinate of the line. 630 */ 631 int count_chars(LINE *line) 632 { 633 register int cnt = get_shift(line->shift_count) * -SHIFT_SIZE; 634 register char *textp = line->text; 635 636 /* Find begin of line on screen */ 637 while (cnt < 0) { 638 if (is_tab(*textp++)) 639 cnt = tab(cnt); 640 else 641 cnt++; 642 } 643 644 /* Count number of chars left */ 645 cnt = 0; 646 while (*textp != '\n') { 647 if (is_tab(*textp++)) 648 cnt = tab(cnt); 649 else 650 cnt++; 651 } 652 return cnt; 653 } 654 655 /* 656 * Move to coordinates nx, ny at screen. The caller must check that scrolling 657 * is not needed. 658 * If new_x is lower than 0 or higher than XBREAK, move_to() will check if 659 * the line can be shifted. If it can it sets(or resets) the shift_count field 660 * of the current line accordingly. 661 * Move also sets cur_text to the right char. 662 * If we're moving to the same x coordinate, try to move the the x-coordinate 663 * used on the other previous call. 664 */ 665 void move(register int new_x, char *new_address, int new_y) 666 { 667 register LINE *line = cur_line; /* For building new cur_line */ 668 int shift = 0; /* How many shifts to make */ 669 static int rel_x = 0; /* Remember relative x position */ 670 int tx = x; 671 672 /* Check for illegal values */ 673 if (new_y < 0 || new_y > last_y) 674 return; 675 676 /* Adjust y-coordinate and cur_line */ 677 if (new_y < y) 678 while (y != new_y) { 679 y--; 680 line = line->prev; 681 } 682 else 683 while (y != new_y) { 684 y++; 685 line = line->next; 686 } 687 688 /* Set or unset relative x-coordinate */ 689 if (new_address == NULL) { 690 new_address = find_address(line, (new_x == x) ? rel_x : new_x , &tx); 691 if (new_x != x) 692 rel_x = tx; 693 new_x = tx; 694 } 695 else 696 rel_x = new_x = find_x(line, new_address); 697 698 /* Adjust shift_count if new_x lower than 0 or higher than XBREAK */ 699 if (new_x < 0 || new_x >= XBREAK) { 700 if (new_x > XBREAK || (new_x == XBREAK && *new_address != '\n')) 701 shift = (new_x - XBREAK) / SHIFT_SIZE + 1; 702 else { 703 shift = new_x / SHIFT_SIZE; 704 if (new_x % SHIFT_SIZE) 705 shift--; 706 } 707 708 if (shift != 0) { 709 line->shift_count += shift; 710 new_x = find_x(line, new_address); 711 set_cursor(0, y); 712 line_print(line); 713 rel_x = new_x; 714 } 715 } 716 717 /* Assign and position cursor */ 718 x = new_x; 719 cur_text = new_address; 720 cur_line = line; 721 set_cursor(x, y); 722 } 723 724 /* 725 * Find_x() returns the x coordinate belonging to address. 726 * (Tabs are expanded). 727 */ 728 int find_x(LINE *line, char *address) 729 { 730 register char *textp = line->text; 731 register int nx = get_shift(line->shift_count) * -SHIFT_SIZE; 732 733 while (textp != address && *textp != '\0') { 734 if (is_tab(*textp++)) /* Expand tabs */ 735 nx = tab(nx); 736 else 737 nx++; 738 } 739 return nx; 740 } 741 742 /* 743 * Find_address() returns the pointer in the line with offset x_coord. 744 * (Tabs are expanded). 745 */ 746 char *find_address(LINE *line, int x_coord, int *old_x) 747 { 748 register char *textp = line->text; 749 register int tx = get_shift(line->shift_count) * -SHIFT_SIZE; 750 751 while (tx < x_coord && *textp != '\n') { 752 if (is_tab(*textp)) { 753 if (*old_x - x_coord == 1 && tab(tx) > x_coord) 754 break; /* Moving left over tab */ 755 else 756 tx = tab(tx); 757 } 758 else 759 tx++; 760 textp++; 761 } 762 763 *old_x = tx; 764 return textp; 765 } 766 767 /* 768 * Length_of() returns the number of characters int the string `string' 769 * excluding the '\0'. 770 */ 771 int length_of(register char *string) 772 { 773 register int count = 0; 774 775 if (string != NULL) { 776 while (*string++ != '\0') 777 count++; 778 } 779 return count; 780 } 781 782 /* 783 * Copy_string() copies the string `from' into the string `to'. `To' must be 784 * long enough to hold `from'. 785 */ 786 void copy_string(register char *to, register char *from) 787 { 788 while ((*to++ = *from++)) 789 ; 790 } 791 792 /* 793 * Reset assigns bot_line, top_line and cur_line according to `head_line' 794 * which must be the first line of the screen, and an y-coordinate, 795 * which will be the current y-coordinate (if it isn't larger than last_y) 796 */ 797 void reset(LINE *head_line, int screen_y) 798 { 799 register LINE *line; 800 801 top_line = line = head_line; 802 803 /* Search for bot_line (might be last line in file) */ 804 for (last_y = 0; last_y < nlines - 1 && last_y < screenmax 805 && line->next != tail; last_y++) 806 line = line->next; 807 808 bot_line = line; 809 y = (screen_y > last_y) ? last_y : screen_y; 810 811 /* Set cur_line according to the new y value */ 812 cur_line = proceed(top_line, y); 813 } 814 815 /* 816 * Set cursor at coordinates x, y. 817 */ 818 void set_cursor(int nx, int ny) 819 { 820 tputs(tgoto(CM, nx, ny), 0, _putch); 821 } 822 823 /* 824 * Routine to open terminal when mined is used in a pipeline. 825 */ 826 void open_device(void) 827 { 828 if ((input_fd = open("/dev/tty", 0)) < 0) 829 panic("Cannot open /dev/tty for read"); 830 } 831 832 /* 833 * Getchar() reads one character from the terminal. The character must be 834 * masked with 0377 to avoid sign extension. 835 */ 836 int getch(void) 837 { 838 return (_getch() & 0377); 839 } 840 841 /* 842 * Display() shows count lines on the terminal starting at the given 843 * coordinates. When the tail of the list is encountered it will fill the 844 * rest of the screen with blank_line's. 845 * When count is negative, a backwards print from `line' will be done. 846 */ 847 void display(int x_coord, int y_coord, register LINE *line, register int count) 848 { 849 set_cursor(x_coord, y_coord); 850 851 /* Find new startline if count is negative */ 852 if (count < 0) { 853 line = proceed(line, count); 854 count = -count; 855 } 856 857 /* Print the lines */ 858 while (line != tail && count-- >= 0) { 859 line_print(line); 860 line = line->next; 861 } 862 863 /* Print the blank lines (if any) */ 864 if (loading == FALSE) { 865 while (count-- >= 0) { 866 tputs(CE, 0, _putch); 867 putch('\n'); 868 } 869 } 870 } 871 872 /* 873 * Write_char does a buffered output. 874 */ 875 int write_char(int fd, int c) 876 { 877 screen [out_count++] = c; 878 if (out_count == SCREEN_SIZE) /* Flush on SCREEN_SIZE chars */ 879 return flush_buffer(fd); 880 return FINE; 881 } 882 883 /* 884 * Writeline writes the given string on the given filedescriptor. 885 */ 886 int writeline(register int fd, register char *text) 887 { 888 while(*text) 889 if (write_char(fd, *text++) == ERRORS) 890 return ERRORS; 891 return FINE; 892 } 893 894 /* 895 * Put_line print the given line on the standard output. If offset is not zero 896 * printing will start at that x-coordinate. If the FLAG clear_line is TRUE, 897 * then (screen) line will be cleared when the end of the line has been 898 * reached. 899 */ 900 void put_line(LINE *line, int offset, FLAG clear_line) 901 { 902 register char *textp = line->text; 903 register int count = get_shift(line->shift_count) * -SHIFT_SIZE; 904 int tab_count; /* Used in tab expansion */ 905 906 /* Skip all chars as indicated by the offset and the shift_count field */ 907 while (count < offset) { 908 if (is_tab(*textp++)) 909 count = tab(count); 910 else 911 count++; 912 } 913 914 while (*textp != '\n' && count < XBREAK) { 915 if (is_tab(*textp)) { /* Expand tabs to spaces */ 916 tab_count = tab(count); 917 while (count < XBREAK && count < tab_count) { 918 count++; 919 putch(' '); 920 } 921 textp++; 922 } 923 else { 924 if (*textp >= '\01' && *textp <= '\037') { 925 tputs(SO, 0, _putch); 926 putch(*textp++ + '\100'); 927 tputs(SE, 0, _putch); 928 } 929 else 930 putch(*textp++); 931 count++; 932 } 933 } 934 935 /* If line is longer than XBREAK chars, print the shift_mark */ 936 if (count == XBREAK && *textp != '\n') 937 putch(textp[1]=='\n' ? *textp : SHIFT_MARK); 938 939 /* Clear the rest of the line is clear_line is TRUE */ 940 if (clear_line == TRUE) { 941 tputs(CE, 0, _putch); 942 putch('\n'); 943 } 944 } 945 946 /* 947 * Flush the I/O buffer on filedescriptor fd. 948 */ 949 int flush_buffer(int fd) 950 { 951 if (out_count <= 0) /* There is nothing to flush */ 952 return FINE; 953 if (fd == STD_OUT) { 954 printf("%.*s", out_count, screen); 955 _flush(); 956 } 957 else if (write(fd, screen, out_count) != out_count) { 958 bad_write(fd); 959 return ERRORS; 960 } 961 clear_buffer(); /* Empty buffer */ 962 return FINE; 963 } 964 965 /* 966 * Bad_write() is called when a write failed. Notify the user. 967 */ 968 void bad_write(int fd) 969 { 970 if (fd == STD_OUT) /* Cannot write to terminal? */ 971 exit(1); 972 973 clear_buffer(); 974 build_string(text_buffer, "Command aborted: %s (File incomplete)", 975 (errno == ENOSPC || errno == -ENOSPC) ? 976 "No space on device" : "Write error"); 977 error(text_buffer, NULL); 978 } 979 980 /* 981 * Catch the SIGQUIT signal (^\) send to mined. It turns on the quitflag. 982 */ 983 void catch(int sig) 984 { 985 /* Reset the signal */ 986 signal(SIGQUIT, catch); 987 quit = TRUE; 988 } 989 990 /* 991 * Abort_mined() will leave mined. Confirmation is asked first. 992 */ 993 void abort_mined(void) 994 { 995 quit = FALSE; 996 997 /* Ask for confirmation */ 998 status_line("Really abort? ", NULL); 999 if (getch() != 'y') { 1000 clear_status(); 1001 return; 1002 } 1003 1004 /* Reset terminal */ 1005 raw_mode(OFF); 1006 set_cursor(0, ymax); 1007 putch('\n'); 1008 flush(); 1009 abort(); 1010 } 1011 1012 #define UNDEF _POSIX_VDISABLE 1013 1014 /* 1015 * Set and reset tty into CBREAK or old mode according to argument `state'. It 1016 * also sets all signal characters (except for ^\) to UNDEF. ^\ is caught. 1017 */ 1018 void raw_mode(FLAG state) 1019 { 1020 static struct termios old_tty; 1021 static struct termios new_tty; 1022 1023 if (state == OFF) { 1024 tcsetattr(input_fd, TCSANOW, &old_tty); 1025 return; 1026 } 1027 1028 /* Save old tty settings */ 1029 tcgetattr(input_fd, &old_tty); 1030 1031 /* Set tty to CBREAK mode */ 1032 tcgetattr(input_fd, &new_tty); 1033 new_tty.c_lflag &= ~(ICANON|ECHO|ECHONL); 1034 new_tty.c_iflag &= ~(IXON|IXOFF); 1035 1036 /* Unset signal chars, leave only SIGQUIT set to ^\ */ 1037 new_tty.c_cc[VINTR] = new_tty.c_cc[VSUSP] = UNDEF; 1038 new_tty.c_cc[VQUIT] = '\\' & 037; 1039 signal(SIGQUIT, catch); /* Which is caught */ 1040 1041 tcsetattr(input_fd, TCSANOW, &new_tty); 1042 } 1043 1044 /* 1045 * Panic() is called with an error number and a message. It is called when 1046 * something unrecoverable has happened. 1047 * It writes the message to the terminal, resets the tty and exits. 1048 * Ask the user if he wants to save his file. 1049 */ 1050 void panic(register char *message) 1051 { 1052 extern char yank_file[]; 1053 1054 tputs(CL, 0, _putch); 1055 build_string(text_buffer, "%s\nError code %d\n", message, errno); 1056 (void) write(STD_OUT, text_buffer, length_of(text_buffer)); 1057 1058 if (loading == FALSE) 1059 XT(); /* Check if file can be saved */ 1060 else 1061 (void) unlink(yank_file); 1062 raw_mode(OFF); 1063 1064 abort(); 1065 } 1066 1067 char *alloc(int bytes) 1068 { 1069 char *p; 1070 1071 p = malloc((unsigned) bytes); 1072 if (p == NULL) { 1073 if (loading == TRUE) 1074 panic("File too big."); 1075 panic("Out of memory."); 1076 } 1077 return(p); 1078 } 1079 1080 void free_space(char *p) 1081 { 1082 free(p); 1083 } 1084 1085 /* ======================================================================== * 1086 * Main loops * 1087 * ======================================================================== */ 1088 1089 /* The mapping between input codes and functions. */ 1090 1091 void (*key_map[256])() = { /* map ASCII characters to functions */ 1092 /* 000-017 */ MA, BL, MP, YA, SD, RD, MN, IF, DPC, S, S, DT, LR, S, DNW,LIB, 1093 /* 020-037 */ DPW, WB, GR, SH, DLN, SU, VI, XWT, XT, PT, EL, ESC, I, GOTO, 1094 HIGH, LOW, 1095 /* 040-057 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1096 /* 060-077 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1097 /* 100-117 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1098 /* 120-137 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1099 /* 140-157 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1100 /* 160-177 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, DCC, 1101 /* 200-217 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1102 /* 220-237 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1103 /* 240-257 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1104 /* 260-277 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1105 /* 300-317 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1106 /* 320-337 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1107 /* 340-357 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1108 /* 360-377 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, 1109 }; 1110 1111 int nlines; /* Number of lines in file */ 1112 LINE *header; /* Head of line list */ 1113 LINE *tail; /* Last line in line list */ 1114 LINE *cur_line; /* Current line in use */ 1115 LINE *top_line; /* First line of screen */ 1116 LINE *bot_line; /* Last line of screen */ 1117 char *cur_text; /* Current char on current line in use */ 1118 int last_y; /* Last y of screen. Usually SCREENMAX */ 1119 char screen[SCREEN_SIZE]; /* Output buffer for "writes" and "reads" */ 1120 1121 int x, y; /* x, y coordinates on screen */ 1122 FLAG modified = FALSE; /* Set when file is modified */ 1123 FLAG stat_visible; /* Set if status_line is visible */ 1124 FLAG writable; /* Set if file cannot be written */ 1125 FLAG loading; /* Set if we are loading a file. */ 1126 FLAG quit = FALSE; /* Set when quit character is typed */ 1127 FLAG rpipe = FALSE; /* Set if file should be read from stdin */ 1128 int input_fd = 0; /* Fd for command input */ 1129 int out_count; /* Index in output buffer */ 1130 char file_name[LINE_LEN]; /* Name of file in use */ 1131 char text_buffer[MAX_CHARS]; /* Buffer for modifying text */ 1132 1133 /* Escape sequences. */ 1134 char *CE, *VS, *SO, *SE, *CL, *AL, *CM; 1135 1136 /* 1137 * Yank variables. 1138 */ 1139 FLAG yank_status = NOT_VALID; /* Status of yank_file */ 1140 char yank_file[] = "/tmp/mined.XXXXXX"; 1141 long chars_saved; /* Nr of chars in buffer */ 1142 1143 /* 1144 * Initialize is called when a another file is edited. It free's the allocated 1145 * space and sets modified back to FALSE and fixes the header/tail pointer. 1146 */ 1147 void initialize(void) 1148 { 1149 register LINE *line, *next_line; 1150 1151 /* Delete the whole list */ 1152 for (line = header->next; line != tail; line = next_line) { 1153 next_line = line->next; 1154 free_space(line->text); 1155 free_space((char*)line); 1156 } 1157 1158 /* header and tail should point to itself */ 1159 line->next = line->prev = line; 1160 x = y = 0; 1161 rpipe = modified = FALSE; 1162 } 1163 1164 /* 1165 * Basename() finds the absolute name of the file out of a given path_name. 1166 */ 1167 char *basename(char *path) 1168 { 1169 register char *ptr = path; 1170 register char *last = NULL; 1171 1172 while (*ptr != '\0') { 1173 if (*ptr == '/') 1174 last = ptr; 1175 ptr++; 1176 } 1177 if (last == NULL) 1178 return path; 1179 if (*(last + 1) == '\0') { /* E.g. /usr/tmp/pipo/ */ 1180 *last = '\0'; 1181 return basename(path);/* Try again */ 1182 } 1183 return last + 1; 1184 } 1185 1186 /* 1187 * Load_file loads the file `file' into core. If file is a NULL or the file 1188 * couldn't be opened, just some initializations are done, and a line consisting 1189 * of a `\n' is installed. 1190 */ 1191 void load_file(char *file) 1192 { 1193 register LINE *line = header; 1194 register int len; 1195 long nr_of_chars = 0L; 1196 int fd = -1; /* Filedescriptor for file */ 1197 1198 nlines = 0; /* Zero lines to start with */ 1199 1200 /* Open file */ 1201 writable = TRUE; /* Benefit of the doubt */ 1202 if (file == NULL) { 1203 if (rpipe == FALSE) 1204 status_line("No file.", NULL); 1205 else { 1206 fd = 0; 1207 file = "standard input"; 1208 } 1209 file_name[0] = '\0'; 1210 } 1211 else { 1212 copy_string(file_name, file); /* Save file name */ 1213 if (access(file, 0) < 0) /* Cannot access file. */ 1214 status_line("New file ", file); 1215 else if ((fd = open(file, 0)) < 0) 1216 status_line("Cannot open ", file); 1217 else if (access(file, 2) != 0) /* Set write flag */ 1218 writable = FALSE; 1219 } 1220 1221 /* Read file */ 1222 loading = TRUE; /* Loading file, so set flag */ 1223 1224 if (fd >= 0) { 1225 status_line("Reading ", file); 1226 while ((len = get_line(fd, text_buffer)) != ERRORS) { 1227 line = line_insert(line, text_buffer, len); 1228 nr_of_chars += (long) len; 1229 } 1230 if (nlines == 0) /* The file was empty! */ 1231 line = line_insert(line, "\n", 1); 1232 clear_buffer(); /* Clear output buffer */ 1233 cur_line = header->next; 1234 fstatus("Read", nr_of_chars); 1235 (void) close(fd); /* Close file */ 1236 } 1237 else /* Just install a "\n" */ 1238 (void) line_insert(line, "\n", 1); 1239 1240 reset(header->next, 0); /* Initialize pointers */ 1241 1242 /* Print screen */ 1243 display (0, 0, header->next, last_y); 1244 move_to (0, 0); 1245 flush(); /* Flush buffer */ 1246 loading = FALSE; /* Stop loading, reset flag */ 1247 } 1248 1249 1250 /* 1251 * Get_line reads one line from filedescriptor fd. If EOF is reached on fd, 1252 * get_line() returns ERRORS, else it returns the length of the string. 1253 */ 1254 int get_line(int fd, register char *buffer) 1255 { 1256 static char *last = NULL; 1257 static char *current = NULL; 1258 static int read_chars; 1259 register char *cur_pos = current; 1260 char *begin = buffer; 1261 1262 do { 1263 if (cur_pos == last) { 1264 if ((read_chars = read(fd, screen, SCREEN_SIZE)) <= 0) 1265 break; 1266 last = &screen[read_chars]; 1267 cur_pos = screen; 1268 } 1269 if (*cur_pos == '\0') 1270 *cur_pos = ' '; 1271 } while ((*buffer++ = *cur_pos++) != '\n'); 1272 1273 current = cur_pos; 1274 if (read_chars <= 0) { 1275 if (buffer == begin) 1276 return ERRORS; 1277 if (*(buffer - 1) != '\n') { 1278 if (loading == TRUE) { /* Add '\n' to last line of file */ 1279 *buffer++ = '\n'; 1280 } else { 1281 *buffer = '\0'; 1282 return NO_LINE; 1283 } 1284 } 1285 } 1286 1287 *buffer = '\0'; 1288 return buffer - begin; 1289 } 1290 1291 /* 1292 * Install_line installs the buffer into a LINE structure It returns a pointer 1293 * to the allocated structure. 1294 */ 1295 LINE *install_line(char *buffer, int length) 1296 { 1297 register LINE *new_line = (LINE *) alloc(sizeof(LINE)); 1298 1299 new_line->text = alloc(length + 1); 1300 new_line->shift_count = 0; 1301 copy_string(new_line->text, buffer); 1302 1303 return new_line; 1304 } 1305 1306 int main(int argc, char *argv[]) 1307 { 1308 /* mined is the Minix editor. */ 1309 1310 register int index; /* Index in key table */ 1311 struct winsize winsize; 1312 1313 get_term(); 1314 tputs(VS, 0, _putch); 1315 tputs(CL, 0, _putch); 1316 if (ioctl(STD_OUT, TIOCGWINSZ, &winsize) == 0 && winsize.ws_row != 0) { 1317 ymax = winsize.ws_row - 1; 1318 screenmax = ymax - 1; 1319 } 1320 1321 if (!isatty(0)) { /* Reading from pipe */ 1322 if (argc != 1) { 1323 write(2, "Cannot find terminal.\n", 22); 1324 exit (1); 1325 } 1326 rpipe = TRUE; 1327 modified = TRUE; /* Set modified so he can write */ 1328 open_device(); 1329 } 1330 1331 raw_mode(ON); /* Set tty to appropriate mode */ 1332 1333 header = tail = (LINE *) alloc(sizeof(LINE)); /* Make header of list*/ 1334 header->text = NULL; 1335 header->next = tail->prev = header; 1336 1337 /* Load the file (if any) */ 1338 if (argc < 2) 1339 load_file(NULL); 1340 else { 1341 (void) get_file(NULL, argv[1]); /* Truncate filename */ 1342 load_file(argv[1]); 1343 } 1344 1345 /* Main loop of the editor. */ 1346 for (;;) { 1347 index = getch(); 1348 if (stat_visible == TRUE) 1349 clear_status(); 1350 if (quit == TRUE) 1351 abort_mined(); 1352 else { /* Call the function for this key */ 1353 (*key_map[index])(index); 1354 flush(); /* Flush output (if any) */ 1355 if (quit == TRUE) 1356 quit = FALSE; 1357 } 1358 } 1359 /* NOTREACHED */ 1360 } 1361 1362 /* ======================================================================== * 1363 * Miscellaneous * 1364 * ======================================================================== */ 1365 1366 /* 1367 * Redraw the screen 1368 */ 1369 void RD(void) 1370 { 1371 /* Clear screen */ 1372 tputs(VS, 0, _putch); 1373 tputs(CL, 0, _putch); 1374 1375 /* Print first page */ 1376 display(0, 0, top_line, last_y); 1377 1378 /* Clear last line */ 1379 set_cursor(0, ymax); 1380 tputs(CE, 0, _putch); 1381 move_to(x, y); 1382 } 1383 1384 /* 1385 * Ignore this keystroke. 1386 */ 1387 void I(void) 1388 { 1389 } 1390 1391 /* 1392 * Leave editor. If the file has changed, ask if the user wants to save it. 1393 */ 1394 void XT(void) 1395 { 1396 if (modified == TRUE && ask_save() == ERRORS) 1397 return; 1398 1399 raw_mode(OFF); 1400 set_cursor(0, ymax); 1401 putch('\n'); 1402 flush(); 1403 (void) unlink(yank_file); /* Might not be necessary */ 1404 exit(0); 1405 } 1406 1407 void (*escfunc(int c))(void) 1408 { 1409 if (c == '[') { 1410 /* Start of ASCII escape sequence. */ 1411 c = getch(); 1412 switch (c) { 1413 case 'H': return(HO); 1414 case 'A': return(UP1); 1415 case 'B': return(DN1); 1416 case 'C': return(RT1); 1417 case 'D': return(LF1); 1418 case '@': return(MA); 1419 case 'G': return(FS); 1420 case 'S': return(SR); 1421 case 'T': return(SF); 1422 case 'U': return(PD); 1423 case 'V': return(PU); 1424 case 'Y': return(EF); 1425 } 1426 return(I); 1427 } 1428 return(I); 1429 } 1430 1431 /* 1432 * ESC() wants a count and a command after that. It repeats the 1433 * command count times. If a ^\ is given during repeating, stop looping and 1434 * return to main loop. 1435 */ 1436 void ESC(void) 1437 { 1438 register int count = 0; 1439 register void (*func)(); 1440 int index; 1441 1442 index = getch(); 1443 while (index >= '0' && index <= '9' && quit == FALSE) { 1444 count *= 10; 1445 count += index - '0'; 1446 index = getch(); 1447 } 1448 if (count == 0) { 1449 count = 1; 1450 func = escfunc(index); 1451 } else { 1452 func = key_map[index]; 1453 if (func == ESC) 1454 func = escfunc(getch()); 1455 } 1456 1457 if (func == I) { /* Function assigned? */ 1458 clear_status(); 1459 return; 1460 } 1461 1462 while (count-- > 0 && quit == FALSE) { 1463 if (stat_visible == TRUE) 1464 clear_status(); 1465 (*func)(index); 1466 flush(); 1467 } 1468 1469 if (quit == TRUE) /* Abort has been given */ 1470 error("Aborted", NULL); 1471 } 1472 1473 /* 1474 * Ask the user if he wants to save his file or not. 1475 */ 1476 int ask_save(void) 1477 { 1478 register int c; 1479 1480 status_line(file_name[0] ? basename(file_name) : "[buffer]" , 1481 " has been modified. Save? (y/n)"); 1482 1483 while((c = getch()) != 'y' && c != 'n' && quit == FALSE) { 1484 ring_bell(); 1485 flush(); 1486 } 1487 1488 clear_status(); 1489 1490 if (c == 'y') 1491 return WT(); 1492 1493 if (c == 'n') 1494 return FINE; 1495 1496 quit = FALSE; /* Abort character has been given */ 1497 return ERRORS; 1498 } 1499 1500 /* 1501 * Line_number() finds the line number we're on. 1502 */ 1503 int line_number(void) 1504 { 1505 register LINE *line = header->next; 1506 register int count = 1; 1507 1508 while (line != cur_line) { 1509 count++; 1510 line = line->next; 1511 } 1512 1513 return count; 1514 } 1515 1516 /* 1517 * Display a line telling how many chars and lines the file contains. Also tell 1518 * whether the file is readonly and/or modified. 1519 */ 1520 void file_status(char *message, register long count, char *file, int lines, 1521 FLAG writefl, FLAG changed) 1522 { 1523 register LINE *line; 1524 char msg[LINE_LEN + 40];/* Buffer to hold line */ 1525 char yank_msg[LINE_LEN];/* Buffer for msg of yank_file */ 1526 1527 if (count < 0) /* Not valid. Count chars in file */ 1528 for (line = header->next; line != tail; line = line->next) 1529 count += length_of(line->text); 1530 1531 if (yank_status != NOT_VALID) /* Append buffer info */ 1532 build_string(yank_msg, " Buffer: %D char%s.", chars_saved, 1533 (chars_saved == 1L) ? "" : "s"); 1534 else 1535 yank_msg[0] = '\0'; 1536 1537 build_string(msg, "%s %s%s%s %d line%s %D char%s.%s Line %d", message, 1538 (rpipe == TRUE && *message != '[') ? "standard input" : basename(file), 1539 (changed == TRUE) ? "*" : "", 1540 (writefl == FALSE) ? " (Readonly)" : "", 1541 lines, (lines == 1) ? "" : "s", 1542 count, (count == 1L) ? "" : "s", 1543 yank_msg, line_number()); 1544 1545 if (length_of(msg) + 1 > LINE_LEN - 4) { 1546 msg[LINE_LEN - 4] = SHIFT_MARK; /* Overflow on status line */ 1547 msg[LINE_LEN - 3] = '\0'; 1548 } 1549 status_line(msg, NULL); /* Print the information */ 1550 } 1551 1552 /* 1553 * Build_string() prints the arguments as described in fmt, into the buffer. 1554 * %s indicates an argument string, %d indicated an argument number. 1555 */ 1556 void build_string(char *buf, char *fmt, ...) 1557 { 1558 va_list argptr; 1559 char *scanp; 1560 1561 va_start(argptr, fmt); 1562 1563 while (*fmt) { 1564 if (*fmt == '%') { 1565 fmt++; 1566 switch (*fmt++) { 1567 case 's' : 1568 scanp = va_arg(argptr, char *); 1569 break; 1570 case 'd' : 1571 scanp = num_out((long) va_arg(argptr, int)); 1572 break; 1573 case 'D' : 1574 scanp = num_out((long) va_arg(argptr, long)); 1575 break; 1576 default : 1577 scanp = ""; 1578 } 1579 while ((*buf++ = *scanp++)) 1580 ; 1581 buf--; 1582 } 1583 else 1584 *buf++ = *fmt++; 1585 } 1586 va_end(argptr); 1587 *buf = '\0'; 1588 } 1589 1590 /* 1591 * Output an (unsigned) long in a 10 digit field without leading zeros. 1592 * It returns a pointer to the first digit in the buffer. 1593 */ 1594 char *num_out(long number) 1595 { 1596 static char num_buf[11]; /* Buffer to build number */ 1597 register long digit; /* Next digit of number */ 1598 register long pow = 1000000000L; /* Highest ten power of long */ 1599 FLAG digit_seen = FALSE; 1600 int i; 1601 1602 for (i = 0; i < 10; i++) { 1603 digit = number / pow; /* Get next digit */ 1604 if (digit == 0L && digit_seen == FALSE && i != 9) 1605 num_buf[i] = ' '; 1606 else { 1607 num_buf[i] = '0' + (char) digit; 1608 number -= digit * pow; /* Erase digit */ 1609 digit_seen = TRUE; 1610 } 1611 pow /= 10L; /* Get next digit */ 1612 } 1613 for (i = 0; num_buf[i] == ' '; i++) /* Skip leading spaces */ 1614 ; 1615 return (&num_buf[i]); 1616 } 1617 1618 /* 1619 * Get_number() read a number from the terminal. The last character typed in is 1620 * returned. ERRORS is returned on a bad number. The resulting number is put 1621 * into the integer the arguments points to. 1622 */ 1623 int get_number(char *message, int *result) 1624 { 1625 register int index; 1626 register int count = 0; 1627 1628 status_line(message, NULL); 1629 1630 index = getch(); 1631 if (quit == FALSE && (index < '0' || index > '9')) { 1632 error("Bad count", NULL); 1633 return ERRORS; 1634 } 1635 1636 /* Convert input to a decimal number */ 1637 while (index >= '0' && index <= '9' && quit == FALSE) { 1638 count *= 10; 1639 count += index - '0'; 1640 index = getch(); 1641 } 1642 1643 if (quit == TRUE) { 1644 clear_status(); 1645 return ERRORS; 1646 } 1647 1648 *result = count; 1649 return index; 1650 } 1651 1652 /* 1653 * Input() reads a string from the terminal. When the KILL character is typed, 1654 * it returns ERRORS. 1655 */ 1656 int input(char *inbuf, FLAG clearfl) 1657 { 1658 register char *ptr; 1659 register char c; /* Character read */ 1660 1661 ptr = inbuf; 1662 1663 *ptr = '\0'; 1664 while (quit == FALSE) { 1665 flush(); 1666 switch (c = getch()) { 1667 case '\b' : /* Erase previous char */ 1668 if (ptr > inbuf) { 1669 ptr--; 1670 tputs(SE, 0, _putch); 1671 if (is_tab(*ptr)) 1672 string_print(" \b\b\b \b\b"); 1673 else 1674 string_print(" \b\b \b"); 1675 tputs(SO, 0, _putch); 1676 string_print(" \b"); 1677 *ptr = '\0'; 1678 } 1679 else 1680 ring_bell(); 1681 break; 1682 case '\n' : /* End of input */ 1683 /* If inbuf is empty clear status_line */ 1684 return (ptr == inbuf && clearfl == TRUE) ? NO_INPUT :FINE; 1685 default : /* Only read ASCII chars */ 1686 if ((c >= ' ' && c <= '~') || c == '\t') { 1687 *ptr++ = c; 1688 *ptr = '\0'; 1689 if (c == '\t') 1690 string_print("^I"); 1691 else 1692 putch(c); 1693 string_print(" \b"); 1694 } 1695 else 1696 ring_bell(); 1697 } 1698 } 1699 quit = FALSE; 1700 return ERRORS; 1701 } 1702 1703 /* 1704 * Get_file() reads a filename from the terminal. Filenames longer than 1705 * FILE_LENGHT chars are truncated. 1706 */ 1707 int get_file(char *message, char *file) 1708 { 1709 char *ptr; 1710 int ret; 1711 1712 if (message == NULL || (ret = get_string(message, file, TRUE)) == FINE) { 1713 if (length_of((ptr = basename(file))) > NAME_MAX) 1714 ptr[NAME_MAX] = '\0'; 1715 } 1716 return ret; 1717 } 1718 1719 /* ======================================================================== * 1720 * UNIX I/O Routines * 1721 * ======================================================================== */ 1722 1723 int _getch(void) 1724 { 1725 char c; 1726 1727 if (read(input_fd, &c, 1) != 1 && quit == FALSE) 1728 panic ("Cannot read 1 byte from input"); 1729 return c & 0377; 1730 } 1731 1732 void _flush(void) 1733 { 1734 (void) fflush(stdout); 1735 } 1736 1737 int _putch(int c) 1738 { 1739 if (write_char(STD_OUT, c) == FINE) 1740 return c; 1741 else 1742 return EOF; 1743 } 1744 1745 void get_term(void) 1746 { 1747 static char termbuf[50]; 1748 extern char *tgetstr(), *getenv(); 1749 char *loc = termbuf; 1750 char entry[2048]; 1751 1752 if (tgetent(entry, getenv("TERM")) <= 0) { 1753 printf("Unknown terminal.\n"); 1754 exit(1); 1755 } 1756 1757 AL = tgetstr("al", &loc); 1758 CE = tgetstr("ce", &loc); 1759 VS = tgetstr("vs", &loc); 1760 CL = tgetstr("cl", &loc); 1761 SO = tgetstr("so", &loc); 1762 SE = tgetstr("se", &loc); 1763 CM = tgetstr("cm", &loc); 1764 ymax = tgetnum("li") - 1; 1765 screenmax = ymax - 1; 1766 1767 if (!CE || !SO || !SE || !CL || !AL || !CM) { 1768 printf("Sorry, no mined on this type of terminal\n"); 1769 exit(1); 1770 } 1771 } 1772